Merge branch 'rebrand-ui' of https://github.com/frappe/erpnext into shopping-cart
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..24f122a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+# Root editor config file
+root = true
+
+# Common settings
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+charset = utf-8
+
+# python, js indentation settings
+[{*.py,*.js}]
+indent_style = tab
+indent_size = 4
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..26bb7ab
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Community Forum
+ url: https://discuss.erpnext.com/
+ about: For general QnA, discussions and community help.
diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py
index b603ed5..9cc4663 100644
--- a/.github/helper/documentation.py
+++ b/.github/helper/documentation.py
@@ -21,8 +21,8 @@
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
- _, org, repo, _type, ref = parsed_url.path.split('/')
- if org == "frappe" and repo in docs_repos:
+ parts = parsed_url.path.split('/')
+ if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
diff --git a/.travis/site_config.json b/.travis/site_config.json
index dae8009..572bbd0 100644
--- a/.travis/site_config.json
+++ b/.travis/site_config.json
@@ -9,5 +9,6 @@
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
- "install_apps": ["erpnext"]
+ "install_apps": ["erpnext"],
+ "throttle_user_limit": 100
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0f6a521..15782a2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
<p>ERP made simple</p>
</p>
-[](https://travis-ci.com/frappe/erpnext)
+[](https://travis-ci.com/frappe/erpnext)
[](https://www.codetriage.com/frappe/erpnext)
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index 39bf4b0..85f54f9 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -6,9 +6,8 @@
from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute
-from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
-from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
-
+from frappe.utils.dashboard import cache_source
+from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
deleted file mode 100644
index 2917a36..0000000
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ /dev/null
@@ -1,157 +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 {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\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]"
- }
- ],
- "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,
- "icon": "accounting",
- "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/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 3fc109b..849df18 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
@@ -910,98 +910,8 @@
},
"is_group": 1
},
- "Passiva": {
+ "Passiva - Verbindlichkeiten": {
"root_type": "Liability",
- "A - Eigenkapital": {
- "account_type": "Equity",
- "is_group": 1,
- "I - Gezeichnetes Kapital": {
- "account_type": "Equity",
- "is_group": 1,
- "Gezeichnetes Kapital": {
- "account_type": "Equity",
- "account_number": "2900"
- },
- "Ausstehende Einlagen auf das gezeichnete Kapital": {
- "account_number": "2910",
- "is_group": 1
- }
- },
- "II - Kapitalr\u00fccklage": {
- "account_type": "Equity",
- "is_group": 1,
- "Kapitalr\u00fccklage": {
- "account_number": "2920"
- }
- },
- "III - Gewinnr\u00fccklagen": {
- "account_type": "Equity",
- "1 - gesetzliche R\u00fccklage": {
- "account_type": "Equity",
- "is_group": 1,
- "Gesetzliche R\u00fccklage": {
- "account_number": "2930"
- }
- },
- "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
- "account_type": "Equity",
- "is_group": 1
- },
- "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
- "account_type": "Equity",
- "is_group": 1,
- "Satzungsm\u00e4\u00dfige R\u00fccklagen": {
- "account_number": "2950"
- }
- },
- "4 - andere Gewinnr\u00fccklagen": {
- "account_type": "Equity",
- "is_group": 1,
- "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
- "is_group": 1,
- "Gewinnr\u00fccklagen (BilMoG)": {
- "account_number": "2963"
- },
- "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
- "account_number": "2964"
- },
- "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
- "account_number": "2965"
- },
- "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
- "account_number": "2966"
- }
- },
- "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
- "account_number": "2967"
- },
- "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
- "account_number": "2968"
- },
- "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
- "account_number": "2969"
- }
- },
- "is_group": 1
- },
- "IV - Gewinnvortrag/Verlustvortrag": {
- "account_type": "Equity",
- "is_group": 1,
- "Gewinnvortrag vor Verwendung": {
- "account_number": "2970"
- },
- "Verlustvortrag vor Verwendung": {
- "account_number": "2978"
- }
- },
- "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
- "account_type": "Equity",
- "is_group": 1
- },
- "Einlagen stiller Gesellschafter": {
- "account_number": "9295"
- }
- },
"B - R\u00fcckstellungen": {
"is_group": 1,
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@@ -1618,6 +1528,143 @@
},
"is_group": 1
},
+ "Passiva - Eigenkapital": {
+ "root_type": "Equity",
+ "A - Eigenkapital": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "I - Gezeichnetes Kapital": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Gezeichnetes Kapital": {
+ "account_number": "2900",
+ "account_type": "Equity"
+ },
+ "Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
+ "account_number": "2901"
+ },
+ "Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
+ "account_number": "2902"
+ },
+ "Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
+ "account_number": "2903"
+ },
+ "R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+ "account_number": "2906"
+ },
+ "Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+ "account_number": "2907"
+ },
+ "Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
+ "account_number": "2908"
+ },
+ "Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
+ "account_number": "2910"
+ }
+ },
+ "II - Kapitalr\u00fccklage": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Kapitalr\u00fccklage": {
+ "account_number": "2920"
+ },
+ "Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
+ "account_number": "2925"
+ },
+ "Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
+ "account_number": "2926"
+ },
+ "Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
+ "account_number": "2927"
+ },
+ "Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
+ "account_number": "2928"
+ },
+ "Nachschusskapital (Gegenkonto 1299)": {
+ "account_number": "2929"
+ }
+ },
+ "III - Gewinnr\u00fccklagen": {
+ "account_type": "Equity",
+ "1 - gesetzliche R\u00fccklage": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Gesetzliche R\u00fccklage": {
+ "account_number": "2930"
+ }
+ },
+ "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+ "account_number": "2935"
+ }
+ },
+ "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Satzungsm\u00e4\u00dfige R\u00fccklagen": {
+ "account_number": "2950"
+ }
+ },
+ "4 - andere Gewinnr\u00fccklagen": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Andere Gewinnr\u00fccklagen": {
+ "account_number": "2960"
+ },
+ "Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
+ "account_number": "2961"
+ },
+ "Eigenkapitalanteil von Wertaufholungen": {
+ "account_number": "2962"
+ },
+ "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
+ "is_group": 1,
+ "Gewinnr\u00fccklagen (BilMoG)": {
+ "account_number": "2963"
+ },
+ "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
+ "account_number": "2964"
+ },
+ "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
+ "account_number": "2965"
+ },
+ "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
+ "account_number": "2966"
+ }
+ },
+ "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
+ "account_number": "2967"
+ },
+ "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+ "account_number": "2968"
+ },
+ "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+ "account_number": "2969"
+ }
+ },
+ "is_group": 1
+ },
+ "IV - Gewinnvortrag/Verlustvortrag": {
+ "account_type": "Equity",
+ "is_group": 1,
+ "Gewinnvortrag vor Verwendung": {
+ "account_number": "2970"
+ },
+ "Verlustvortrag vor Verwendung": {
+ "account_number": "2978"
+ }
+ },
+ "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
+ "account_type": "Equity",
+ "is_group": 1
+ },
+ "Einlagen stiller Gesellschafter": {
+ "account_number": "9295"
+ }
+ }
+ },
"1 - Umsatzerl\u00f6se": {
"root_type": "Income",
"is_group": 1,
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
index 6c83e3b..acb11e5 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
@@ -245,6 +245,9 @@
"account_number": "2200"
},
_("Duties and Taxes"): {
+ _("TDS Payable"): {
+ "account_number": "2310"
+ },
"account_type": "Tax",
"is_group": 1,
"account_number": "2300"
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 2754633..e9fc5f0 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -9,11 +9,13 @@
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.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
def setUp(self):
+ make_pos_profile()
add_transactions()
add_payments()
@@ -27,6 +29,9 @@
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""")
+ # Delete POS Profile
+ frappe.db.sql("delete from `tabPOS Profile`")
+
frappe.flags.test_bank_transactions_created = False
frappe.flags.test_payments_created = False
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
index 2235298..f795dfa 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
@@ -94,8 +94,7 @@
callback: function(r) {
if(r.message===false) {
frm.set_value("company", "");
- frappe.throw(__(`Transactions against the company already exist!
- Chart Of accounts can be imported for company with no transactions`));
+ frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
} else {
frm.trigger("refresh");
}
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index 8083b21..af8940c 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -137,11 +137,12 @@
"cost_center": erpnext.get_default_cost_center(self.company)
})
- je.append("accounts", {
- "account": self.bank_charges_account,
- "debit_in_account_currency": flt(self.bank_charges),
- "cost_center": erpnext.get_default_cost_center(self.company)
- })
+ if self.bank_charges:
+ je.append("accounts", {
+ "account": self.bank_charges_account,
+ "debit_in_account_currency": flt(self.bank_charges),
+ "cost_center": erpnext.get_default_cost_center(self.company)
+ })
je.append("accounts", {
"account": self.short_term_loan,
diff --git a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
index 3d74d9a..919dd0c 100644
--- a/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
@@ -80,6 +80,7 @@
short_term_loan=self.short_term_loan,
bank_charges_account=self.bank_charges_account,
bank_account=self.bank_account,
+ bank_charges=100
)
je = inv_disc.create_disbursement_entry()
@@ -289,6 +290,7 @@
inv_disc.bank_account=args.bank_account
inv_disc.loan_start_date = args.start or nowdate()
inv_disc.loan_period = args.period or 30
+ inv_disc.bank_charges = flt(args.bank_charges)
for d in invoices:
inv_disc.append("invoices", {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 4573c50..b7bbb74 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-03-25 10:53:52",
@@ -503,7 +504,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-02 18:15:46.955697",
+ "modified": "2020-10-30 13:56:01.121995",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index d839478..cd71273 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -34,6 +34,7 @@
self.validate_entries_for_advance()
self.validate_multi_currency()
self.set_amounts_in_company_currency()
+ self.validate_debit_credit_amount()
self.validate_total_debit_and_credit()
self.validate_against_jv()
self.validate_reference_doc()
@@ -339,8 +340,7 @@
currency=account_currency)
if flt(voucher_total) < (flt(order.advance_paid) + total):
- frappe.throw(_("Advance paid against {0} {1} cannot be greater \
- than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
+ frappe.throw(_("Advance paid against {0} {1} cannot be greater than Grand Total {2}").format(reference_type, reference_name, formatted_voucher_total))
def validate_invoices(self):
"""Validate totals and docstatus for invoices"""
@@ -369,6 +369,11 @@
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
if flt(d.credit > 0): d.against_account = ", ".join(list(set(accounts_debited)))
+ def validate_debit_credit_amount(self):
+ for d in self.get('accounts'):
+ if not flt(d.debit) and not flt(d.credit):
+ frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
+
def validate_total_debit_and_credit(self):
self.set_total_debit_credit()
if self.difference:
diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
index d3040c8..7a06d35 100644
--- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
+++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.js
@@ -1,13 +1,17 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return{
- filters: [
- ['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
- ['Account', 'is_group', '=', 0],
- ['Account', 'company', '=', d.company]
- ]
- }
-});
+frappe.ui.form.on('Mode of Payment', {
+ setup: function(frm) {
+ frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
+ return {
+ filters: [
+ ['Account', 'account_type', 'in', 'Bank, Cash, Receivable'],
+ ['Account', 'is_group', '=', 0],
+ ['Account', 'company', '=', d.company]
+ ]
+ };
+ });
+ },
+});
\ No newline at end of file
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 d51856a..76027a3 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 @@
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)
@@ -155,7 +155,8 @@
"posting_date": row.posting_date,
frappe.scrub(row.party_type): row.party,
"is_pos": 0,
- "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
+ "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
+ "update_stock": 0
})
accounting_dimension = get_accounting_dimensions()
@@ -209,7 +210,7 @@
frappe.db.commit()
if errors:
frappe.msgprint(_("You had {} errors while creating opening invoices. Check {} for more details")
- .format(errors, "<a href='#List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
+ .format(errors, "<a href='/app/List/Error Log' class='variant-click'>Error Log</a>"), indicator="red", title=_("Error Occured"))
return names
def publish(index, total, doctype):
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index 54229f5..bdfe532 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -7,17 +7,24 @@
import unittest
test_dependencies = ["Customer", "Supplier"]
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
class TestOpeningInvoiceCreationTool(unittest.TestCase):
- def make_invoices(self, invoice_type="Sales"):
+ def setUp(self):
+ if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
+ make_company()
+
+ def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
doc = frappe.get_single("Opening Invoice Creation Tool")
- args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
+ args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
+ party_1=party_1, party_2=party_2)
doc.update(args)
return doc.make_invoices()
def test_opening_sales_invoice_creation(self):
- invoices = self.make_invoices()
+ property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
+ invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@@ -27,6 +34,13 @@
}
self.check_expected_values(invoices, expected_value)
+ si = frappe.get_doc("Sales Invoice", invoices[0])
+
+ # Check if update stock is not enabled
+ self.assertEqual(si.update_stock, 0)
+
+ property_setter.delete()
+
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -36,7 +50,7 @@
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
def test_opening_purchase_invoice_creation(self):
- invoices = self.make_invoices(invoice_type="Purchase")
+ invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@@ -46,6 +60,32 @@
}
self.check_expected_values(invoices, expected_value, "Purchase")
+ def test_opening_sales_invoice_creation_with_missing_debit_account(self):
+ company = "_Test Opening Invoice Company"
+ party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
+
+ old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
+ frappe.db.set_value("Company", company, "default_receivable_account", "")
+
+ if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
+ cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
+ "is_group": 1, "company": "_Test Opening Invoice Company"})
+ cc.insert(ignore_mandatory=True)
+ cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
+ "company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
+ cc2.insert()
+
+ frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
+
+ self.make_invoices(company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
+
+ # Check if missing debit account error raised
+ error_log = frappe.db.exists("Error Log", {"error": ["like", "%erpnext.controllers.accounts_controller.AccountMissingError%"]})
+ self.assertTrue(error_log)
+
+ # teardown
+ frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
+
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
@@ -57,7 +97,7 @@
{
"qty": 1.0,
"outstanding_amount": 300,
- "party": "_Test {0}".format(party),
+ "party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -66,7 +106,7 @@
{
"qty": 2.0,
"outstanding_amount": 250,
- "party": "_Test {0} 1".format(party),
+ "party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -76,4 +116,31 @@
})
invoice_dict.update(args)
- return invoice_dict
\ No newline at end of file
+ return invoice_dict
+
+def make_company():
+ if frappe.db.exists("Company", "_Test Opening Invoice Company"):
+ return frappe.get_doc("Company", "_Test Opening Invoice Company")
+
+ company = frappe.new_doc("Company")
+ company.company_name = "_Test Opening Invoice Company"
+ company.abbr = "_TOIC"
+ company.default_currency = "INR"
+ company.country = "India"
+ company.insert()
+ return company
+
+def make_customer(customer=None):
+ customer_name = customer or "Opening Customer"
+ customer = frappe.get_doc({
+ "doctype": "Customer",
+ "customer_name": customer_name,
+ "customer_group": "All Customer Groups",
+ "customer_type": "Company",
+ "territory": "All Territories"
+ })
+ if not frappe.db.exists("Customer", customer_name):
+ customer.insert(ignore_permissions=True)
+ return customer.name
+ else:
+ return frappe.db.exists("Customer", customer_name)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 72149a6..2e1f201 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-06-01 14:38:51.012597",
@@ -587,7 +588,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-09-02 13:39:43.383705",
+ "modified": "2020-10-30 13:56:20.007336",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 11ab020..31a4c8a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -202,17 +202,32 @@
# if account_type not in account_types:
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
- def set_exchange_rate(self):
+ def set_exchange_rate(self, ref_doc=None):
+ self.set_source_exchange_rate(ref_doc)
+ self.set_target_exchange_rate(ref_doc)
+
+ def set_source_exchange_rate(self, ref_doc=None):
if self.paid_from and not self.source_exchange_rate:
if self.paid_from_account_currency == self.company_currency:
self.source_exchange_rate = 1
else:
- self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
- self.company_currency, self.posting_date)
+ if ref_doc:
+ if self.paid_from_account_currency == ref_doc.currency:
+ self.source_exchange_rate = ref_doc.get("exchange_rate")
+ if not self.source_exchange_rate:
+ self.source_exchange_rate = get_exchange_rate(self.paid_from_account_currency,
+ self.company_currency, self.posting_date)
+
+ def set_target_exchange_rate(self, ref_doc=None):
if self.paid_to and not self.target_exchange_rate:
- self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
- self.company_currency, self.posting_date)
+ if ref_doc:
+ if self.paid_to_account_currency == ref_doc.currency:
+ self.target_exchange_rate = ref_doc.get("exchange_rate")
+
+ if not self.target_exchange_rate:
+ self.target_exchange_rate = get_exchange_rate(self.paid_to_account_currency,
+ self.company_currency, self.posting_date)
def validate_mandatory(self):
for field in ("paid_amount", "received_amount", "source_exchange_rate", "target_exchange_rate"):
@@ -282,9 +297,10 @@
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
- frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
- If this is undesirable please cancel the corresponding Payment Entry.")
- .format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
+ frappe.msgprint(
+ _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
+ .format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
+ + "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange")
@@ -909,22 +925,24 @@
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
- if party_account_currency == company_currency:
- if ref_doc.doctype == "Expense Claim":
+ if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
- elif ref_doc.doctype == "Employee Advance":
- total_amount = ref_doc.advance_amount
- else:
+ elif ref_doc.doctype == "Employee Advance":
+ total_amount = ref_doc.advance_amount
+ exchange_rate = ref_doc.get("exchange_rate")
+ if party_account_currency != ref_doc.currency:
+ total_amount = flt(total_amount) * flt(exchange_rate)
+ if not total_amount:
+ if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
- exchange_rate = 1
- else:
- total_amount = ref_doc.grand_total
-
+ exchange_rate = 1
+ else:
+ total_amount = ref_doc.grand_total
+ if not exchange_rate:
# Get the exchange rate from the original ref doc
- # or get it based on the posting date of the ref doc
+ # or get it based on the posting date of the ref doc.
exchange_rate = ref_doc.get("conversion_rate") or \
get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
-
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
@@ -932,11 +950,15 @@
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
- flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
elif reference_doctype == "Employee Advance":
- outstanding_amount = ref_doc.advance_amount - flt(ref_doc.paid_amount)
+ outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+ if party_account_currency != ref_doc.currency:
+ outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
+ if party_account_currency == company_currency:
+ exchange_rate = 1
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
- # Get the exchange rate based on the posting date of the ref doc
+ # Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency,
company_currency, ref_doc.posting_date)
@@ -948,102 +970,104 @@
"bill_no": bill_no
})
+def get_amounts_based_on_reference_doctype(reference_doctype, ref_doc, party_account_currency, company_currency, reference_name):
+ total_amount, outstanding_amount, exchange_rate = None
+ if reference_doctype == "Fees":
+ total_amount = ref_doc.get("grand_total")
+ exchange_rate = 1
+ outstanding_amount = ref_doc.get("outstanding_amount")
+ elif reference_doctype == "Dunning":
+ total_amount = ref_doc.get("dunning_amount")
+ exchange_rate = 1
+ outstanding_amount = ref_doc.get("dunning_amount")
+ elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
+ total_amount = ref_doc.get("total_amount")
+ if ref_doc.multi_currency:
+ exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+ else:
+ exchange_rate = 1
+ outstanding_amount = get_outstanding_on_journal_entry(reference_name)
+
+ return total_amount, outstanding_amount, exchange_rate
+
+def get_amounts_based_on_ref_doc(reference_doctype, ref_doc, party_account_currency, company_currency):
+ total_amount, outstanding_amount, exchange_rate = None
+ if ref_doc.doctype == "Expense Claim":
+ 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)
+
+ if not exchange_rate:
+ # Get the exchange rate from the original ref doc
+ # or get it based on the posting date of the ref doc
+ exchange_rate = ref_doc.get("conversion_rate") or \
+ get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
+
+ outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
+ reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency)
+
+ return total_amount, outstanding_amount, exchange_rate, bill_no
+
+def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
+ total_amount = ref_doc.advance_amount
+ exchange_rate = ref_doc.get("exchange_rate")
+ if party_account_currency != ref_doc.currency:
+ total_amount = flt(total_amount) * flt(exchange_rate)
+
+ return total_amount, exchange_rate
+
+def get_total_amount_exchange_rate_base_on_currency(party_account_currency, company_currency, ref_doc):
+ exchange_rate = None
+ if party_account_currency == company_currency:
+ total_amount = ref_doc.base_grand_total
+ exchange_rate = 1
+ else:
+ total_amount = ref_doc.grand_total
+
+ return total_amount, exchange_rate
+
+def get_bill_no_and_update_amounts(reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency):
+ outstanding_amount, bill_no = None
+ if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
+ outstanding_amount = ref_doc.get("outstanding_amount")
+ bill_no = ref_doc.get("bill_no")
+ elif reference_doctype == "Expense Claim":
+ outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) + flt(ref_doc.get("total_taxes_and_charges"))\
+ - flt(ref_doc.get("total_amount_reimbursed")) - flt(ref_doc.get("total_advance_amount"))
+ elif reference_doctype == "Employee Advance":
+ outstanding_amount = (flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount))
+ if party_account_currency != ref_doc.currency:
+ outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
+ if party_account_currency == company_currency:
+ exchange_rate = 1
+ else:
+ outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
+
+ return outstanding_amount, exchange_rate, bill_no
+
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
+ reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
- if dt in ("Sales Invoice", "Sales Order", "Dunning"):
- party_type = "Customer"
- elif dt in ("Purchase Invoice", "Purchase Order"):
- party_type = "Supplier"
- elif dt in ("Expense Claim", "Employee Advance"):
- party_type = "Employee"
- elif dt in ("Fees"):
- party_type = "Student"
-
- # party account
- if dt == "Sales Invoice":
- party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
- elif dt == "Purchase Invoice":
- party_account = doc.credit_to
- elif dt == "Fees":
- party_account = doc.receivable_account
- elif dt == "Employee Advance":
- party_account = doc.advance_account
- elif dt == "Expense Claim":
- party_account = doc.payable_account
- else:
- party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
-
- if dt not in ("Sales Invoice", "Purchase Invoice"):
- party_account_currency = get_account_currency(party_account)
- else:
- party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
-
- # payment type
- if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
- or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
- payment_type = "Receive"
- else:
- payment_type = "Pay"
-
- # amounts
- grand_total = outstanding_amount = 0
- if party_amount:
- grand_total = outstanding_amount = party_amount
- elif dt in ("Sales Invoice", "Purchase Invoice"):
- if party_account_currency == doc.company_currency:
- grand_total = doc.base_rounded_total or doc.base_grand_total
- else:
- grand_total = doc.rounded_total or doc.grand_total
- outstanding_amount = doc.outstanding_amount
- elif dt in ("Expense Claim"):
- grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
- outstanding_amount = doc.grand_total \
- - doc.total_amount_reimbursed
- elif dt == "Employee Advance":
- grand_total = doc.advance_amount
- outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
- elif dt == "Fees":
- grand_total = doc.grand_total
- outstanding_amount = doc.outstanding_amount
- elif dt == "Dunning":
- grand_total = doc.grand_total
- outstanding_amount = doc.grand_total
- else:
- if party_account_currency == doc.company_currency:
- grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
- else:
- grand_total = flt(doc.get("rounded_total") or doc.grand_total)
- outstanding_amount = grand_total - flt(doc.advance_paid)
+ party_type = set_party_type(dt)
+ party_account = set_party_account(dt, dn, doc, party_type)
+ party_account_currency = set_party_account_currency(dt, party_account, doc)
+ payment_type = set_payment_type(dt, doc)
+ grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc)
# bank or cash
- bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
- account=bank_account)
+ bank = get_bank_cash_account(doc, bank_account)
- if not bank:
- bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
- account=bank_account)
-
- paid_amount = received_amount = 0
- if party_account_currency == bank.account_currency:
- paid_amount = received_amount = abs(outstanding_amount)
- elif payment_type == "Receive":
- paid_amount = abs(outstanding_amount)
- if bank_amount:
- received_amount = bank_amount
- else:
- received_amount = paid_amount * doc.get('conversion_rate', 1)
- else:
- received_amount = abs(outstanding_amount)
- if bank_amount:
- paid_amount = bank_amount
- else:
- # if party account currency and bank currency is different then populate paid amount as well
- paid_amount = received_amount * doc.get('conversion_rate', 1)
+ paid_amount, received_amount = set_paid_amount_and_received_amount(
+ dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
@@ -1115,10 +1139,120 @@
pe.setup_party_account_field()
pe.set_missing_values()
if party_account and bank:
- pe.set_exchange_rate()
+ if dt == "Employee Advance":
+ reference_doc = doc
+ pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
return pe
+def get_bank_cash_account(doc, bank_account):
+ bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
+ account=bank_account)
+
+ if not bank:
+ bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
+ account=bank_account)
+
+ return bank
+
+def set_party_type(dt):
+ if dt in ("Sales Invoice", "Sales Order", "Dunning"):
+ party_type = "Customer"
+ elif dt in ("Purchase Invoice", "Purchase Order"):
+ party_type = "Supplier"
+ elif dt in ("Expense Claim", "Employee Advance"):
+ party_type = "Employee"
+ elif dt in ("Fees"):
+ party_type = "Student"
+ return party_type
+
+def set_party_account(dt, dn, doc, party_type):
+ if dt == "Sales Invoice":
+ party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
+ elif dt == "Purchase Invoice":
+ party_account = doc.credit_to
+ elif dt == "Fees":
+ party_account = doc.receivable_account
+ elif dt == "Employee Advance":
+ party_account = doc.advance_account
+ elif dt == "Expense Claim":
+ party_account = doc.payable_account
+ else:
+ party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
+ return party_account
+
+def set_party_account_currency(dt, party_account, doc):
+ if dt not in ("Sales Invoice", "Purchase Invoice"):
+ party_account_currency = get_account_currency(party_account)
+ else:
+ party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
+ return party_account_currency
+
+def set_payment_type(dt, doc):
+ if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
+ or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
+ payment_type = "Receive"
+ else:
+ payment_type = "Pay"
+ return payment_type
+
+def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_currency, doc):
+ grand_total = outstanding_amount = 0
+ if party_amount:
+ grand_total = outstanding_amount = party_amount
+ elif dt in ("Sales Invoice", "Purchase Invoice"):
+ if party_account_currency == doc.company_currency:
+ grand_total = doc.base_rounded_total or doc.base_grand_total
+ else:
+ grand_total = doc.rounded_total or doc.grand_total
+ outstanding_amount = doc.outstanding_amount
+ elif dt in ("Expense Claim"):
+ grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
+ outstanding_amount = doc.grand_total \
+ - doc.total_amount_reimbursed
+ elif dt == "Employee Advance":
+ grand_total = flt(doc.advance_amount)
+ outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
+ if party_account_currency != doc.currency:
+ grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
+ outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
+ elif dt == "Fees":
+ grand_total = doc.grand_total
+ outstanding_amount = doc.outstanding_amount
+ elif dt == "Dunning":
+ grand_total = doc.grand_total
+ outstanding_amount = doc.grand_total
+ else:
+ if party_account_currency == doc.company_currency:
+ grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
+ else:
+ grand_total = flt(doc.get("rounded_total") or doc.grand_total)
+ outstanding_amount = grand_total - flt(doc.advance_paid)
+ return grand_total, outstanding_amount
+
+def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc):
+ paid_amount = received_amount = 0
+ if party_account_currency == bank.account_currency:
+ paid_amount = received_amount = abs(outstanding_amount)
+ elif payment_type == "Receive":
+ paid_amount = abs(outstanding_amount)
+ if bank_amount:
+ received_amount = bank_amount
+ else:
+ received_amount = paid_amount * doc.get('conversion_rate', 1)
+ if dt == "Employee Advance":
+ received_amount = paid_amount * doc.get('exchange_rate', 1)
+ else:
+ received_amount = abs(outstanding_amount)
+ if bank_amount:
+ paid_amount = bank_amount
+ else:
+ # if party account currency and bank currency is different then populate paid amount as well
+ paid_amount = received_amount * doc.get('conversion_rate', 1)
+ if dt == "Employee Advance":
+ paid_amount = received_amount * doc.get('exchange_rate', 1)
+ return paid_amount, received_amount
+
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 1cff3c6..5bc57b4 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2020-01-24 15:29:29.933693",
@@ -1580,7 +1581,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2020-09-28 16:51:24.641755",
+ "modified": "2020-10-30 13:56:51.056083",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index a7e20a0..d486ff6 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -39,6 +39,7 @@
self.validate_serialised_or_batched_item()
self.validate_stock_availablility()
self.validate_return_items_qty()
+ self.validate_non_stock_items()
self.set_status()
self.set_account_for_mode_of_payment()
self.validate_pos()
@@ -174,6 +175,14 @@
_("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")
+ if not is_stock_item:
+ frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
+ d.idx, frappe.bold(d.item_code)
+ ), title=_("Invalid Item"))
def validate_mode_of_payment(self):
if len(self.payments) == 0:
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 558e21c..7f4f755 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -35,6 +35,15 @@
};
});
+ frm.set_query("taxes_and_charges", function() {
+ return {
+ filters: [
+ ['Sales Taxes and Charges Template', 'company', '=', frm.doc.company],
+ ['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
+ ]
+ };
+ });
+
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index 570111a..d856ae3 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -14,7 +14,6 @@
"column_break_9",
"update_stock",
"ignore_pricing_rule",
- "hide_unavailable_items",
"warehouse",
"campaign",
"company_address",
@@ -23,6 +22,9 @@
"section_break_11",
"payments",
"section_break_14",
+ "hide_images",
+ "hide_unavailable_items",
+ "auto_add_item_to_cart",
"item_groups",
"column_break_16",
"customer_groups",
@@ -124,7 +126,8 @@
},
{
"fieldname": "section_break_14",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Configuration"
},
{
"description": "Only show Items from these Item Groups",
@@ -314,13 +317,25 @@
"fieldname": "hide_unavailable_items",
"fieldtype": "Check",
"label": "Hide Unavailable Items"
+ },
+ {
+ "default": "0",
+ "fieldname": "hide_images",
+ "fieldtype": "Check",
+ "label": "Hide Images"
+ },
+ {
+ "default": "0",
+ "fieldname": "auto_add_item_to_cart",
+ "fieldtype": "Check",
+ "label": "Automatically Add Filtered Item To Cart"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-10-29 13:18:38.795925",
+ "modified": "2020-12-10 13:59:28.877572",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index edf8659..62dc1fc 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -70,6 +70,7 @@
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
def make_pos_profile(**args):
+ frappe.db.sql("delete from `tabPOS Payment Method`")
frappe.db.sql("delete from `tabPOS Profile`")
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
index c92b58b..d79ad5f 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js
@@ -42,56 +42,56 @@
<tr><td>
<h4>
<i class="fa fa-hand-right"></i>
- ${__('Notes')}
+ {{__('Notes')}}
</h4>
<ul>
<li>
- ${__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}
+ {{__("Pricing Rule is made to overwrite Price List / define discount percentage, based on some criteria.")}}
</li>
<li>
- ${__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}
+ {{__("If selected Pricing Rule is made for 'Rate', it will overwrite Price List. Pricing Rule rate is the final rate, so no further discount should be applied. Hence, in transactions like Sales Order, Purchase Order etc, it will be fetched in 'Rate' field, rather than 'Price List Rate' field.")}}
</li>
<li>
- ${__('Discount Percentage can be applied either against a Price List or for all Price List.')}
+ {{__('Discount Percentage can be applied either against a Price List or for all Price List.')}}
</li>
<li>
- ${__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}
+ {{__('To not apply Pricing Rule in a particular transaction, all applicable Pricing Rules should be disabled.')}}
</li>
</ul>
</td></tr>
<tr><td>
<h4><i class="fa fa-question-sign"></i>
- ${__('How Pricing Rule is applied?')}
+ {{__('How Pricing Rule is applied?')}}
</h4>
<ol>
<li>
- ${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}
+ {{__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}}
</li>
<li>
- ${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}
+ {{__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")}}
</li>
<li>
- ${__('Pricing Rules are further filtered based on quantity.')}
+ {{__('Pricing Rules are further filtered based on quantity.')}}
</li>
<li>
- ${__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}
+ {{__('If two or more Pricing Rules are found based on the above conditions, Priority is applied. Priority is a number between 0 to 20 while default value is zero (blank). Higher number means it will take precedence if there are multiple Pricing Rules with same conditions.')}}
</li>
<li>
- ${__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}
+ {{__('Even if there are multiple Pricing Rules with highest priority, then following internal priorities are applied:')}}
<ul>
<li>
- ${__('Item Code > Item Group > Brand')}
+ {{__('Item Code > Item Group > Brand')}}
</li>
<li>
- ${__('Customer > Customer Group > Territory')}
+ {{__('Customer > Customer Group > Territory')}}
</li>
<li>
- ${__('Supplier > Supplier Type')}
+ {{__('Supplier > Supplier Type')}}
</li>
</ul>
</li>
<li>
- ${__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}
+ {{__('If multiple Pricing Rules continue to prevail, users are asked to set Priority manually to resolve conflict.')}}
</li>
</ol>
</td></tr>
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index cc8ed4b..d08a854 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -406,6 +406,7 @@
"fieldtype": "Column Break"
},
{
+ "default": "0",
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate",
"fieldtype": "Currency",
@@ -469,6 +470,7 @@
"options": "UOM"
},
{
+ "description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Rate"
@@ -563,7 +565,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
- "modified": "2020-10-28 16:53:14.416172",
+ "modified": "2020-12-04 00:36:24.698219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index ec0a485..af8d21d 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -521,6 +521,22 @@
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()
+ def test_pricing_rule_for_transaction(self):
+ make_item("Water Flask 1")
+ frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+ make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
+ apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
+
+ si = create_sales_invoice(qty=5, do_not_submit=True)
+ self.assertEquals(len(si.items), 2)
+ self.assertEquals(si.items[1].rate, 10)
+
+ si1 = create_sales_invoice(qty=2, do_not_submit=True)
+ self.assertEquals(len(si1.items), 1)
+
+ for doc in [si, si1]:
+ doc.delete()
+
def make_pricing_rule(**args):
args = frappe._dict(args)
@@ -539,20 +555,23 @@
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
"discount_percentage": args.discount_percentage or 0.0,
"rate": args.rate or 0.0,
- "margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})
- if args.get("priority"):
- doc.priority = args.get("priority")
+ for field in ["free_item", "free_qty", "free_item_rate", "priority",
+ "margin_type", "price_or_product_discount"]:
+ if args.get(field):
+ doc.set(field, args.get(field))
apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
- doc.append(child_table.get(doc.apply_on), {
- apply_on: args.get(apply_on) or "_Test Item"
- })
+
+ if doc.apply_on != "Transaction":
+ doc.append(child_table.get(doc.apply_on), {
+ apply_on: args.get(apply_on) or "_Test Item"
+ })
doc.insert(ignore_permissions=True)
if args.get(apply_on) and apply_on != "item_code":
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b003328..2c7cd14 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -457,6 +457,9 @@
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
doc.total, pricing_rules)
+ if not pricing_rules:
+ remove_free_item(doc)
+
for d in pricing_rules:
if d.price_or_product_discount == 'Price':
if d.apply_discount_on:
@@ -480,6 +483,12 @@
get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
+ doc.calculate_taxes_and_totals()
+
+def remove_free_item(doc):
+ for d in doc.items:
+ if d.is_free_item:
+ doc.remove(d)
def get_applied_pricing_rules(pricing_rules):
if pricing_rules:
@@ -492,7 +501,7 @@
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item
- if pricing_rule.same_item:
+ if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
free_item = item_details.item_code or args.item_code
if not free_item:
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 31356c6..e08a0e5 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -21,7 +21,7 @@
item.no_of_months = 12
item.save()
- si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
+ si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 1d41d0f..7830cfd 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -15,6 +15,16 @@
return (doc.qty<=doc.received_qty) ? "green" : "orange";
});
}
+
+ this.frm.set_query("unrealized_profit_loss_account", function() {
+ return {
+ filters: {
+ company: doc.company,
+ is_group: 0,
+ root_type: "Liability",
+ }
+ };
+ });
},
onload: function() {
this._super();
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 8925b87..c64ffd8 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -126,6 +126,7 @@
"write_off_cost_center",
"advances_section",
"allocate_advances_automatically",
+ "adjust_advance_taxes",
"get_advances",
"advances",
"payment_schedule_section",
@@ -151,9 +152,11 @@
"is_opening",
"against_expense_account",
"column_break_63",
+ "unrealized_profit_loss_account",
"status",
"inter_company_invoice_reference",
"is_internal_supplier",
+ "represents_company",
"remarks",
"subscription_section",
"from_date",
@@ -1222,7 +1225,7 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
- "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
+ "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1
},
{
@@ -1329,12 +1332,37 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
+ },
+ {
+ "default": "0",
+ "description": "Taxes paid while advance payment will be adjusted against this invoice",
+ "fieldname": "adjust_advance_taxes",
+ "fieldtype": "Check",
+ "label": "Adjust Advance Taxes"
+ },
+ {
+ "depends_on": "eval:doc.is_internal_supplier",
+ "description": "Unrealized Profit / Loss account for intra-company transfers",
+ "fieldname": "unrealized_profit_loss_account",
+ "fieldtype": "Link",
+ "label": "Unrealized Profit / Loss Account",
+ "options": "Account"
+ },
+ {
+ "depends_on": "eval:doc.is_internal_supplier",
+ "description": "Company which internal supplier represents",
+ "fetch_from": "supplier.represents_company",
+ "fieldname": "represents_company",
+ "fieldtype": "Link",
+ "label": "Represents Company",
+ "options": "Company"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
- "modified": "2020-09-21 12:22:09.164068",
+ "links": [],
+ "modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1396,4 +1424,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 91c4dfb..d94d261 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -147,6 +147,11 @@
throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self):
+ if not self.credit_to:
+ self.credit_to = get_party_account("Supplier", self.supplier, self.company)
+ if not self.credit_to:
+ self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
+
account = frappe.db.get_value("Account", self.credit_to,
["account_type", "report_type", "account_currency"], as_dict=True)
@@ -201,8 +206,8 @@
["Purchase Receipt", "purchase_receipt", "pr_detail"]
])
- def validate_warehouse(self):
- if self.update_stock:
+ def validate_warehouse(self, for_validate=True):
+ if self.update_stock and for_validate:
for d in self.get('items'):
if not d.warehouse:
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@@ -228,7 +233,7 @@
if self.update_stock:
self.validate_item_code()
- self.validate_warehouse()
+ self.validate_warehouse(for_validate)
if auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@@ -444,6 +449,7 @@
self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
+ self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self)
@@ -452,7 +458,6 @@
self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries)
-
return gl_entries
def check_asset_cwip_enabled(self):
@@ -469,31 +474,30 @@
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
- if grand_total:
- # Didnot use base_grand_total to book rounding loss gle
- grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
- self.precision("grand_total"))
- gl_entries.append(
- self.get_gl_dict({
- "account": self.credit_to,
- "party_type": "Supplier",
- "party": self.supplier,
- "due_date": self.due_date,
- "against": self.against_expense_account,
- "credit": grand_total_in_company_currency,
- "credit_in_account_currency": grand_total_in_company_currency \
- if self.party_account_currency==self.company_currency else grand_total,
- "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
- "against_voucher_type": self.doctype,
- "project": self.project,
- "cost_center": self.cost_center
- }, self.party_account_currency, item=self)
- )
+ if grand_total and not self.is_internal_transfer():
+ # Didnot use base_grand_total to book rounding loss gle
+ grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
+ self.precision("grand_total"))
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.credit_to,
+ "party_type": "Supplier",
+ "party": self.supplier,
+ "due_date": self.due_date,
+ "against": self.against_expense_account,
+ "credit": grand_total_in_company_currency,
+ "credit_in_account_currency": grand_total_in_company_currency \
+ if self.party_account_currency==self.company_currency else grand_total,
+ "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
+ "against_voucher_type": self.doctype,
+ "project": self.project,
+ "cost_center": self.cost_center
+ }, self.party_account_currency, item=self)
+ )
def make_item_gl_entries(self, gl_entries):
# item gl entries
stock_items = self.get_stock_items()
- expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company)
@@ -521,7 +525,6 @@
item, voucher_wise_stock_value, account_currency)
if item.from_warehouse:
-
gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"],
@@ -541,16 +544,18 @@
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
- gl_entries.append(
- self.get_gl_dict({
- "account": item.expense_account,
- "against": self.supplier,
- "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "cost_center": item.cost_center,
- "project": item.project
- }, account_currency, item=item)
- )
+ # Do not book expense for transfer within same company transfer
+ if not self.is_internal_transfer():
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": item.expense_account,
+ "against": self.supplier,
+ "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "cost_center": item.cost_center,
+ "project": item.project
+ }, account_currency, item=item)
+ )
else:
gl_entries.append(
@@ -827,7 +832,8 @@
}, account_currency, item=tax)
)
# accumulate valuation tax
- if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
+ if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
+ and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0)
@@ -871,8 +877,19 @@
"against": self.supplier,
"credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock"
- }, item=tax)
- )
+ }, item=tax))
+
+ def make_internal_transfer_gl_entries(self, gl_entries):
+ if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
+ account_currency = get_account_currency(self.unrealized_profit_loss_account)
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.unrealized_profit_loss_account,
+ "against": self.supplier,
+ "credit": flt(self.total_taxes_and_charges),
+ "credit_in_account_currency": flt(self.base_total_taxes_and_charges),
+ "cost_center": self.cost_center
+ }, account_currency, item=self))
def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries
@@ -1032,7 +1049,9 @@
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
for pr in set(updated_pr):
- frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
+ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
+ pr_doc = frappe.get_doc("Purchase Receipt", pr)
+ update_billing_percentage(pr_doc, update_modified=update_modified)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None
@@ -1088,7 +1107,9 @@
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
- if outstanding_amount > 0 and due_date < nowdate:
+ if self.is_internal_transfer():
+ self.status = 'Internal Transfer'
+ elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
index 661559a..699a49d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
@@ -4,23 +4,25 @@
// render
frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
- "currency", "is_return", "release_date", "on_hold"],
+ "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) {
- if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
+ if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "gray", "outstanding_amount,<=,0"];
- } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
+ } else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "gray"];
- } else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
+ } else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "gray"];
- } else if(frappe.datetime.get_diff(doc.due_date) < 0) {
+ } else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
}
- } else if(cint(doc.is_return)) {
+ } else if (cint(doc.is_return)) {
return [__("Return"), "gray", "is_return,=,Yes"];
- } else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
+ } else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
+ return [__("Internal Transfer"), "gray", "outstanding_amount,=,0"];
+ } else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"];
}
}
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 2e5a714..f2499d2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -998,7 +998,7 @@
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"conversion_factor": 1.0,
"serial_no": args.serial_no,
- "stock_uom": "_Test UOM",
+ "stock_uom": args.uom or "_Test UOM",
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"project": args.project,
"rejected_warehouse": args.rejected_warehouse or "",
@@ -1040,7 +1040,8 @@
pi.is_return = args.is_return
pi.credit_to = args.return_against or "Creditors - _TC"
pi.is_subcontracted = args.is_subcontracted or "No"
- pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
+ if args.supplier_warehouse:
+ pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
diff --git a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
index 23dc6c4..f1ed8ef 100644
--- a/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
+++ b/erpnext/accounts/doctype/salary_component_account/salary_component_account.json
@@ -1,92 +1,38 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-07-27 17:24:24.956896",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-07-27 17:24:24.956896",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "account"
+ ],
"fields": [
{
- "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_list_view": 1,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
- "fieldname": "default_account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Default Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "description": "Default Bank / Cash account will be automatically updated in Salary Journal Entry when this mode is selected.",
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-09-02 07:49:06.567389",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Salary Component Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-10-18 17:57:57.110257",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Salary Component Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 502e65e..9cbfb0f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -580,6 +580,16 @@
};
});
+ frm.set_query("unrealized_profit_loss_account", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ root_type: "Liability",
+ }
+ };
+ });
+
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return',
@@ -664,12 +674,12 @@
};
},
// 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)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index ae40153..6799fb9 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -157,6 +157,7 @@
"more_information",
"inter_company_invoice_reference",
"is_internal_customer",
+ "represents_company",
"customer_group",
"campaign",
"is_discounted",
@@ -170,6 +171,7 @@
"c_form_applicable",
"c_form_no",
"column_break8",
+ "unrealized_profit_loss_account",
"remarks",
"sales_team_section_break",
"sales_partner",
@@ -1654,7 +1656,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
- "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled",
+ "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1,
"read_only": 1
},
@@ -1949,13 +1951,31 @@
"fieldtype": "Data",
"label": "Company Tax ID",
"read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.is_internal_customer",
+ "description": "Unrealized Profit / Loss account for intra-company transfers",
+ "fieldname": "unrealized_profit_loss_account",
+ "fieldtype": "Link",
+ "label": "Unrealized Profit / Loss Account",
+ "options": "Account"
+ },
+ {
+ "depends_on": "eval:doc.is_internal_customer",
+ "description": "Company which internal customer represents",
+ "fetch_from": "customer.represents_company",
+ "fieldname": "represents_company",
+ "fieldtype": "Link",
+ "label": "Represents Company",
+ "options": "Company",
+ "read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-09 15:59:57.544736",
+ "modified": "2020-12-11 12:48:31.769958",
"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 08da943..8b09130 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -405,6 +405,8 @@
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
+ if not pos_profile:
+ frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name')
pos = {}
@@ -472,6 +474,11 @@
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
def validate_debit_to_acc(self):
+ if not self.debit_to:
+ self.debit_to = get_party_account("Customer", self.customer, self.company)
+ if not self.debit_to:
+ self.raise_missing_debit_credit_account_error("Customer", self.customer)
+
account = frappe.get_cached_value("Account", self.debit_to,
["account_type", "report_type", "account_currency"], as_dict=True)
@@ -751,6 +758,7 @@
self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries)
+ self.make_internal_transfer_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries)
@@ -770,7 +778,7 @@
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
- if grand_total:
+ if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total"))
@@ -809,6 +817,18 @@
}, account_currency, item=tax)
)
+ def make_internal_transfer_gl_entries(self, gl_entries):
+ if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
+ account_currency = get_account_currency(self.unrealized_profit_loss_account)
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": self.unrealized_profit_loss_account,
+ "against": self.customer,
+ "debit": flt(self.total_taxes_and_charges),
+ "debit_in_account_currency": flt(self.base_total_taxes_and_charges),
+ "cost_center": self.cost_center
+ }, account_currency, item=self))
+
def make_item_gl_entries(self, gl_entries):
# income account gl entries
for item in self.get("items"):
@@ -831,22 +851,24 @@
asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None)
else:
- income_account = (item.income_account
- if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
+ # Do not book income for transfer within same company
+ if not self.is_internal_transfer():
+ income_account = (item.income_account
+ if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
- account_currency = get_account_currency(income_account)
- gl_entries.append(
- self.get_gl_dict({
- "account": income_account,
- "against": self.customer,
- "credit": flt(item.base_net_amount, item.precision("base_net_amount")),
- "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
- if account_currency==self.company_currency
- else flt(item.net_amount, item.precision("net_amount"))),
- "cost_center": item.cost_center,
- "project": item.project or self.project
- }, account_currency, item=item)
- )
+ account_currency = get_account_currency(income_account)
+ gl_entries.append(
+ self.get_gl_dict({
+ "account": income_account,
+ "against": self.customer,
+ "credit": flt(item.base_net_amount, item.precision("base_net_amount")),
+ "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
+ if account_currency==self.company_currency
+ else flt(item.net_amount, item.precision("net_amount"))),
+ "cost_center": item.cost_center,
+ "project": item.project or self.project
+ }, account_currency, item=item)
+ )
# expense account gl entries
if cint(self.update_stock) and \
@@ -1258,7 +1280,9 @@
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
- if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
+ if self.is_internal_transfer():
+ self.status = 'Internal Transfer'
+ elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
@@ -1523,9 +1547,13 @@
if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
+ source_document_warehouse_field = 'target_warehouse'
+ target_document_warehouse_field = 'from_warehouse'
else:
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
+ source_document_warehouse_field = 'from_warehouse'
+ target_document_warehouse_field = 'target_warehouse'
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
@@ -1552,6 +1580,26 @@
if currency:
target_doc.currency = currency
+ item_field_map = {
+ "doctype": target_doctype + " Item",
+ "field_no_map": [
+ "income_account",
+ "expense_account",
+ "cost_center",
+ "warehouse"
+ ]
+ }
+
+ if source_doc.get('update_stock'):
+ item_field_map.update({
+ 'field_map': {
+ source_document_warehouse_field: target_document_warehouse_field,
+ 'batch_no': 'batch_no',
+ 'serial_no': 'serial_no'
+ }
+ })
+
+
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": target_doctype,
@@ -1560,15 +1608,7 @@
"taxes_and_charges"
]
},
- doctype +" Item": {
- "doctype": target_doctype + " Item",
- "field_no_map": [
- "income_account",
- "expense_account",
- "cost_center",
- "warehouse"
- ]
- }
+ doctype +" Item": item_field_map
}, target_doc, set_missing_values)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
index 5ac86d6f..1a01cb5 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
@@ -14,8 +14,8 @@
"Credit Note Issued": "gray",
"Unpaid and Discounted": "orange",
"Overdue and Discounted": "red",
- "Overdue": "red"
-
+ "Overdue": "red",
+ "Internal Transfer": "darkgrey"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
},
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 9660c95..22a4f33 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -690,7 +690,8 @@
self.assertTrue(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
- make_pos_profile()
+ make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
+ expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@@ -746,7 +747,8 @@
self.assertEqual(pos_return.get('payments')[0].amount, -1000)
def test_pos_change_amount(self):
- make_pos_profile()
+ make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
+ expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
@@ -1571,7 +1573,7 @@
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
-
+
def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project
@@ -1605,9 +1607,9 @@
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1)
-
+
self.assertTrue(gl_entries)
-
+
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
@@ -1779,6 +1781,60 @@
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
+ def test_internal_transfer_gl_entry(self):
+ ## Create internal transfer account
+ account = create_account(account_name="Unrealized Profit",
+ parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
+
+ frappe.db.set_value('Company', '_Test Company with perpetual inventory',
+ 'unrealized_profit_loss_account', account)
+
+ customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
+ "_Test Company with perpetual inventory")
+
+ create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
+ "_Test Company with perpetual inventory")
+
+ si = create_sales_invoice(
+ company = "_Test Company with perpetual inventory",
+ customer = customer,
+ debit_to = "Debtors - TCP1",
+ warehouse = "Stores - TCP1",
+ income_account = "Sales - TCP1",
+ expense_account = "Cost of Goods Sold - TCP1",
+ cost_center = "Main - TCP1",
+ currency = "INR",
+ do_not_save = 1
+ )
+
+ si.selling_price_list = "_Test Price List Rest of the World"
+ si.update_stock = 1
+ si.items[0].target_warehouse = 'Work In Progress - TCP1'
+ add_taxes(si)
+ si.save()
+ si.submit()
+
+ target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+ target_doc.company = '_Test Company with perpetual inventory'
+ target_doc.items[0].warehouse = 'Finished Goods - TCP1'
+ add_taxes(target_doc)
+ target_doc.save()
+ target_doc.submit()
+
+ si_gl_entries = [
+ ["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
+ ["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
+ ]
+
+ check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
+
+ pi_gl_entries = [
+ ["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
+ ["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
+ ]
+
+ check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
+
def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({
@@ -2037,4 +2093,57 @@
"parentfield": "taxes",
"rate": 2,
"row_id": 1
- }]
\ No newline at end of file
+ }]
+
+def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
+ if not frappe.db.exists("Customer", customer_name):
+ customer = frappe.get_doc({
+ "customer_group": "_Test Customer Group",
+ "customer_name": customer_name,
+ "customer_type": "Individual",
+ "doctype": "Customer",
+ "territory": "_Test Territory",
+ "is_internal_customer": 1,
+ "represents_company": represents_company
+ })
+
+ customer.append("companies", {
+ "company": allowed_to_interact_with
+ })
+
+ customer.insert()
+ customer_name = customer.name
+ else:
+ customer_name = frappe.db.get_value("Customer", customer_name)
+
+ return customer_name
+
+def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
+ if not frappe.db.exists("Supplier", supplier_name):
+ supplier = frappe.get_doc({
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": supplier_name,
+ "doctype": "Supplier",
+ "is_internal_supplier": 1,
+ "represents_company": represents_company
+ })
+
+ supplier.append("companies", {
+ "company": allowed_to_interact_with
+ })
+
+ supplier.insert()
+ supplier_name = supplier.name
+ else:
+ supplier_name = frappe.db.exists("Supplier", supplier_name)
+
+ return supplier_name
+
+def add_taxes(doc):
+ doc.append('taxes', {
+ 'account_head': '_Test Account Excise Duty - TCP1',
+ "charge_type": "On Net Total",
+ "cost_center": "Main - TCP1",
+ "description": "Excise Duty",
+ "rate": 12
+ })
\ No newline at end of file
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 8b5e68b..32ad4cb 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -140,9 +140,9 @@
else:
tds_amount = _get_tds(net_total, tax_details.rate)
else:
- supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
- fields = ['sum(net_amount)'],
- filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1)
+ supplier_credit_amount = frappe.get_all('Purchase Invoice',
+ fields = ['sum(net_total)'],
+ filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
supplier_credit_amount = (supplier_credit_amount[0][0]
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
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 b146899..ef77674 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
@@ -7,6 +7,7 @@
import unittest
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"]
@@ -101,6 +102,32 @@
for d in invoices:
d.cancel()
+ def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
+ invoices = []
+ doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
+ tax_withholding_category="Single Threshold TDS")
+ supplier = doc.name
+
+ pi = create_purchase_invoice(supplier=supplier)
+ pi.submit()
+ invoices.append(pi)
+
+ # TDS not applied
+ pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
+ pi.submit()
+ invoices.append(pi)
+
+ pi = create_purchase_invoice(supplier=supplier)
+ pi.submit()
+ invoices.append(pi)
+
+ self.assertEqual(pi.taxes_and_charges_deducted, 2000)
+ self.assertEqual(pi.grand_total, 8000)
+
+ # delete invoices to avoid clashing
+ for d in invoices:
+ d.cancel()
+
def create_purchase_invoice(**args):
# return sales invoice doc object
item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
@@ -109,7 +136,7 @@
pi = frappe.get_doc({
"doctype": "Purchase Invoice",
"posting_date": today(),
- "apply_tds": 1,
+ "apply_tds": 0 if args.do_not_apply_tds else 1,
"supplier": args.supplier,
"company": '_Test Company',
"taxes_and_charges": "",
diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
index 9703527..6ae81d7 100644
--- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
+++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.js
@@ -156,7 +156,7 @@
setup_transactions_dom() {
const me = this;
- me.parent.$main_section.append(`<div class="transactions-table"></div>`)
+ me.parent.$main_section.append('<div class="transactions-table"></div>');
}
create_datatable() {
@@ -167,9 +167,7 @@
})
}
catch(err) {
- let msg = __(`Your file could not be processed by ERPNext.
- <br>It should be a standard CSV or XLSX file.
- <br>The headers should be in the first row.`)
+ 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)
}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
index bb0d0a1..79a6aab 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
@@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
- var range1 = report.columns[11].label;
- var range2 = report.columns[12].label;
- var range3 = report.columns[13].label;
- var range4 = report.columns[14].label;
- var range5 = report.columns[15].label;
+ var start = filters.based_on_payment_terms ? 13 : 11;
+ var range1 = report.columns[start].label;
+ var range2 = report.columns[start+1].label;
+ var range3 = report.columns[start+2].label;
+ var range4 = report.columns[start+3].label;
+ var range5 = report.columns[start+4].label;
+ var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
@@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
+ <th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
- <td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
- <td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
- <td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
- <td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
- <td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
+ <td class="text-right">
+ {%= format_number(balance_row["age"], null, 2) %}
+ </td>
+ <td class="text-right">
+ {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
+ </td>
+ <td class="text-right">
+ {%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
+ </td>
+ <td class="text-right">
+ {%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
+ </td>
+ <td class="text-right">
+ {%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
+ </td>
+ <td class="text-right">
+ {%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
+ </td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
- </td>
+ </td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
@@ -91,6 +107,7 @@
<td></td>
<td></td>
<td></td>
+ <td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
@@ -101,6 +118,7 @@
<th></th>
<th></th>
<th></th>
+ <th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
@@ -218,15 +236,15 @@
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
- {%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
+ {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
- {%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
+ {%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
- {%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
+ {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
@@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
@@ -256,10 +274,10 @@
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
- <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
- <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
+ <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 3ffb3ac..515fd99 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -14,11 +14,93 @@
def get_column():
return [
- _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
- _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
- _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
- _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+ {
+ "label": _("Delivery Note"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Delivery Note",
+ "width": 160
+ },
+ {
+ "label": _("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 120
+ },
+ {
+ "label": _("Customer Name"),
+ "fieldname": "customer_name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 100,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Billed Amount"),
+ "fieldname": "billed_amount",
+ "fieldtype": "Currency",
+ "width": 100,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Returned Amount"),
+ "fieldname": "returned_amount",
+ "fieldtype": "Currency",
+ "width": 120,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Pending Amount"),
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "width": 120,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 120
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120
+ }
]
def get_args():
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index 3445df7..a36e7f8 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -8,6 +8,7 @@
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions)
+from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None):
return _execute(filters)
@@ -22,7 +23,7 @@
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
- doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
+ doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@@ -34,10 +35,14 @@
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice')
+ item_details = get_item_details()
+
for d in item_list:
if not d.stock_qty:
continue
+ item_record = item_details.get(d.item_code)
+
purchase_receipt = None
if d.purchase_receipt:
purchase_receipt = d.purchase_receipt
@@ -48,8 +53,8 @@
row = {
'item_code': d.item_code,
- 'item_name': d.item_name,
- 'item_group': d.item_group,
+ 'item_name': item_record.item_name,
+ 'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
@@ -81,10 +86,10 @@
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
- frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
- frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
+ frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
+ frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
- total_tax += flt(item_tax.get("tax_amount"))
+ total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@@ -309,8 +314,8 @@
select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
- `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`,
- `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description,
+ `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
+ `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
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 a05dcd7..f54ceb0 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
@@ -8,6 +8,7 @@
from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
+from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None):
return _execute(filters)
@@ -16,7 +17,7 @@
if not filters: filters = {}
columns = get_columns(additional_table_columns, filters)
- company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
+ company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns)
if item_list:
@@ -33,7 +34,13 @@
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice')
+ customer_details = get_customer_details()
+ item_details = get_item_details()
+
for d in item_list:
+ customer_record = customer_details.get(d.customer)
+ item_record = item_details.get(d.item_code)
+
delivery_note = None
if d.delivery_note:
delivery_note = d.delivery_note
@@ -45,14 +52,14 @@
row = {
'item_code': d.item_code,
- 'item_name': d.item_name,
- 'item_group': d.item_group,
+ 'item_name': item_record.item_name,
+ 'item_group': item_record.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.customer,
- 'customer_name': d.customer_name,
- 'customer_group': d.customer_group,
+ 'customer_name': customer_record.customer_name,
+ 'customer_group': customer_record.customer_group,
}
if additional_query_columns:
@@ -90,10 +97,10 @@
for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({
- frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
- frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
+ frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
+ frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
})
- total_tax += flt(item_tax.get("tax_amount"))
+ total_tax += flt(item_tax.get('tax_amount'))
row.update({
'total_tax': total_tax,
@@ -226,7 +233,7 @@
if filters.get('group_by') != 'Territory':
columns.extend([
{
- 'label': _("Territory"),
+ 'label': _('Territory'),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
@@ -374,13 +381,12 @@
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`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`.item_name,
- `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `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, `tabSales Invoice Item`.base_net_rate,
- `tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name,
- `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
+ `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
+ `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,
+ `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
+ `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@@ -417,14 +423,14 @@
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
def get_tax_accounts(item_list, columns, company_currency,
- doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
+ doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
import json
item_row_map = {}
tax_columns = []
invoice_item_row = {}
itemised_tax = {}
- tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"),
+ tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2
for d in item_list:
@@ -469,8 +475,8 @@
tax_rate = tax_data
tax_amount = 0
- if charge_type == "Actual" and not tax_rate:
- tax_rate = "NA"
+ if charge_type == 'Actual' and not tax_rate:
+ tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])])
@@ -484,17 +490,17 @@
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
- "tax_rate": tax_rate,
- "tax_amount": tax_value
+ 'tax_rate': tax_rate,
+ 'tax_amount': tax_value
})
except ValueError:
continue
- elif charge_type == "Actual" and tax_amount:
+ elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
- "tax_rate": "NA",
- "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total,
+ 'tax_rate': 'NA',
+ 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision)
})
@@ -563,7 +569,7 @@
})
total_row_map.setdefault('total_row', {
- subtotal_display_field: "Total",
+ subtotal_display_field: 'Total',
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index a9e25bc..2e18ce1 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -17,18 +17,26 @@
return frappe.db.sql("""
Select
- `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
- {project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
+ `{parent_tab}`.name, `{parent_tab}`.{date_field},
+ `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
+ `{child_tab}`.item_code,
+ `{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
- (`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
- `{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
+ (`{child_tab}`.base_amount -
+ (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
+ `{child_tab}`.item_name, `{child_tab}`.description,
+ {project_field}, `{parent_tab}`.company
from
`{parent_tab}`, `{child_tab}`
where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed')
- and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
- ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
+ and `{child_tab}`.amount > 0
+ and (`{child_tab}`.base_amount -
+ round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index 5e8d773..e9e9c9c 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -14,11 +14,93 @@
def get_column():
return [
- _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
- _("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
- _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
- _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+ {
+ "label": _("Purchase Receipt"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Purchase Receipt",
+ "width": 160
+ },
+ {
+ "label": _("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Supplier"),
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 120
+ },
+ {
+ "label": _("Supplier Name"),
+ "fieldname": "supplier_name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 100,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Billed Amount"),
+ "fieldname": "billed_amount",
+ "fieldtype": "Currency",
+ "width": 100,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Returned Amount"),
+ "fieldname": "returned_amount",
+ "fieldtype": "Currency",
+ "width": 120,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Pending Amount"),
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "width": 120,
+ "options": "Company:company:default_currency"
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 120
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120
+ }
]
def get_args():
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 53677cd..550aaef 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -78,7 +78,10 @@
else:
return ((fy.name, fy.year_start_date, fy.year_end_date),)
- error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date))
+ error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
+ if company:
+ error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
+
if verbose==1: frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
new file mode 100644
index 0000000..8d24ca8
--- /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": "2020-12-01 13: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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/agriculture/desk_page/agriculture/agriculture.json b/erpnext/agriculture/desk_page/agriculture/agriculture.json
deleted file mode 100644
index 094e165..0000000
--- a/erpnext/agriculture/desk_page/agriculture/agriculture.json
+++ /dev/null
@@ -1,41 +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,
- "hide_custom": 0,
- "icon": "agriculture",
- "idx": 0,
- "is_standard": 1,
- "label": "Agriculture",
- "modified": "2020-06-30 18:35:25.350213",
- "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 0000000..2cc2524
--- /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 515fc22..0000000
--- a/erpnext/assets/desk_page/assets/assets.json
+++ /dev/null
@@ -1,67 +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,
- "icon": "assets",
- "idx": 0,
- "is_standard": 1,
- "label": "Assets",
- "modified": "2020-06-30 18:36:11.169586",
- "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.js b/erpnext/assets/doctype/asset/asset.js
index 7ad164a..b2318a2 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -373,8 +373,8 @@
doctype_field = frappe.scrub(doctype)
frm.set_value(doctype_field, '');
frappe.msgprint({
- title: __(`Invalid ${doctype}`),
- message: __(`The selected ${doctype} doesn't contains selected Asset Item.`),
+ title: __('Invalid {0}', [__(doctype)]),
+ message: __('The selected {0} does not contain the selected Asset Item.', [__(doctype)]),
indicator: 'red'
});
}
@@ -436,7 +436,7 @@
depreciation_start_date: function(frm, cdt, cdn) {
const book = locals[cdt][cdn];
if (frm.doc.available_for_use_date && book.depreciation_start_date == frm.doc.available_for_use_date) {
- frappe.msgprint(__(`Depreciation Posting Date should not be equal to Available for Use Date.`));
+ frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date."));
book.depreciation_start_date = "";
frm.refresh_field("finance_books");
}
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 30abc66..1793dad 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -471,7 +471,7 @@
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 +503,10 @@
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 +659,7 @@
frappe.db.commit()
- frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
+ frappe.msgprint(_("Asset Movement record {0} created").format("<a href='/app/Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
@frappe.whitelist()
def get_item_details(item_code, asset_category):
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index fd702c7..74ca62f 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -13,8 +13,8 @@
class AssetValueAdjustment(Document):
def validate(self):
self.validate_date()
- self.set_difference_amount()
self.set_current_asset_value()
+ self.set_difference_amount()
def on_submit(self):
self.make_depreciation_entry()
@@ -25,7 +25,7 @@
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value)
-
+
def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date):
@@ -53,6 +53,7 @@
je.posting_date = self.date
je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
+ je.finance_book = self.finance_book
credit_entry = {
"account": accumulated_depreciation_account,
@@ -78,7 +79,7 @@
debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
-
+
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index af08a2a..d1457b9 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -75,24 +75,23 @@
for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name))
- if asset_value:
- row = {
- "asset_id": asset.asset_id,
- "asset_name": asset.asset_name,
- "status": asset.status,
- "department": asset.department,
- "cost_center": asset.cost_center,
- "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
- "gross_purchase_amount": asset.gross_purchase_amount,
- "opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
- "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
- "available_for_use_date": asset.available_for_use_date,
- "location": asset.location,
- "asset_category": asset.asset_category,
- "purchase_date": asset.purchase_date,
- "asset_value": asset_value
- }
- data.append(row)
+ row = {
+ "asset_id": asset.asset_id,
+ "asset_name": asset.asset_name,
+ "status": asset.status,
+ "department": asset.department,
+ "cost_center": asset.cost_center,
+ "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
+ "gross_purchase_amount": asset.gross_purchase_amount,
+ "opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
+ "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
+ "available_for_use_date": asset.available_for_use_date,
+ "location": asset.location,
+ "asset_category": asset.asset_category,
+ "purchase_date": asset.purchase_date,
+ "asset_value": asset_value
+ }
+ data.append(row)
return data
diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json
new file mode 100644
index 0000000..c401581
--- /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 16df8df..0000000
--- a/erpnext/buying/desk_page/buying/buying.json
+++ /dev/null
@@ -1,114 +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,
- "icon": "buying",
- "idx": 0,
- "is_standard": 1,
- "label": "Buying",
- "modified": "2020-10-21 12:29:02.772723",
- "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/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 4b865a9..75da71c 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@@ -167,6 +168,7 @@
"bold": 1,
"fieldname": "supplier",
"fieldtype": "Link",
+ "in_global_search": 1,
"in_standard_filter": 1,
"label": "Supplier",
"oldfieldname": "supplier",
@@ -1105,7 +1107,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-30 11:39:37.388249",
+ "modified": "2020-12-03 16:46:44.229351",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index c427242..e537771 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -290,11 +290,17 @@
dialog.show();
}, __("Get Items From"));
+ // Link Material Requests
+ this.frm.add_custom_button(__('Link to Material Requests'),
+ function() {
+ erpnext.buying.link_to_mrs(me.frm);
+ }, __("Tools"));
+
// Get Suppliers
this.frm.add_custom_button(__('Get Suppliers'),
function() {
me.get_suppliers_button(me.frm);
- });
+ }, __("Tools"));
}
},
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 3af6cf8..4ce4100 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -18,7 +18,6 @@
"suppliers",
"items_section",
"items",
- "link_to_mrs",
"supplier_response_section",
"salutation",
"subject",
@@ -118,13 +117,6 @@
"reqd": 1
},
{
- "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
- "fieldname": "link_to_mrs",
- "fieldtype": "Button",
- "label": "Link to Material Requests"
- },
- {
- "depends_on": "eval:!doc.__islocal",
"fieldname": "supplier_response_section",
"fieldtype": "Section Break",
"label": "Email Details"
@@ -260,7 +252,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-04 22:04:29.017134",
+ "modified": "2020-11-05 22:04:29.017134",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index df143ee..0ee9d18 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -49,6 +49,12 @@
msgprint(_("Series is mandatory"), raise_exception=1)
validate_party_accounts(self)
+ self.validate_internal_supplier()
+
+ def validate_internal_supplier(self):
+ if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
+ frappe.throw(_("Internal Supplier for company {0} already exists").format(
+ frappe.bold(self.represents_company)))
def on_trash(self):
delete_contact_and_address('Supplier', self.name)
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index a377ec9..f9c8d35 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -120,3 +120,20 @@
# Rollback
address.delete()
+
+def create_supplier(**args):
+ args = frappe._dict(args)
+
+ try:
+ doc = frappe.get_doc({
+ "doctype": "Supplier",
+ "supplier_name": args.supplier_name,
+ "supplier_group": args.supplier_group or "Services",
+ "supplier_type": args.supplier_type or "Company",
+ "tax_withholding_category": args.tax_withholding_category
+ }).insert()
+
+ return doc
+
+ except frappe.DuplicateEntryError:
+ return frappe.get_doc("Supplier", args.supplier_name)
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index a7cab50..a3b2085 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -50,6 +50,12 @@
})
}, __("Get Items From"));
+ // Link Material Requests
+ this.frm.add_custom_button(__('Link to Material Requests'),
+ function() {
+ erpnext.buying.link_to_mrs(me.frm);
+ }, __("Tools"));
+
this.frm.add_custom_button(__("Request for Quotation"),
function() {
if (!me.frm.doc.supplier) {
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 9a092ca..40fbe2c 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:45",
@@ -34,7 +35,6 @@
"ignore_pricing_rule",
"items_section",
"items",
- "link_to_mrs",
"pricing_rule_details",
"pricing_rules",
"section_break_22",
@@ -322,12 +322,6 @@
"reqd": 1
},
{
- "depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
- "fieldname": "link_to_mrs",
- "fieldtype": "Button",
- "label": "Link to material requests"
- },
- {
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
"label": "Pricing Rules"
@@ -805,9 +799,10 @@
],
"icon": "fa fa-shopping-cart",
"idx": 29,
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-01 20:56:17.932007",
+ "modified": "2020-12-03 15:18:29.073368",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index ae5611f..6a4c02c 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -71,7 +71,7 @@
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,
- "<a href='desk#Form/Request for Quotation/{0}'> Request for Quotation {0} </a>".format(doc.name)))
+ "<a href='desk/app/Form/Request for Quotation/{0}'> Request for Quotation {0} </a>".format(doc.name)))
quote_status = _('Received')
for item in doc.items:
diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json
new file mode 100644
index 0000000..6c9c0f3
--- /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 839c4ad..0000000
--- 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 937d76e..0000000
--- 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 4cf7cf0..0000000
--- 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 b06bb76..0000000
--- 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 09c2a65..0000000
--- 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 ce7c245..0000000
--- 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 85e6006..0000000
--- 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 4efaaa6..1c8ab10 100644
--- a/erpnext/config/education.py
+++ b/erpnext/config/education.py
@@ -173,7 +173,7 @@
{
"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 dc72316..0000000
--- 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 da24d11..0000000
--- 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 922afb4..0000000
--- 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 9855a11..0000000
--- 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 0afdeb5..0000000
--- 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 f8b3257..0000000
--- 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 a84f13a..0000000
--- 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 012f1ca..0000000
--- 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 42ec9d3..0000000
--- 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 47700d1..ab4db96 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -16,13 +16,13 @@
{
"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 @@
},
]
},
-
+
]
diff --git a/erpnext/config/quality_management.py b/erpnext/config/quality_management.py
deleted file mode 100644
index 35acdfa..0000000
--- 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 738be7e..0000000
--- 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 5db4cc2..0000000
--- 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 323683a..0000000
--- 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 dd35f5a..0000000
--- 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 151c4f7..0000000
--- 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 d31b057..0000000
--- 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 614a53c..6237aef 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -24,6 +24,8 @@
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
+
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
class AccountsController(TransactionBase):
@@ -119,6 +121,8 @@
else:
self.validate_deferred_start_and_end_date()
+ self.set_inter_company_account()
+
validate_regional(self)
if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self)
@@ -752,6 +756,21 @@
return self._abbr
+ def raise_missing_debit_credit_account_error(self, party_type, party):
+ """Raise an error if debit to/credit to account does not exist."""
+ db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
+ rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
+
+ link_to_party = frappe.utils.get_link_to_form(party_type, party)
+ link_to_company = frappe.utils.get_link_to_form("Company", self.company)
+
+ message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
+ message += "<br>" + _("Please set one of the following:") + "<br>"
+ message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
+ message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
+
+ frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
+
def validate_party(self):
party_type, party = self.get_party()
validate_party_frozen_disabled(party_type, party)
@@ -932,6 +951,38 @@
else:
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
+ def set_inter_company_account(self):
+ """
+ Set intercompany account for inter warehouse transactions
+ This account will be used in case billing company and internal customer's
+ representation company is same
+ """
+
+ if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
+ unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
+
+ if not unrealized_profit_loss_account:
+ msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
+ frappe.bold(self.company))
+ frappe.throw(msg)
+
+ self.unrealized_profit_loss_account = unrealized_profit_loss_account
+
+ def is_internal_transfer(self):
+ """
+ It will an internal transfer if its an internal customer and representation
+ company is same as billing company
+ """
+ if self.doctype == 'Sales Invoice':
+ internal_party_field = 'is_internal_customer'
+ else:
+ internal_party_field = 'is_internal_supplier'
+
+ if self.get(internal_party_field) and (self.represents_company == self.company):
+ return True
+
+ return False
+
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 070d469..53f8edb 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -32,6 +32,7 @@
self.validate_items()
self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items()
+ self.update_tax_category_for_internal_transfer()
self.validate_warehouse()
self.validate_from_warehouse()
self.set_supplier_address()
@@ -84,13 +85,23 @@
def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
- tax_for_valuation = [d for d in self.get("taxes")
+ msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
+ self.update_tax_category(msg)
+
+ def update_tax_category_for_internal_transfer(self):
+ if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
+ msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
+ self.update_tax_category(msg)
+
+ def update_tax_category(self, msg):
+ tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]]
- if tax_for_valuation:
- for d in tax_for_valuation:
- d.category = 'Total'
- msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
+ if tax_for_valuation:
+ for d in tax_for_valuation:
+ d.category = 'Total'
+
+ msgprint(msg)
def validate_asset_return(self):
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
@@ -487,6 +498,10 @@
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
+ if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
+ # Set Received Qty in Stock UOM
+ d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
+
def validate_purchase_return(self):
for d in self.get("items"):
if self.is_return and flt(d.rejected_qty) != 0:
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index afc5f81..5299b25 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -203,10 +203,37 @@
return items
+def get_returned_qty_map_for_row(row_name, doctype):
+ child_doctype = doctype + " Item"
+ reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
+
+ fields = [
+ "sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
+ "sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
+ ]
+
+ if doctype == "Purchase Receipt":
+ fields += [
+ "sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
+ "sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
+ "sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
+ ]
+
+ data = frappe.db.get_list(doctype,
+ fields = fields,
+ filters = [
+ [doctype, "docstatus", "=", 1],
+ [doctype, "is_return", "=", 1],
+ [child_doctype, reference_field, "=", row_name]
+ ])
+
+ return data[0]
+
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
+
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
@@ -261,20 +288,25 @@
doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
- target_doc.qty = -1* source_doc.qty
+ target_doc.qty = -1 * source_doc.qty
+
if doctype == "Purchase Receipt":
- target_doc.received_qty = -1* source_doc.received_qty
- target_doc.rejected_qty = -1* source_doc.rejected_qty
- target_doc.qty = -1* source_doc.qty
- target_doc.stock_qty = -1 * source_doc.stock_qty
+ returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+ target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
+ target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+ target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
+
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item
target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
- target_doc.received_qty = -1* source_doc.received_qty
- target_doc.rejected_qty = -1* source_doc.rejected_qty
+ target_doc.received_qty = -1 * source_doc.received_qty
+ target_doc.rejected_qty = -1 * source_doc.rejected_qty
target_doc.qty = -1* source_doc.qty
target_doc.stock_qty = -1 * source_doc.stock_qty
target_doc.purchase_order = source_doc.purchase_order
@@ -285,6 +317,10 @@
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note":
+ returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
target_doc.so_detail = source_doc.so_detail
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index dca2ab0..1ee1097 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -32,7 +32,7 @@
self.validate_max_discount()
self.validate_selling_price()
self.set_qty_as_per_stock_uom()
- self.set_po_nos()
+ self.set_po_nos(for_validate=True)
self.set_gross_profit()
set_default_income_account_for_item(self)
self.set_customer_address()
@@ -360,20 +360,28 @@
}))
self.make_sl_entries(sl_entries)
- def set_po_nos(self):
+ def set_po_nos(self, for_validate=False):
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
+ if for_validate and self.po_no:
+ return
self.set_pos_for_sales_invoice()
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
+ if for_validate and self.po_no:
+ return
self.set_pos_for_delivery_note()
def set_pos_for_sales_invoice(self):
po_nos = []
+ if self.po_no:
+ po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'sales_order', po_nos)
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
def set_pos_for_delivery_note(self):
po_nos = []
+ if self.po_no:
+ po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
@@ -406,26 +414,26 @@
return
for d in self.get('items'):
- if self.doctype == "Sales Invoice":
- e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
- f = [d.item_code, d.description, d.sales_order or d.delivery_note]
+ if self.doctype in ["POS Invoice","Sales Invoice"]:
+ stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
+ non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":
- e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
- f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
+ stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
+ non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
elif self.doctype in ["Sales Order", "Quotation"]:
- e = [d.item_code, d.description, d.warehouse, '']
- f = [d.item_code, d.description]
+ stock_items = [d.item_code, d.description, d.warehouse, '']
+ non_stock_items = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
- if e in check_list:
+ if stock_items in check_list:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
- check_list.append(e)
+ check_list.append(stock_items)
else:
- if f in chk_dupl_itm:
+ if non_stock_items in chk_dupl_itm:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
- chk_dupl_itm.append(f)
+ chk_dupl_itm.append(non_stock_items)
def validate_target_warehouse(self):
items = self.get("items") + (self.get("packed_items") or [])
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 9feac78..8c05134 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -58,6 +58,7 @@
"Delivery Note": [
["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+ ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
@@ -65,6 +66,7 @@
"Purchase Receipt": [
["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+ ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
@@ -232,7 +234,7 @@
self._update_children(args, update_modified)
- if "percent_join_field" in args:
+ if "percent_join_field" in args or "percent_join_field_parent" in args:
self._update_percent_field_in_targets(args, update_modified)
def _update_children(self, args, update_modified):
@@ -252,33 +254,43 @@
if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = ""
- args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
+ args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s"
- and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args
+ and (`tab%(second_source_dt)s`.docstatus=1)
+ %(second_source_extra_cond)s), 0) """ % args)[0][0]
if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = ""
- frappe.db.sql("""update `tab%(target_dt)s`
- set %(target_field)s = (
+ args["source_dt_value"] = frappe.db.sql("""
(select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s)
- %(second_source_condition)s
- )
- %(update_modified)s
+ """ % args)[0][0] or 0.0
+
+ if args['second_source_condition']:
+ args["source_dt_value"] += flt(args['second_source_condition'])
+
+ frappe.db.sql("""update `tab%(target_dt)s`
+ set %(target_field)s = %(source_dt_value)s %(update_modified)s
where name='%(detail_id)s'""" % args)
def _update_percent_field_in_targets(self, args, update_modified=True):
"""Update percent field in parent transaction"""
- distinct_transactions = set([d.get(args['percent_join_field'])
- for d in self.get_all_children(args['source_dt'])])
+ if args.get('percent_join_field_parent'):
+ # if reference to target doc where % is to be updated, is
+ # in source doc's parent form, consider percent_join_field_parent
+ args['name'] = self.get(args['percent_join_field_parent'])
+ self._update_percent_field(args, update_modified)
+ else:
+ distinct_transactions = set([d.get(args['percent_join_field'])
+ for d in self.get_all_children(args['source_dt'])])
- for name in distinct_transactions:
- if name:
- args['name'] = name
- self._update_percent_field(args, update_modified)
+ for name in distinct_transactions:
+ if name:
+ args['name'] = name
+ self._update_percent_field(args, update_modified)
def _update_percent_field(self, args, update_modified=True):
"""Update percent field in parent transaction"""
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index f743d70..683d7f7 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -77,7 +77,7 @@
if sle_list:
for sle in sle_list:
if warehouse_account.get(sle.warehouse):
- # from warehouse account/ target warehouse account
+ # from warehouse account
self.check_expense_account(item_row)
@@ -92,9 +92,16 @@
sle = self.update_stock_ledger_entries(sle)
+ # expense account/ target_warehouse / source_warehouse
+ if item_row.get('target_warehouse'):
+ warehouse = item_row.get('target_warehouse')
+ expense_account = warehouse_account[warehouse]["account"]
+ else:
+ expense_account = item_row.expense_account
+
gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse]["account"],
- "against": item_row.expense_account,
+ "against": expense_account,
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
@@ -102,9 +109,8 @@
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
- # expense account
gl_list.append(self.get_gl_dict({
- "account": item_row.expense_account,
+ "account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'),
@@ -229,9 +235,9 @@
def check_expense_account(self, item):
if not item.get("expense_account"):
- frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
- Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
- title=_("Expense Account Missing"))
+ msg = _("Please set an Expense Account in the Items table")
+ frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
+ .format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
else:
is_expense_account = frappe.db.get_value("Account",
@@ -247,7 +253,9 @@
for d in self.items:
if not d.batch_no: continue
- serial_nos = [sr.name for sr in frappe.get_all("Serial No", {'batch_no': d.batch_no})]
+ serial_nos = [sr.name for sr in frappe.get_all("Serial No",
+ {'batch_no': d.batch_no, 'status': 'Inactive'})]
+
if serial_nos:
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
@@ -338,11 +346,15 @@
validate_warehouse_company(w, self.company)
def update_billing_percentage(self, update_modified=True):
+ target_ref_field = "amount"
+ if self.doctype == "Delivery Note":
+ target_ref_field = "amount - (returned_qty * rate)"
+
self._update_percent_field({
"target_dt": self.doctype + " Item",
"target_parent_dt": self.doctype,
"target_parent_field": "per_billed",
- "target_ref_field": "amount",
+ "target_ref_field": target_ref_field,
"target_field": "billed_amt",
"name": self.name,
}, update_modified)
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 81d07c1..8dd2e5b 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -519,6 +519,17 @@
if self.doc.docstatus == 0:
self.calculate_outstanding_amount()
+ def is_internal_invoice(self):
+ """
+ Checks if its an internal transfer invoice
+ and decides if to calculate any out standing amount or not
+ """
+
+ if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
+ return True
+
+ return False
+
def calculate_outstanding_amount(self):
# NOTE:
# write_off_amount is only for POS Invoice
@@ -526,7 +537,8 @@
if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount()
- if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return
+ if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
+ self.is_internal_invoice(): return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount'])
@@ -641,7 +653,8 @@
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
- 'amount': total_amount_to_pay
+ 'amount': total_amount_to_pay,
+ 'default': 1
})
else:
self.doc.is_pos = 0
diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json
deleted file mode 100644
index 5497f3e..0000000
--- a/erpnext/crm/desk_page/crm/crm.json
+++ /dev/null
@@ -1,87 +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,
- "icon": "crm",
- "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": "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/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 63efeb3..2009ebf 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -126,7 +126,7 @@
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
- 'assign_to': existing_assignee
+ 'assign_to': [existing_assignee]
})
return
if self._assign:
@@ -139,7 +139,7 @@
add_assignemnt({
'doctype': self.doctype,
'name': self.name,
- 'assign_to': agent
+ 'assign_to': [agent]
})
break
diff --git a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
index 99b8214..dc3ae8b 100644
--- a/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
+++ b/erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.js
@@ -4,7 +4,7 @@
let from_time = Date.parse('01/01/2019 ' + d.from_time);
let to_time = Date.parse('01/01/2019 ' + d.to_time);
if (from_time > to_time) {
- frappe.throw(__(`In row ${i + 1} of Appointment Booking Slots : "To Time" must be later than "From Time"`));
+ frappe.throw(__('In row {0} of Appointment Booking Slots: "To Time" must be later than "From Time".', [i + 1]));
}
});
}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/contract/contract.js b/erpnext/crm/doctype/contract/contract.js
index ee9e895..9968855 100644
--- a/erpnext/crm/doctype/contract/contract.js
+++ b/erpnext/crm/doctype/contract/contract.js
@@ -1,23 +1,31 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
-cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
-
-// Add fulfilment terms from contract template into contract
frappe.ui.form.on("Contract", {
contract_template: function (frm) {
- // Populate the fulfilment terms table from a contract template, if any
if (frm.doc.contract_template) {
- frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () {
- var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template);
-
- frm.doc.fulfilment_terms = [];
- $.each(tabletransfer.fulfilment_terms, function (index, row) {
- var d = frm.add_child("fulfilment_terms");
- d.requirement = row.requirement;
- frm.refresh_field("fulfilment_terms");
- });
+ frappe.call({
+ method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
+ args: {
+ template_name: frm.doc.contract_template,
+ doc: frm.doc
+ },
+ callback: function(r) {
+ if (r && r.message) {
+ let contract_template = r.message.contract_template;
+ frm.set_value("contract_terms", r.message.contract_terms);
+ frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
+
+ if (frm.doc.requires_fulfilment) {
+ // Populate the fulfilment terms table from a contract template, if any
+ r.message.contract_template.fulfilment_terms.forEach(element => {
+ let d = frm.add_child("fulfilment_terms");
+ d.requirement = element.requirement;
+ });
+ frm.refresh_field("fulfilment_terms");
+ }
+ }
+ }
});
}
}
diff --git a/erpnext/crm/doctype/contract/contract.json b/erpnext/crm/doctype/contract/contract.json
index 0026e4a..de3230f 100755
--- a/erpnext/crm/doctype/contract/contract.json
+++ b/erpnext/crm/doctype/contract/contract.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2018-04-12 06:32:04.582486",
@@ -247,7 +248,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-30 06:56:07.257932",
+ "modified": "2020-12-07 11:15:58.385521",
"modified_by": "Administrator",
"module": "CRM",
"name": "Contract",
diff --git a/erpnext/crm/doctype/contract_template/contract_template.json b/erpnext/crm/doctype/contract_template/contract_template.json
index 5e4582f..7cc5ec1 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.json
+++ b/erpnext/crm/doctype/contract_template/contract_template.json
@@ -11,7 +11,9 @@
"contract_terms",
"sb_fulfilment",
"requires_fulfilment",
- "fulfilment_terms"
+ "fulfilment_terms",
+ "section_break_6",
+ "contract_template_help"
],
"fields": [
{
@@ -41,10 +43,20 @@
"fieldtype": "Table",
"label": "Fulfilment Terms and Conditions",
"options": "Contract Template Fulfilment Terms"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "contract_template_help",
+ "fieldtype": "HTML",
+ "label": "Contract Template Help",
+ "options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
}
],
"links": [],
- "modified": "2020-11-11 17:49:44.879363",
+ "modified": "2020-12-07 10:44:22.587047",
"modified_by": "Administrator",
"module": "CRM",
"name": "Contract Template",
diff --git a/erpnext/crm/doctype/contract_template/contract_template.py b/erpnext/crm/doctype/contract_template/contract_template.py
index 601ee9a..69fd86f 100644
--- a/erpnext/crm/doctype/contract_template/contract_template.py
+++ b/erpnext/crm/doctype/contract_template/contract_template.py
@@ -5,6 +5,27 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe.utils.jinja import validate_template
+from six import string_types
+import json
class ContractTemplate(Document):
- pass
+ def validate(self):
+ if self.contract_terms:
+ validate_template(self.contract_terms)
+
+@frappe.whitelist()
+def get_contract_template(template_name, doc):
+ if isinstance(doc, string_types):
+ doc = json.loads(doc)
+
+ contract_template = frappe.get_doc("Contract Template", template_name)
+ contract_terms = None
+
+ if contract_template.contract_terms:
+ contract_terms = frappe.render_template(contract_template.contract_terms, doc)
+
+ return {
+ 'contract_template': contract_template,
+ 'contract_terms': contract_terms
+ }
\ No newline at end of file
diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json
new file mode 100644
index 0000000..b4fb7d8
--- /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/demo/data/asset.json b/erpnext/demo/data/asset.json
index 23029ca..44db2ae 100644
--- a/erpnext/demo/data/asset.json
+++ b/erpnext/demo/data/asset.json
@@ -4,48 +4,55 @@
"item_code": "Computer",
"gross_purchase_amount": 100000,
"asset_owner": "Company",
- "available_for_use_date": "2017-01-02"
+ "available_for_use_date": "2017-01-02",
+ "location": "Main Location"
},
{
"asset_name": "Macbook Air - 1",
"item_code": "Computer",
"gross_purchase_amount": 60000,
"asset_owner": "Company",
- "available_for_use_date": "2017-10-02"
+ "available_for_use_date": "2017-10-02",
+ "location": "Avg Location"
},
{
"asset_name": "Conferrence Table",
"item_code": "Table",
"gross_purchase_amount": 30000,
"asset_owner": "Company",
- "available_for_use_date": "2018-10-02"
+ "available_for_use_date": "2018-10-02",
+ "location": "Zany Location"
},
{
"asset_name": "Lunch Table",
"item_code": "Table",
"gross_purchase_amount": 20000,
"asset_owner": "Company",
- "available_for_use_date": "2018-06-02"
+ "available_for_use_date": "2018-06-02",
+ "location": "Fletcher Location"
},
{
"asset_name": "ERPNext",
"item_code": "ERP",
"gross_purchase_amount": 100000,
"asset_owner": "Company",
- "available_for_use_date": "2018-09-02"
+ "available_for_use_date": "2018-09-02",
+ "location":"Main Location"
},
{
"asset_name": "Chair 1",
"item_code": "Chair",
"gross_purchase_amount": 10000,
"asset_owner": "Company",
- "available_for_use_date": "2018-07-02"
+ "available_for_use_date": "2018-07-02",
+ "location": "Zany Location"
},
{
"asset_name": "Chair 2",
"item_code": "Chair",
"gross_purchase_amount": 10000,
"asset_owner": "Company",
- "available_for_use_date": "2018-07-02"
+ "available_for_use_date": "2018-07-02",
+ "location": "Avg Location"
}
]
diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json
new file mode 100644
index 0000000..b521aa0
--- /dev/null
+++ b/erpnext/demo/data/location.json
@@ -0,0 +1,22 @@
+[
+ {
+ "location_name": "Main Location",
+ "latitude": 40.0,
+ "longitude": 20.0
+ },
+ {
+ "location_name": "Avg Location",
+ "latitude": 63.0,
+ "longitude": 99.3
+ },
+ {
+ "location_name": "Zany Location",
+ "latitude": 47.5,
+ "longitude": 10.0
+ },
+ {
+ "location_name": "Fletcher Location",
+ "latitude": 100.90,
+ "longitude": 80
+ }
+]
\ No newline at end of file
diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py
index d384636..7d6b501 100644
--- a/erpnext/demo/setup/manufacture.py
+++ b/erpnext/demo/setup/manufacture.py
@@ -9,6 +9,7 @@
from six import iteritems
def setup_data():
+ import_json("Location")
import_json("Asset Category")
setup_item()
setup_workstation()
diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py
index a395c7c..05ee28a 100644
--- a/erpnext/demo/setup/setup_data.py
+++ b/erpnext/demo/setup/setup_data.py
@@ -134,7 +134,7 @@
salary_component = frappe.get_doc('Salary Component', d.name)
salary_component.append('accounts', dict(
company=erpnext.get_default_company(),
- default_account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
+ account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
))
salary_component.save()
diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py
index f95a6b8..d44da7d 100644
--- a/erpnext/demo/user/stock.py
+++ b/erpnext/demo/user/stock.py
@@ -79,7 +79,7 @@
if item.qty:
item.qty = item.qty - round(random.randint(1, item.qty))
try:
- stock_reco.insert(ignore_permissions=True)
+ stock_reco.insert(ignore_permissions=True, ignore_mandatory=True)
stock_reco.submit()
frappe.db.commit()
except OpeningEntryAccountError:
diff --git a/erpnext/education/desk_page/education/education.json b/erpnext/education/desk_page/education/education.json
deleted file mode 100644
index 0d51048..0000000
--- a/erpnext/education/desk_page/education/education.json
+++ /dev/null
@@ -1,155 +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,
- "icon": "education",
- "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 3cd4512..053f0c2 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 @@
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 20503f9..4e2ccaa 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.js
@@ -25,7 +25,7 @@
<thead><tr><th>${__("Course")}</th><th>${__("Date")}</th></tr></thead>
<tbody>
${course_schedules.map(
- c => `<tr><td><a href="#Form/Course Schedule/${c.name}">${c.name}</a></td>
+ c => `<tr><td><a href="/app/course-schedule/${c.name}">${c.name}</a></td>
<td>${c.schedule_date}</td></tr>`
).join('')}
</tbody>
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js
index 75dd446..0b4c2cd 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.js
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js
@@ -43,7 +43,7 @@
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 6fbcd8a..3045db7 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -87,7 +87,7 @@
fees.submit()
fee_list.append(fees.name)
if fee_list:
- fee_list = ["""<a href="#Form/Fees/%s" target="_blank">%s</a>""" % \
+ fee_list = ["""<a href="/app/Form/Fees/%s" target="_blank">%s</a>""" % \
(fee, fee) for fee in fee_list]
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json
new file mode 100644
index 0000000..bf74961
--- /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/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index 8aa7453..f0a05ed 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -149,26 +149,28 @@
si.shopify_order_number = shopify_order.get("name")
si.set_posting_time = 1
si.posting_date = posting_date
+ si.due_date = posting_date
si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
si.flags.ignore_mandatory = True
set_cost_center(si.items, shopify_settings.cost_center)
si.insert(ignore_mandatory=True)
si.submit()
- make_payament_entry_against_sales_invoice(si, shopify_settings)
+ make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
frappe.db.commit()
def set_cost_center(items, cost_center):
for item in items:
item.cost_center = cost_center
-def make_payament_entry_against_sales_invoice(doc, shopify_settings):
+def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
- payemnt_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
- payemnt_entry.flags.ignore_mandatory = True
- payemnt_entry.reference_no = doc.name
- payemnt_entry.reference_date = nowdate()
- payemnt_entry.insert(ignore_permissions=True)
- payemnt_entry.submit()
+ payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
+ payment_entry.flags.ignore_mandatory = True
+ payment_entry.reference_no = doc.name
+ payment_entry.posting_date = posting_date or nowdate()
+ payment_entry.reference_date = posting_date or nowdate()
+ payment_entry.insert(ignore_permissions=True)
+ payment_entry.submit()
def create_delivery_note(shopify_order, shopify_settings, so):
if not cint(shopify_settings.sync_delivery_note):
@@ -258,6 +260,15 @@
"""Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
+ if shipping_charge.get("price"):
+ taxes.append({
+ "charge_type": _("Actual"),
+ "account_head": get_tax_account_head(shipping_charge),
+ "description": shipping_charge["title"],
+ "tax_amount": shipping_charge["price"],
+ "cost_center": shopify_settings.cost_center
+ })
+
for tax in shipping_charge.get("tax_lines"):
taxes.append({
"charge_type": _("Actual"),
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 ea3b129..0000000
--- 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 3bbc36a..0000000
--- 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/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
index fd16d1e..5482b9c 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.msgprint({
message: __("An error has occurred during {0}. Check {1} for more details",
[
- repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
+ repl("<a href='/app/tally-migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
tally_document: frm.docname
}),
- "<a href='#List/Error Log' class='variant-click'>Error Log</a>"
+ "<a href='/app/error-log' class='variant-click'>Error Log</a>"
]
),
title: __("Tally Migration Error"),
diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py
index 24fc3d4..f960998 100644
--- a/erpnext/erpnext_integrations/taxjar_integration.py
+++ b/erpnext/erpnext_integrations/taxjar_integration.py
@@ -1,5 +1,7 @@
import traceback
+import taxjar
+
import frappe
from erpnext import get_default_company
from frappe import _
@@ -29,7 +31,6 @@
def create_transaction(doc, method):
- import taxjar
"""Create an order transaction in TaxJar"""
if not TAXJAR_CREATE_TRANSACTIONS:
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index e278fd7..362f6cf 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -60,4 +60,12 @@
"default_account": payment_gateway_account
}]
})
- mode_of_payment.insert(ignore_permissions=True)
\ No newline at end of file
+ mode_of_payment.insert(ignore_permissions=True)
+
+def get_tracking_url(carrier, tracking_number):
+ # Return the formatted Tracking URL.
+ tracking_url = ''
+ url_reference = frappe.get_value('Parcel Service', carrier, 'url_reference')
+ if url_reference:
+ tracking_url = frappe.render_template(url_reference, {'tracking_number': tracking_number})
+ return tracking_url
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 0000000..4a5e54e
--- /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 0000000..d258d57
--- /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/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
deleted file mode 100644
index 353d86f..0000000
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ /dev/null
@@ -1,118 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Masters",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Consultation Setup",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Consultation",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Settings",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Laboratory Setup",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Laboratory",
- "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Rehabilitation and Physiotherapy",
- "links": "[\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Records and History",
- "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
- },
- {
- "hidden": 0,
- "label": "Reports",
- "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]"
- }
- ],
- "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": "Desk Page",
- "extends_another_page": 0,
- "hide_custom": 0,
- "icon": "healthcare",
- "idx": 0,
- "is_standard": 1,
- "label": "Healthcare",
- "modified": "2020-09-10 15:37:23.666787",
- "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/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
index eb7d4bd..ff51646 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js
@@ -85,8 +85,7 @@
callback: function(r) {
if (r.message) {
frappe.show_alert({
- message: __('Stock Entry {0} created',
- ['<a class="bold" href="#Form/Stock Entry/'+ r.message + '">' + r.message + '</a>']),
+ message: __('Stock Entry {0} created', ['<a class="bold" href="/app/stock-entry/'+ r.message + '">' + r.message + '</a>']),
indicator: 'green'
});
}
@@ -105,8 +104,7 @@
callback: function(r) {
if (!r.exc) {
if (r.message == 'insufficient stock') {
- let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?',
- [frm.doc.warehouse.bold()]);
+ let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?', [frm.doc.warehouse.bold()]);
frappe.confirm(
msg,
function() {
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index cdf692e..7e7fd82 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -7,6 +7,7 @@
import unittest
from frappe.utils import nowdate, add_days
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_appointment, create_healthcare_service_items
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Company"]
@@ -15,6 +16,7 @@
frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity`""")
frappe.db.sql("""delete from `tabPatient`""")
+ make_pos_profile()
def test_fee_validity(self):
item = create_healthcare_service_items()
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
index f523cf2..ca97489 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
@@ -29,6 +29,29 @@
}
};
});
+
+ if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock)
+ return;
+
+ frm.add_custom_button(__('Make Stock Entry'), function() {
+ frappe.call({
+ method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry',
+ args: { docname: frm.doc.name },
+ freeze: true,
+ callback: function(r) {
+ if (r.message) {
+ var doclist = frappe.model.sync(r.message);
+ frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
+ } else {
+ frappe.msgprint({
+ title: __('No Drug Shortage'),
+ message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'),
+ indicator: 'green'
+ });
+ }
+ }
+ });
+ });
},
patient: function(frm) {
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
index 23e7519..70ae713 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
@@ -142,25 +142,32 @@
return orders, order_entry_map
def check_stock_qty(self):
- from erpnext.stock.stock_ledger import NegativeStockError
+ drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse)
- drug_availability = dict()
- for d in self.medication_orders:
- if not drug_availability.get(d.drug_code):
- drug_availability[d.drug_code] = 0
- drug_availability[d.drug_code] += flt(d.dosage)
+ if drug_shortage:
+ message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse))
+ message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.')
- for drug, dosage in drug_availability.items():
- available_qty = get_latest_stock_qty(drug, self.warehouse)
+ formatted_item_rows = ''
- # validate qty
- if flt(available_qty) < flt(dosage):
- frappe.throw(_('Quantity not available for {0} in warehouse {1}').format(
- frappe.bold(drug), frappe.bold(self.warehouse))
- + '<br><br>' + _('Available quantity is {0}, you need {1}').format(
- frappe.bold(available_qty), frappe.bold(dosage))
- + '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
- NegativeStockError, title=_('Insufficient Stock'))
+ for drug, shortage_qty in drug_shortage.items():
+ item_link = get_link_to_form('Item', drug)
+ formatted_item_rows += """
+ <td>{0}</td>
+ <td>{1}</td>
+ </tr>""".format(item_link, frappe.bold(shortage_qty))
+
+ message += """
+ <table class='table'>
+ <thead>
+ <th>{0}</th>
+ <th>{1}</th>
+ </thead>
+ {2}
+ </table>
+ """.format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows)
+
+ frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True)
def make_stock_entry(self):
stock_entry = frappe.new_doc('Stock Entry')
@@ -223,7 +230,8 @@
for doc in data:
inpatient_record = doc.inpatient_record
- doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
+ if inpatient_record:
+ doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if entry.service_unit and doc.service_unit != entry.service_unit:
to_remove.append(doc)
@@ -274,4 +282,57 @@
def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
- return ip_record.inpatient_occupancies[-1].service_unit
\ No newline at end of file
+ if ip_record.inpatient_occupancies:
+ return ip_record.inpatient_occupancies[-1].service_unit
+ return
+
+
+def get_drug_shortage_map(medication_orders, warehouse):
+ """
+ Returns a dict like { drug_code: shortage_qty }
+ """
+ drug_requirement = dict()
+ for d in medication_orders:
+ if not drug_requirement.get(d.drug_code):
+ drug_requirement[d.drug_code] = 0
+ drug_requirement[d.drug_code] += flt(d.dosage)
+
+ drug_shortage = dict()
+ for drug, required_qty in drug_requirement.items():
+ available_qty = get_latest_stock_qty(drug, warehouse)
+ if flt(required_qty) > flt(available_qty):
+ drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty))
+
+ return drug_shortage
+
+
+@frappe.whitelist()
+def make_difference_stock_entry(docname):
+ doc = frappe.get_doc('Inpatient Medication Entry', docname)
+ drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse)
+
+ if not drug_shortage:
+ return None
+
+ stock_entry = frappe.new_doc('Stock Entry')
+ stock_entry.purpose = 'Material Transfer'
+ stock_entry.set_stock_entry_type()
+ stock_entry.to_warehouse = doc.warehouse
+ stock_entry.company = doc.company
+ cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center')
+ expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company)
+
+ for drug, shortage_qty in drug_shortage.items():
+ se_child = stock_entry.append('items')
+ se_child.item_code = drug
+ se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom')
+ se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom')
+ se_child.stock_uom = se_child.uom
+ se_child.qty = flt(shortage_qty)
+ se_child.t_warehouse = doc.warehouse
+ # in stock uom
+ se_child.conversion_factor = 1
+ se_child.cost_center = cost_center
+ se_child.expense_account = expense_account
+
+ return stock_entry
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
index 2f1bb6b..7cb5a48 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py
@@ -9,6 +9,7 @@
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
+from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
class TestInpatientMedicationEntry(unittest.TestCase):
@@ -82,6 +83,39 @@
self.assertEqual(stock_entry.items[0].patient, self.patient)
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
+ def test_drug_shortage_stock_entry(self):
+ ipmo = create_ipmo(self.patient)
+ ipmo.submit()
+ ipmo.reload()
+
+ date = add_days(getdate(), -1)
+ filters = frappe._dict(
+ from_date=date,
+ to_date=date,
+ from_time='',
+ to_time='',
+ item_code='Dextromethorphan',
+ patient=self.patient
+ )
+
+ # check drug shortage
+ ipme = create_ipme(filters, update_stock=1)
+ ipme.warehouse = 'Finished Goods - _TC'
+ ipme.save()
+ drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse)
+ self.assertEqual(drug_shortage.get('Dextromethorphan'), 3)
+
+ # check material transfer for drug shortage
+ make_stock_entry()
+ stock_entry = make_difference_stock_entry(ipme.name)
+ self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan')
+ self.assertEqual(stock_entry.items[0].qty, 3)
+ stock_entry.from_warehouse = 'Stores - _TC'
+ stock_entry.submit()
+
+ ipme.reload()
+ ipme.submit()
+
def tearDown(self):
# cleanup - Discharge
schedule_discharge(frappe.as_json({'patient': self.patient}))
@@ -94,15 +128,12 @@
for entry in frappe.get_all('Inpatient Medication Entry'):
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
doc.cancel()
- frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
- doc.delete()
for entry in frappe.get_all('Inpatient Medication Order'):
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
doc.cancel()
- doc.delete()
-def make_stock_entry():
+def make_stock_entry(warehouse=None):
frappe.db.set_value('Company', '_Test Company', {
'stock_adjustment_account': 'Stock Adjustment - _TC',
'default_inventory_account': 'Stock In Hand - _TC'
@@ -110,7 +141,7 @@
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.stock_entry_type = 'Material Receipt'
stock_entry.company = '_Test Company'
- stock_entry.to_warehouse = 'Stores - _TC'
+ stock_entry.to_warehouse = warehouse or 'Stores - _TC'
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
se_child = stock_entry.append('items')
se_child.item_code = 'Dextromethorphan'
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index bc76970..c7ab447 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -50,7 +50,7 @@
if ip_record:
msg = _(("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) \
- + """ <b><a href="#Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
+ + """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
frappe.throw(msg)
def admit(self, service_unit, check_in, expected_discharge=None):
diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py
index e3def72..39603f7 100644
--- a/erpnext/healthcare/doctype/patient/patient_dashboard.py
+++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py
@@ -18,6 +18,10 @@
{
'label': _('Billing'),
'items': ['Sales Invoice']
+ },
+ {
+ 'label': _('Orders'),
+ 'items': ['Inpatient Medication Order']
}
]
}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index e685b20..90d9023 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -63,7 +63,7 @@
if overlaps:
overlapping_details = _('Appointment overlaps with ')
- overlapping_details += "<b><a href='#Form/Patient Appointment/{0}'>{0}</a></b><br>".format(overlaps[0][0])
+ overlapping_details += "<b><a href='/app/Form/Patient Appointment/{0}'>{0}</a></b><br>".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'))
@@ -75,7 +75,7 @@
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 += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(self.patient)
+ msg += " <b><a href='/app/Form/Patient/{0}'>{0}</a></b>".format(self.patient)
frappe.throw(msg, title=_('Customer Not Found'))
def update_prescription_details(self):
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index eeed157..3df7ba1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -7,12 +7,14 @@
from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
from frappe.utils import nowdate, add_days
from frappe.utils.make_random import get_random
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
class TestPatientAppointment(unittest.TestCase):
def setUp(self):
frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity`""")
frappe.db.sql("""delete from `tabPatient Encounter`""")
+ make_pos_profile()
def test_status(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index aa85a23..419d956 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -6,11 +6,13 @@
import frappe
from frappe.utils import nowdate
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
class TestPatientMedicalRecord(unittest.TestCase):
def setUp(self):
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
+ make_pos_profile()
def test_medical_record(self):
patient, medical_department, practitioner = create_healthcare_docs()
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/healthcare/report/inpatient_medication_orders/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/healthcare/report/inpatient_medication_orders/__init__.py
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js
new file mode 100644
index 0000000..a10f837
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js
@@ -0,0 +1,57 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Inpatient Medication Orders"] = {
+ "filters": [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ reqd: 1
+ },
+ {
+ fieldname: "to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.now_date(),
+ reqd: 1
+ },
+ {
+ fieldname: "patient",
+ label: __("Patient"),
+ fieldtype: "Link",
+ options: "Patient"
+ },
+ {
+ fieldname: "service_unit",
+ label: __("Healthcare Service Unit"),
+ fieldtype: "Link",
+ options: "Healthcare Service Unit",
+ get_query: () => {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ 'company': company,
+ 'is_group': 0
+ }
+ }
+ }
+ },
+ {
+ fieldname: "show_completed_orders",
+ label: __("Show Completed Orders"),
+ fieldtype: "Check",
+ default: 1
+ }
+ ]
+};
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json
new file mode 100644
index 0000000..9217fa1
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-11-23 17:25:58.802949",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2020-11-23 19:40:20.227591",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Medication Orders",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Inpatient Medication Order",
+ "report_name": "Inpatient Medication Orders",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Healthcare Administrator"
+ },
+ {
+ "role": "Nursing User"
+ },
+ {
+ "role": "Physician"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
new file mode 100644
index 0000000..b907730
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py
@@ -0,0 +1,198 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
+
+def execute(filters=None):
+ columns = get_columns()
+ data = get_data(filters)
+ chart = get_chart_data(data)
+
+ return columns, data, None, chart
+
+def get_columns():
+ return [
+ {
+ "fieldname": "patient",
+ "fieldtype": "Link",
+ "label": "Patient",
+ "options": "Patient",
+ "width": 200
+ },
+ {
+ "fieldname": "healthcare_service_unit",
+ "fieldtype": "Link",
+ "label": "Healthcare Service Unit",
+ "options": "Healthcare Service Unit",
+ "width": 150
+ },
+ {
+ "fieldname": "drug",
+ "fieldtype": "Link",
+ "label": "Drug Code",
+ "options": "Item",
+ "width": 150
+ },
+ {
+ "fieldname": "drug_name",
+ "fieldtype": "Data",
+ "label": "Drug Name",
+ "width": 150
+ },
+ {
+ "fieldname": "dosage",
+ "fieldtype": "Link",
+ "label": "Dosage",
+ "options": "Prescription Dosage",
+ "width": 80
+ },
+ {
+ "fieldname": "dosage_form",
+ "fieldtype": "Link",
+ "label": "Dosage Form",
+ "options": "Dosage Form",
+ "width": 100
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "label": "Date",
+ "width": 100
+ },
+ {
+ "fieldname": "time",
+ "fieldtype": "Time",
+ "label": "Time",
+ "width": 100
+ },
+ {
+ "fieldname": "is_completed",
+ "fieldtype": "Check",
+ "label": "Is Order Completed",
+ "width": 100
+ },
+ {
+ "fieldname": "healthcare_practitioner",
+ "fieldtype": "Link",
+ "label": "Healthcare Practitioner",
+ "options": "Healthcare Practitioner",
+ "width": 200
+ },
+ {
+ "fieldname": "inpatient_medication_entry",
+ "fieldtype": "Link",
+ "label": "Inpatient Medication Entry",
+ "options": "Inpatient Medication Entry",
+ "width": 200
+ },
+ {
+ "fieldname": "inpatient_record",
+ "fieldtype": "Link",
+ "label": "Inpatient Record",
+ "options": "Inpatient Record",
+ "width": 200
+ }
+ ]
+
+def get_data(filters):
+ conditions, values = get_conditions(filters)
+
+ data = frappe.db.sql("""
+ SELECT
+ parent.patient, parent.inpatient_record, parent.practitioner,
+ child.drug, child.drug_name, child.dosage, child.dosage_form,
+ child.date, child.time, child.is_completed, child.name
+ FROM `tabInpatient Medication Order` parent
+ INNER JOIN `tabInpatient Medication Order Entry` child
+ ON child.parent = parent.name
+ WHERE
+ parent.docstatus = 1
+ {conditions}
+ ORDER BY date, time
+ """.format(conditions=conditions), values, as_dict=1)
+
+ data = get_inpatient_details(data, filters.get("service_unit"))
+
+ return data
+
+def get_conditions(filters):
+ conditions = ""
+ values = dict()
+
+ if filters.get("company"):
+ conditions += " AND parent.company = %(company)s"
+ values["company"] = filters.get("company")
+
+ if filters.get("from_date") and filters.get("to_date"):
+ conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s"
+ values["from_date"] = filters.get("from_date")
+ values["to_date"] = filters.get("to_date")
+
+ if filters.get("patient"):
+ conditions += " AND parent.patient = %(patient)s"
+ values["patient"] = filters.get("patient")
+
+ if not filters.get("show_completed_orders"):
+ conditions += " AND child.is_completed = 0"
+
+ return conditions, values
+
+
+def get_inpatient_details(data, service_unit):
+ service_unit_filtered_data = []
+
+ for entry in data:
+ entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record)
+ if entry.is_completed:
+ entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name)
+
+ if service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit:
+ service_unit_filtered_data.append(entry)
+
+ entry.pop("name", None)
+
+ for entry in service_unit_filtered_data:
+ data.remove(entry)
+
+ return data
+
+def get_inpatient_medication_entry(order_entry):
+ return frappe.db.get_value("Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent")
+
+def get_chart_data(data):
+ if not data:
+ return None
+
+ labels = ["Pending", "Completed"]
+ datasets = []
+
+ status_wise_data = {
+ "Pending": 0,
+ "Completed": 0
+ }
+
+ for d in data:
+ if d.is_completed:
+ status_wise_data["Completed"] += 1
+ else:
+ status_wise_data["Pending"] += 1
+
+ datasets.append({
+ "name": "Inpatient Medication Order Status",
+ "values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")]
+ })
+
+ chart = {
+ "data": {
+ "labels": labels,
+ "datasets": datasets
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ chart["fieldtype"] = "Data"
+
+ return chart
\ No newline at end of file
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
new file mode 100644
index 0000000..0d3f45f
--- /dev/null
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -0,0 +1,128 @@
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+import datetime
+from frappe.utils import getdate, now_datetime
+from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
+from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
+from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
+from erpnext.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import execute
+
+class TestInpatientMedicationOrders(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'")
+ frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'")
+ self.patient = create_patient()
+ self.ip_record = create_records(self.patient)
+
+ def test_inpatient_medication_orders_report(self):
+ filters = {
+ 'company': '_Test Company',
+ 'from_date': getdate(),
+ 'to_date': getdate(),
+ 'patient': '_Test IPD Patient',
+ 'service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ }
+
+ report = execute(filters)
+
+ expected_data = [
+ {
+ 'patient': '_Test IPD Patient',
+ 'inpatient_record': self.ip_record.name,
+ 'practitioner': None,
+ 'drug': 'Dextromethorphan',
+ 'drug_name': 'Dextromethorphan',
+ 'dosage': 1.0,
+ 'dosage_form': 'Tablet',
+ 'date': getdate(),
+ 'time': datetime.timedelta(seconds=32400),
+ 'is_completed': 0,
+ 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ },
+ {
+ 'patient': '_Test IPD Patient',
+ 'inpatient_record': self.ip_record.name,
+ 'practitioner': None,
+ 'drug': 'Dextromethorphan',
+ 'drug_name': 'Dextromethorphan',
+ 'dosage': 1.0,
+ 'dosage_form': 'Tablet',
+ 'date': getdate(),
+ 'time': datetime.timedelta(seconds=50400),
+ 'is_completed': 0,
+ 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ },
+ {
+ 'patient': '_Test IPD Patient',
+ 'inpatient_record': self.ip_record.name,
+ 'practitioner': None,
+ 'drug': 'Dextromethorphan',
+ 'drug_name': 'Dextromethorphan',
+ 'dosage': 1.0,
+ 'dosage_form': 'Tablet',
+ 'date': getdate(),
+ 'time': datetime.timedelta(seconds=75600),
+ 'is_completed': 0,
+ 'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+ }
+ ]
+
+ self.assertEqual(expected_data, report[1])
+
+ filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='')
+ ipme = create_ipme(filters)
+ ipme.submit()
+
+ filters = {
+ 'company': '_Test Company',
+ 'from_date': getdate(),
+ 'to_date': getdate(),
+ 'patient': '_Test IPD Patient',
+ 'service_unit': 'Test Service Unit Ip Occupancy - _TC',
+ 'show_completed_orders': 0
+ }
+
+ report = execute(filters)
+ self.assertEqual(len(report[1]), 0)
+
+ def tearDown(self):
+ if frappe.db.get_value('Patient', self.patient, 'inpatient_record'):
+ # cleanup - Discharge
+ schedule_discharge(frappe.as_json({'patient': self.patient}))
+ self.ip_record.reload()
+ mark_invoiced_inpatient_occupancy(self.ip_record)
+
+ self.ip_record.reload()
+ discharge_patient(self.ip_record)
+
+ for entry in frappe.get_all('Inpatient Medication Entry'):
+ doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
+ doc.cancel()
+ doc.delete()
+
+ for entry in frappe.get_all('Inpatient Medication Order'):
+ doc = frappe.get_doc('Inpatient Medication Order', entry.name)
+ doc.cancel()
+ doc.delete()
+
+
+def create_records(patient):
+ frappe.db.sql("""delete from `tabInpatient Record`""")
+
+ # Admit
+ ip_record = create_inpatient(patient)
+ ip_record.expected_length_of_stay = 0
+ ip_record.save()
+ ip_record.reload()
+ service_unit = get_healthcare_service_unit()
+ admit_patient(ip_record, service_unit, now_datetime())
+
+ ipmo = create_ipmo(patient)
+ ipmo.submit()
+
+ return ip_record
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index 96282f5..2486923 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -32,7 +32,7 @@
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 += " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
+ msg += " <b><a href='/app/Form/Patient/{0}'>{0}</a></b>".format(patient.name)
frappe.throw(msg, title=_('Customer Not Found'))
@@ -169,7 +169,7 @@
service_item = get_healthcare_service_item('clinical_procedure_consumable_item')
if not service_item:
msg = _('Please Configure Clinical Procedure Consumable Item in ')
- msg += '''<b><a href='#Form/Healthcare Settings'>Healthcare Settings</a></b>'''
+ msg += '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>'''
frappe.throw(msg, title=_('Missing Configuration'))
clinical_procedures_to_invoice.append({
@@ -324,7 +324,7 @@
service_item_label = _('Inpatient Visit Charge Item')
msg = _(('Please Configure {0} in ').format(service_item_label) \
- + '''<b><a href='#Form/Healthcare Settings'>Healthcare Settings</a></b>''')
+ + '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>''')
frappe.throw(msg, title=_('Missing Configuration'))
@@ -334,7 +334,7 @@
charge_name = _('Inpatient Visit Charge')
msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \
- + ''' <b><a href='#Form/Healthcare Practitioner/{0}'>{0}</a></b>'''.format(practitioner))
+ + ''' <b><a href='/app/Form/Healthcare Practitioner/{0}'>{0}</a></b>'''.format(practitioner))
frappe.throw(msg, title=_('Missing Configuration'))
@@ -654,6 +654,6 @@
><div class='col-md-12 col-sm-12'>" \
+ section_html + html +'</div></div>'
if doc_html:
- doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='#Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
+ doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='/app/Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
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 0000000..b93dda2
--- /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 aadead2..b0de3a0 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -238,6 +238,9 @@
"Website Settings": {
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
},
+ "Tax Category": {
+ "validate": "erpnext.regional.india.utils.validate_tax_category"
+ },
"Sales Invoice": {
"on_submit": [
"erpnext.regional.create_transaction_log",
@@ -251,7 +254,11 @@
"on_trash": "erpnext.regional.check_deletion_permission"
},
"Purchase Invoice": {
- "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm"
+ "validate": [
+ "erpnext.regional.india.utils.update_grand_total_for_rcm",
+ "erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
+ "erpnext.regional.united_arab_emirates.utils.validate_returns"
+ ]
},
"Payment Entry": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
@@ -265,11 +272,11 @@
},
"Contact": {
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
- "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
+ "after_insert": "erpnext.telephony.doctype.call_log.call_log.set_caller_information",
"validate": "erpnext.crm.utils.update_lead_phone_numbers"
},
"Lead": {
- "after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
+ "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"
@@ -341,14 +348,16 @@
"erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
+ "erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
"erpnext.hr.utils.generate_leave_encashment",
+ "erpnext.hr.utils.allocate_earned_leaves",
+ "erpnext.hr.utils.grant_leaves_automatically",
"erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall.create_process_loan_security_shortfall",
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
"erpnext.crm.doctype.lead.lead.daily_open_lead"
],
"monthly_long": [
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
- "erpnext.hr.utils.allocate_earned_leaves",
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_demand_loans"
]
}
@@ -392,7 +401,8 @@
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
},
'United Arab Emirates': {
- 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
+ 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
+ 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.united_arab_emirates.utils.make_regional_gl_entries',
},
'Saudi Arabia': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
@@ -432,42 +442,43 @@
{"doctype": "Sales Order", "index": 8},
{"doctype": "Quotation", "index": 9},
{"doctype": "Work Order", "index": 10},
- {"doctype": "Purchase Receipt", "index": 11},
- {"doctype": "Purchase Invoice", "index": 12},
- {"doctype": "Delivery Note", "index": 13},
- {"doctype": "Stock Entry", "index": 14},
- {"doctype": "Material Request", "index": 15},
- {"doctype": "Delivery Trip", "index": 16},
- {"doctype": "Pick List", "index": 17},
- {"doctype": "Salary Slip", "index": 18},
- {"doctype": "Leave Application", "index": 19},
- {"doctype": "Expense Claim", "index": 20},
- {"doctype": "Payment Entry", "index": 21},
- {"doctype": "Lead", "index": 22},
- {"doctype": "Opportunity", "index": 23},
- {"doctype": "Item Price", "index": 24},
- {"doctype": "Purchase Taxes and Charges Template", "index": 25},
- {"doctype": "Sales Taxes and Charges", "index": 26},
- {"doctype": "Asset", "index": 27},
- {"doctype": "Project", "index": 28},
- {"doctype": "Task", "index": 29},
- {"doctype": "Timesheet", "index": 30},
- {"doctype": "Issue", "index": 31},
- {"doctype": "Serial No", "index": 32},
- {"doctype": "Batch", "index": 33},
- {"doctype": "Branch", "index": 34},
- {"doctype": "Department", "index": 35},
- {"doctype": "Employee Grade", "index": 36},
- {"doctype": "Designation", "index": 37},
- {"doctype": "Job Opening", "index": 38},
- {"doctype": "Job Applicant", "index": 39},
- {"doctype": "Job Offer", "index": 40},
- {"doctype": "Salary Structure Assignment", "index": 41},
- {"doctype": "Appraisal", "index": 42},
- {"doctype": "Loan", "index": 43},
- {"doctype": "Maintenance Schedule", "index": 44},
- {"doctype": "Maintenance Visit", "index": 45},
- {"doctype": "Warranty Claim", "index": 46},
+ {"doctype": "Purchase Order", "index": 11},
+ {"doctype": "Purchase Receipt", "index": 12},
+ {"doctype": "Purchase Invoice", "index": 13},
+ {"doctype": "Delivery Note", "index": 14},
+ {"doctype": "Stock Entry", "index": 15},
+ {"doctype": "Material Request", "index": 16},
+ {"doctype": "Delivery Trip", "index": 17},
+ {"doctype": "Pick List", "index": 18},
+ {"doctype": "Salary Slip", "index": 19},
+ {"doctype": "Leave Application", "index": 20},
+ {"doctype": "Expense Claim", "index": 21},
+ {"doctype": "Payment Entry", "index": 22},
+ {"doctype": "Lead", "index": 23},
+ {"doctype": "Opportunity", "index": 24},
+ {"doctype": "Item Price", "index": 25},
+ {"doctype": "Purchase Taxes and Charges Template", "index": 26},
+ {"doctype": "Sales Taxes and Charges", "index": 27},
+ {"doctype": "Asset", "index": 28},
+ {"doctype": "Project", "index": 29},
+ {"doctype": "Task", "index": 30},
+ {"doctype": "Timesheet", "index": 31},
+ {"doctype": "Issue", "index": 32},
+ {"doctype": "Serial No", "index": 33},
+ {"doctype": "Batch", "index": 34},
+ {"doctype": "Branch", "index": 35},
+ {"doctype": "Department", "index": 36},
+ {"doctype": "Employee Grade", "index": 37},
+ {"doctype": "Designation", "index": 38},
+ {"doctype": "Job Opening", "index": 39},
+ {"doctype": "Job Applicant", "index": 40},
+ {"doctype": "Job Offer", "index": 41},
+ {"doctype": "Salary Structure Assignment", "index": 42},
+ {"doctype": "Appraisal", "index": 43},
+ {"doctype": "Loan", "index": 44},
+ {"doctype": "Maintenance Schedule", "index": 45},
+ {"doctype": "Maintenance Visit", "index": 46},
+ {"doctype": "Warranty Claim", "index": 47},
],
"Healthcare": [
{'doctype': 'Patient', 'index': 1},
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
deleted file mode 100644
index 217b94a..0000000
--- a/erpnext/hr/desk_page/hr/hr.json
+++ /dev/null
@@ -1,146 +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": "Payroll",
- "links": "[\n {\n \"label\": \"Salary Structure\",\n \"name\": \"Salary Structure\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Salary Structure\",\n \"Employee\"\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 {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Salary Component\",\n \"name\": \"Salary Component\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Additional Salary\",\n \"name\": \"Additional Salary\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Retention Bonus\",\n \"name\": \"Retention Bonus\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Incentive\",\n \"name\": \"Employee Incentive\",\n \"type\": \"doctype\"\n },\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]"
- },
- {
- "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": "Loans",
- "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\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]"
- },
- {
- "hidden": 0,
- "label": "Employee Tax and Benefits",
- "links": "[\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\",\n \"Payroll Period\"\n ],\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Application\",\n \"name\": \"Employee Benefit Application\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Benefit Claim\",\n \"name\": \"Employee Benefit Claim\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Employee\"\n ],\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n }\n]"
- }
- ],
- "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": "Desk Page",
- "extends_another_page": 0,
- "hide_custom": 0,
- "icon": "hr",
- "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": "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/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index 9b2de0e..d337959 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -20,7 +20,7 @@
approvers = []
department_details = {}
department_list = []
- employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
+ employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department:
@@ -59,11 +59,9 @@
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
if len(approvers) == 0:
- frappe.throw(_("Please set {0} for the Employee or for Department: {1}").
- format(
- field_name, frappe.bold(employee_department),
- frappe.bold(employee.name)
- ),
- title=_(field_name + " Missing"))
+ error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
+ if department_list:
+ error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
+ frappe.throw(error_msg, title=_(field_name + " Missing"))
return set(tuple(approver) for approver in approvers)
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index da78919..4f1c04f 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -57,7 +57,6 @@
"column_break_45",
"shift_request_approver",
"attendance_and_leave_details",
- "leave_policy",
"attendance_device_id",
"column_break_44",
"holiday_list",
@@ -412,14 +411,6 @@
"options": "Branch"
},
{
- "fetch_from": "grade.default_leave_policy",
- "fetch_if_empty": 1,
- "fieldname": "leave_policy",
- "fieldtype": "Link",
- "label": "Leave Policy",
- "options": "Leave Policy"
- },
- {
"description": "Applicable Holiday List",
"fieldname": "holiday_list",
"fieldtype": "Link",
@@ -672,10 +663,10 @@
"oldfieldtype": "Date"
},
{
- "depends_on": "eval:doc.status == \"Left\"",
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
+ "mandatory_depends_on": "eval:doc.status == \"Left\"",
"oldfieldname": "relieving_date",
"oldfieldtype": "Date"
},
@@ -822,7 +813,7 @@
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-10-06 15:58:23.805489",
+ "modified": "2020-10-16 15:02:04.283657",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index cba8ee9..5037ceb 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -15,11 +15,21 @@
});
frm.set_query("advance_account", function() {
+ if (!frm.doc.employee) {
+ frappe.msgprint(__("Please select employee first"));
+ }
+ let company_currency = erpnext.get_currency(frm.doc.company);
+ let currencies = [company_currency];
+ if (frm.doc.currency && (frm.doc.currency != company_currency)) {
+ currencies.push(frm.doc.currency);
+ }
+
return {
filters: {
"root_type": "Asset",
"is_group": 0,
- "company": frm.doc.company
+ "company": frm.doc.company,
+ "account_currency": ["in", currencies],
}
};
});
@@ -63,7 +73,7 @@
}, __('Create'));
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
frm.add_custom_button(__("Deduction from salary"), function() {
- frm.events.make_deduction_via_additional_salary(frm)
+ frm.events.make_deduction_via_additional_salary(frm);
}, __('Create'));
}
}
@@ -127,7 +137,9 @@
'employee_advance_name': frm.doc.name,
'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount),
'advance_account': frm.doc.advance_account,
- 'mode_of_payment': frm.doc.mode_of_payment
+ 'mode_of_payment': frm.doc.mode_of_payment,
+ 'currency': frm.doc.currency,
+ 'exchange_rate': frm.doc.exchange_rate
},
callback: function(r) {
const doclist = frappe.model.sync(r.message);
@@ -138,16 +150,74 @@
employee: function (frm) {
if (frm.doc.employee) {
- return frappe.call({
- method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
- args: {
- "employee": frm.doc.employee,
- "posting_date": frm.doc.posting_date
- },
- callback: function(r) {
- frm.set_value("pending_amount",r.message);
- }
- });
+ frappe.run_serially([
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('get_pending_amount')
+ ]);
}
+ },
+
+ get_pending_amount: function(frm) {
+ frappe.call({
+ method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
+ args: {
+ "employee": frm.doc.employee,
+ "posting_date": frm.doc.posting_date
+ },
+ callback: function(r) {
+ frm.set_value("pending_amount", r.message);
+ }
+ });
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ },
+
+ currency: function(frm) {
+ if (frm.doc.currency) {
+ var from_currency = frm.doc.currency;
+ var company_currency;
+ if (!frm.doc.company) {
+ company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
+ } else {
+ company_currency = erpnext.get_currency(frm.doc.company);
+ }
+ if (from_currency != company_currency) {
+ frm.events.set_exchange_rate(frm, from_currency, company_currency);
+ } else {
+ frm.set_value("exchange_rate", 1.0);
+ frm.set_df_property('exchange_rate', 'hidden', 1);
+ frm.set_df_property("exchange_rate", "description", "" );
+ }
+ frm.refresh_fields();
+ }
+ },
+
+ set_exchange_rate: function(frm, from_currency, company_currency) {
+ frappe.call({
+ method: "erpnext.setup.utils.get_exchange_rate",
+ args: {
+ from_currency: from_currency,
+ to_currency: company_currency,
+ },
+ callback: function(r) {
+ frm.set_value("exchange_rate", flt(r.message));
+ frm.set_df_property('exchange_rate', 'hidden', 0);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+ + " = [?] " + company_currency);
+ }
+ });
}
});
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 0d90913..cf6b540 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -13,6 +13,8 @@
"department",
"column_break_4",
"posting_date",
+ "currency",
+ "exchange_rate",
"repay_unclaimed_amount_from_salary",
"section_break_8",
"purpose",
@@ -91,7 +93,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Advance Amount",
- "options": "Company:company:default_currency",
+ "options": "currency",
"reqd": 1
},
{
@@ -99,7 +101,7 @@
"fieldtype": "Currency",
"label": "Paid Amount",
"no_copy": 1,
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -107,7 +109,7 @@
"fieldtype": "Currency",
"label": "Claimed Amount",
"no_copy": 1,
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -161,7 +163,7 @@
"fieldname": "return_amount",
"fieldtype": "Currency",
"label": "Returned Amount",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -175,13 +177,31 @@
"fieldname": "pending_amount",
"fieldtype": "Currency",
"label": "Pending Amount",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "reqd": 1
+ },
+ {
+ "depends_on": "currency",
+ "fieldname": "exchange_rate",
+ "fieldtype": "Float",
+ "label": "Exchange Rate",
+ "precision": "9",
+ "print_hide": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-12 12:42:39.833818",
+ "modified": "2020-11-25 12:01:55.980721",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 3c435b8..cb72f6b 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -19,7 +19,6 @@
def validate(self):
self.set_status()
- self.validate_employee_advance_account()
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
@@ -38,16 +37,9 @@
elif self.docstatus == 2:
self.status = "Cancelled"
- def validate_employee_advance_account(self):
- company_currency = erpnext.get_company_currency(self.company)
- if (self.advance_account and
- company_currency != frappe.db.get_value('Account', self.advance_account, 'account_currency')):
- frappe.throw(_("Advance account currency should be same as company currency {0}")
- .format(company_currency))
-
def set_total_advance_paid(self):
paid_amount = frappe.db.sql("""
- select ifnull(sum(debit_in_account_currency), 0) as paid_amount
+ select ifnull(sum(debit), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and against_voucher = %s
@@ -56,7 +48,7 @@
""", (self.name, self.employee), as_dict=1)[0].paid_amount
return_amount = frappe.db.sql("""
- select name, ifnull(sum(credit_in_account_currency), 0) as return_amount
+ select ifnull(sum(credit), 0) as return_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and voucher_type != 'Expense Claim'
@@ -65,6 +57,11 @@
and party = %s
""", (self.name, self.employee), as_dict=1)[0].return_amount
+ if paid_amount != 0:
+ paid_amount = flt(paid_amount) / flt(self.exchange_rate)
+ if return_amount != 0:
+ return_amount = flt(return_amount) / flt(self.exchange_rate)
+
if flt(paid_amount) > self.advance_amount:
frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
EmployeeAdvanceOverPayment)
@@ -107,16 +104,27 @@
doc = frappe.get_doc(dt, dn)
payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
mode_of_payment=doc.mode_of_payment)
+ if not payment_account:
+ frappe.throw(_("Please set a Default Cash Account in Company defaults"))
+
+ advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
+
+ advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
+
+ paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
je.voucher_type = 'Bank Entry'
je.company = doc.company
je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
+ je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
je.append("accounts", {
"account": doc.advance_account,
- "debit_in_account_currency": flt(doc.advance_amount),
+ "account_currency": advance_account_currency,
+ "exchange_rate": flt(advance_exchange_rate),
+ "debit_in_account_currency": flt(advance_amount),
"reference_type": "Employee Advance",
"reference_name": doc.name,
"party_type": "Employee",
@@ -128,19 +136,41 @@
je.append("accounts", {
"account": payment_account.account,
"cost_center": erpnext.get_default_cost_center(doc.company),
- "credit_in_account_currency": flt(doc.advance_amount),
+ "credit_in_account_currency": flt(paying_amount),
"account_currency": payment_account.account_currency,
- "account_type": payment_account.account_type
+ "account_type": payment_account.account_type,
+ "exchange_rate": flt(paying_exchange_rate)
})
return je.as_dict()
+def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
+ if advance_account_currency != doc.currency:
+ advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
+ advance_exchange_rate = 1
+ else:
+ advance_amount = doc.advance_amount
+ advance_exchange_rate = doc.exchange_rate
+
+ return advance_amount, advance_exchange_rate
+
+def get_paying_amount_paying_exchange_rate(payment_account, doc):
+ if payment_account.account_currency != doc.currency:
+ paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
+ paying_exchange_rate = 1
+ else:
+ paying_amount = doc.advance_amount
+ paying_exchange_rate = doc.exchange_rate
+
+ return paying_amount, paying_exchange_rate
+
@frappe.whitelist()
def create_return_through_additional_salary(doc):
import json
doc = frappe._dict(json.loads(doc))
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = doc.employee
+ additional_salary.currency = doc.currency
additional_salary.amount = doc.paid_amount - doc.claimed_amount
additional_salary.company = doc.company
additional_salary.ref_doctype = doc.doctype
@@ -149,26 +179,28 @@
return additional_salary
@frappe.whitelist()
-def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, mode_of_payment=None):
- return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
-
- mode_of_payment_type = ''
- if mode_of_payment:
- mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
- if mode_of_payment_type not in ["Cash", "Bank"]:
- # if mode of payment is General then it unset the type
- mode_of_payment_type = None
-
+def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, currency, exchange_rate, mode_of_payment=None):
+ bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
+ if not bank_cash_account:
+ frappe.throw(_("Please set a Default Cash Account in Company defaults"))
+
+ advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
+
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
- # if mode of payment is Bank then voucher type is Bank Entry
- je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
+ je.voucher_type = get_voucher_type(mode_of_payment)
je.company = company
je.remark = 'Return against Employee Advance: ' + employee_advance_name
+ je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
+
+ advance_account_amount = flt(return_amount) if advance_account_currency==currency \
+ else flt(return_amount) * flt(exchange_rate)
je.append('accounts', {
'account': advance_account,
- 'credit_in_account_currency': return_amount,
+ 'credit_in_account_currency': advance_account_amount,
+ 'account_currency': advance_account_currency,
+ 'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
'reference_type': 'Employee Advance',
'reference_name': employee_advance_name,
'party_type': 'Employee',
@@ -176,13 +208,25 @@
'is_advance': 'Yes'
})
+ bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
+ else flt(return_amount) * flt(exchange_rate)
+
je.append("accounts", {
- "account": return_account.account,
- "debit_in_account_currency": return_amount,
- "account_currency": return_account.account_currency,
- "account_type": return_account.account_type
+ "account": bank_cash_account.account,
+ "debit_in_account_currency": bank_amount,
+ "account_currency": bank_cash_account.account_currency,
+ "account_type": bank_cash_account.account_type,
+ "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
})
return je.as_dict()
+def get_voucher_type(mode_of_payment=None):
+ voucher_type = "Cash Entry"
+ if mode_of_payment:
+ mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
+ if mode_of_payment_type == "Bank":
+ voucher_type = "Bank Entry"
+
+ return voucher_type
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 2097e71..c88b2b8 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -3,15 +3,17 @@
# See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
import unittest
from frappe.utils import nowdate
from erpnext.hr.doctype.employee_advance.employee_advance import make_bank_entry
from erpnext.hr.doctype.employee_advance.employee_advance import EmployeeAdvanceOverPayment
+from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeAdvance(unittest.TestCase):
def test_paid_amount_and_status(self):
- advance = make_employee_advance()
+ employee_name = make_employee("_T@employe.advance")
+ advance = make_employee_advance(employee_name)
journal_entry = make_payment_entry(advance)
journal_entry.submit()
@@ -33,11 +35,13 @@
return journal_entry
-def make_employee_advance():
+def make_employee_advance(employee_name):
doc = frappe.new_doc("Employee Advance")
- doc.employee = "_T-Employee-00001"
+ doc.employee = employee_name
doc.company = "_Test company"
doc.purpose = "For site visit"
+ doc.currency = erpnext.get_company_currency("_Test company")
+ doc.exchange_rate = 1
doc.advance_amount = 1000
doc.posting_date = nowdate()
doc.advance_account = "_Test Employee Advance - _TC"
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade.json b/erpnext/hr/doctype/employee_grade/employee_grade.json
index e63ffae..88b061a 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade.json
+++ b/erpnext/hr/doctype/employee_grade/employee_grade.json
@@ -1,167 +1,69 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "autoname": "Prompt",
- "beta": 0,
- "creation": "2018-04-13 16:14:24.174138",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 16:14:24.174138",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "default_salary_structure"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "default_leave_policy",
- "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": "Default Leave Policy",
- "length": 0,
- "no_copy": 0,
- "options": "Leave Policy",
- "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": "default_salary_structure",
"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": "Default Salary Structure",
- "length": 0,
- "no_copy": 0,
- "options": "Salary Structure",
- "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": "Salary Structure"
}
],
- "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-09-18 17:17:45.617624",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-08-26 13:12:07.815330",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Grade",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
},
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"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,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index c730e02..37d616f 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -50,7 +50,7 @@
employee = frappe.get_doc("Employee", self.employee)
if self.create_new_employee_id:
if self.new_employee_id:
- frappe.throw(_("Please delete the Employee <a href='#Form/Employee/{0}'>{0}</a>\
+ frappe.throw(_("Please delete the Employee <a href='/app/Form/Employee/{0}'>{0}</a>\
to cancel this document").format(self.new_employee_id))
#mark the employee as active
employee.status = "Active"
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 6e97f05..4a0908d 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -7,6 +7,7 @@
from frappe.utils import random_string, nowdate
from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.hr.doctype.employee.test_employee import make_employee
test_records = frappe.get_test_records('Expense Claim')
test_dependencies = ['Employee']
@@ -126,6 +127,9 @@
def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
employee = frappe.db.get_value("Employee", {"status": "Active"})
+ if not employee:
+ employee = make_employee("test_employee@expense_claim.com", company=company)
+
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
expense_claim = {
"doctype": "Expense Claim",
diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
index 885e3ee..020457d 100644
--- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
+++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
@@ -71,9 +71,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
- "oldfieldname": "tax_amount",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"columns": 2,
@@ -81,9 +79,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total",
- "oldfieldname": "total",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -106,7 +102,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-05-11 19:01:26.611758",
+ "modified": "2020-09-23 20:27:36.027728",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Taxes and Charges",
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 4374d29..f999635 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -21,6 +21,7 @@
"show_leaves_of_all_department_members_in_calendar",
"auto_leave_encashment",
"restrict_backdated_leave_application",
+ "automatically_allocate_leaves_based_on_leave_policy",
"hiring_settings",
"check_vacancies"
],
@@ -41,7 +42,7 @@
"description": "Employee records are created using the selected field",
"fieldname": "emp_created_by",
"fieldtype": "Select",
- "label": "Employee Records to Be Created By",
+ "label": "Employee Records to be created by",
"options": "Naming Series\nEmployee Number\nFull Name"
},
{
@@ -117,7 +118,7 @@
"default": "0",
"fieldname": "restrict_backdated_leave_application",
"fieldtype": "Check",
- "label": "Restrict Backdated Leave Applications"
+ "label": "Restrict Backdated Leave Application"
},
{
"depends_on": "eval:doc.restrict_backdated_leave_application == 1",
@@ -125,13 +126,19 @@
"fieldtype": "Link",
"label": "Role Allowed to Create Backdated Leave Application",
"options": "Role"
+ },
+ {
+ "default": "0",
+ "fieldname": "automatically_allocate_leaves_based_on_leave_policy",
+ "fieldtype": "Check",
+ "label": "Automatically Allocate Leaves Based On Leave Policy"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2020-10-13 11:49:46.168027",
+ "modified": "2020-08-27 14:30:28.995324",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 007497e..4b31501 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-02-20 19:10:38",
@@ -24,6 +25,7 @@
"compensatory_request",
"leave_period",
"leave_policy",
+ "leave_policy_assignment",
"carry_forwarded_leaves_count",
"expired",
"amended_from",
@@ -160,9 +162,10 @@
"read_only": 1
},
{
- "fetch_from": "employee.leave_policy",
+ "fetch_from": "leave_policy_assignment.leave_policy",
"fieldname": "leave_policy",
"fieldtype": "Link",
+ "hidden": 1,
"in_standard_filter": 1,
"label": "Leave Policy",
"options": "Leave Policy",
@@ -209,12 +212,21 @@
"fieldtype": "Float",
"label": "Carry Forwarded Leaves",
"read_only": 1
+ },
+ {
+ "fieldname": "leave_policy_assignment",
+ "fieldtype": "Link",
+ "label": "Leave Policy Assignment",
+ "options": "Leave Policy Assignment",
+ "read_only": 1
}
],
"icon": "fa fa-ok",
"idx": 1,
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
- "modified": "2019-08-08 15:08:42.440909",
+ "links": [],
+ "modified": "2020-08-20 14:25:10.314323",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 03fe3fa..5e3822e 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -51,9 +51,19 @@
def on_cancel(self):
self.create_leave_ledger_entry(submit=False)
+ if self.leave_policy_assignment:
+ self.update_leave_policy_assignments_when_no_allocations_left()
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True)
+ def update_leave_policy_assignments_when_no_allocations_left(self):
+ allocations = frappe.db.get_list("Leave Allocation", filters = {
+ "docstatus": 1,
+ "leave_policy_assignment": self.leave_policy_assignment
+ })
+ if len(allocations) == 0:
+ frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
+
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
frappe.throw(_("To date cannot be before from date"))
@@ -82,7 +92,7 @@
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') + ': <a href="#Form/Leave Allocation/{0}">{0}</a>'
+ frappe.throw(_('Reference') + ': <a href="/app/Form/Leave Allocation/{0}">{0}</a>'
.format(leave_allocation[0][0]), OverlapError)
def validate_back_dated_allocation(self):
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 3f25f58..132c3bd 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -130,8 +130,7 @@
if self.status == "Approved":
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime("%Y-%m-%d")
- status = "Half Day" if getdate(date) == getdate(self.half_day_date) else "On Leave"
-
+ status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
attendance_date = date, docstatus = ('!=', 2)))
@@ -246,7 +245,7 @@
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'])) \
- + """ <b><a href="#Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
+ + """ <b><a href="/app/Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):
@@ -293,7 +292,8 @@
def set_half_day_date(self):
if self.from_date == self.to_date and self.half_day == 1:
self.half_day_date = self.from_date
- elif self.half_day == 0:
+
+ if self.half_day == 0:
self.half_day_date = None
def notify_employee(self):
@@ -376,24 +376,32 @@
if expiry_date:
self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp)
else:
+ raise_exception = True
+ if frappe.flags.in_patch:
+ raise_exception=False
+
args = dict(
leaves=self.total_leave_days * -1,
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee)
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
)
create_leave_ledger_entry(self, args, submit)
def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
''' splits leave application into two ledger entries to consider expiry of allocation '''
+
+ raise_exception = True
+ if frappe.flags.in_patch:
+ raise_exception=False
+
args = dict(
from_date=self.from_date,
to_date=expiry_date,
leaves=(date_diff(expiry_date, self.from_date) + 1) * -1,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee),
-
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
)
create_leave_ledger_entry(self, args, submit)
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 6e909c3..53b7a39 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -10,6 +10,7 @@
from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
test_dependencies = ["Leave Allocation", "Leave Block List"]
@@ -410,25 +411,39 @@
self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21)
def test_earned_leaves_creation(self):
+
+ frappe.db.sql('''delete from `tabLeave Period`''')
+ frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
+ frappe.db.sql('''delete from `tabLeave Allocation`''')
+ frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
+
leave_period = get_leave_period()
employee = get_employee()
leave_type = 'Test Earned Leave Type'
- if not frappe.db.exists('Leave Type', leave_type):
- frappe.get_doc(dict(
- leave_type_name = leave_type,
- doctype = 'Leave Type',
- is_earned_leave = 1,
- earned_leave_frequency = 'Monthly',
- rounding = 0.5,
- max_leaves_allowed = 6
- )).insert()
+ frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
+ frappe.get_doc(dict(
+ leave_type_name = leave_type,
+ doctype = 'Leave Type',
+ is_earned_leave = 1,
+ earned_leave_frequency = 'Monthly',
+ rounding = 0.5,
+ max_leaves_allowed = 6
+ )).insert()
+
leave_policy = frappe.get_doc({
"doctype": "Leave Policy",
"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
}).insert()
- frappe.db.set_value("Employee", employee.name, "leave_policy", leave_policy.name)
- allocate_leaves(employee, leave_period, leave_type, 0, eligible_leaves = 12)
+ data = {
+ "assignment_based_on": "Leave Period",
+ "leave_policy": leave_policy.name,
+ "leave_period": leave_period.name
+ }
+
+ leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+ frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
from erpnext.hr.utils import allocate_earned_leaves
i = 0
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.js b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
index 71a3422..81936a4 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.js
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
@@ -22,7 +22,12 @@
}
},
employee: function(frm) {
- frm.trigger("get_leave_details_for_encashment");
+ if (frm.doc.employee) {
+ frappe.run_serially([
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('get_leave_details_for_encashment')
+ ]);
+ }
},
leave_type: function(frm) {
frm.trigger("get_leave_details_for_encashment");
@@ -40,5 +45,20 @@
}
});
}
- }
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ },
});
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index 2cf6ccf..83eeae3 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -12,6 +12,7 @@
"employee",
"employee_name",
"department",
+ "company",
"column_break_4",
"leave_type",
"leave_allocation",
@@ -19,9 +20,11 @@
"encashable_days",
"amended_from",
"payroll",
- "encashment_amount",
"encashment_date",
- "additional_salary"
+ "additional_salary",
+ "column_break_14",
+ "currency",
+ "encashment_amount"
],
"fields": [
{
@@ -109,6 +112,7 @@
"in_list_view": 1,
"label": "Encashment Amount",
"no_copy": 1,
+ "options": "currency",
"read_only": 1
},
{
@@ -124,11 +128,34 @@
"no_copy": 1,
"options": "Additional Salary",
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2019-12-16 11:51:57.732223",
+ "modified": "2020-11-25 11:56:06.777241",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index c1dcc97..4c1a465 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -16,10 +16,16 @@
def validate(self):
set_employee_name(self)
self.get_leave_details_for_encashment()
+ self.validate_salary_structure()
if not self.encashment_date:
self.encashment_date = getdate(nowdate())
+ def validate_salary_structure(self):
+ if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+ frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
+
def before_submit(self):
if self.encashment_amount <= 0:
frappe.throw(_("You can only submit Leave Encashment for a valid encashment amount"))
@@ -30,6 +36,7 @@
additional_salary = frappe.new_doc("Additional Salary")
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
additional_salary.employee = self.employee
+ additional_salary.currency = self.currency
earning_component = frappe.get_value("Leave Type", self.leave_type, "earning_component")
if not earning_component:
frappe.throw(_("Please set Earning Component for Leave type: {0}.").format(self.leave_type))
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 99f6463..aafc964 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -9,6 +9,7 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\
test_dependencies = ["Leave Type"]
@@ -16,6 +17,7 @@
class TestLeaveEncashment(unittest.TestCase):
def setUp(self):
frappe.db.sql('''delete from `tabLeave Period`''')
+ frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
frappe.db.sql('''delete from `tabLeave Allocation`''')
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
frappe.db.sql('''delete from `tabAdditional Salary`''')
@@ -29,14 +31,26 @@
# create employee, salary structure and assignment
self.employee = make_employee("test_employee_encashment@example.com")
- frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name)
+ self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
+
+ data = {
+ "assignment_based_on": "Leave Period",
+ "leave_policy": leave_policy.name,
+ "leave_period": self.leave_period.name
+ }
+
+ leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
other_details={"leave_encashment_amount_per_day": 50})
- # create the leave period and assign the leaves
- self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
- self.leave_period.grant_leave_allocation(employee=self.employee)
+ #grant Leaves
+ frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0]).grant_leave_alloc_for_employee()
+
+
+ def tearDown(self):
+ for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
+ frappe.db.sql("delete from `tab%s`" % dt)
def test_leave_balance_value_and_amount(self):
frappe.db.sql('''delete from `tabLeave Encashment`''')
@@ -45,7 +59,8 @@
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
- payroll_date=today()
+ payroll_date=today(),
+ currency="INR"
)).insert()
self.assertEqual(leave_encashment.leave_balance, 10)
@@ -65,7 +80,8 @@
employee=self.employee,
leave_type="_Test Leave Type Encashment",
leave_period=self.leave_period.name,
- payroll_date=today()
+ payroll_date=today(),
+ currency="INR"
)).insert()
leave_encashment.submit()
diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js
index bad2b87..0e88bc1 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.js
+++ b/erpnext/hr/doctype/leave_period/leave_period.js
@@ -2,14 +2,6 @@
// For license information, please see license.txt
frappe.ui.form.on('Leave Period', {
- refresh: (frm)=>{
- frm.set_df_property("grant_leaves", "hidden", frm.doc.__islocal ? 1:0);
- if(!frm.is_new()) {
- frm.add_custom_button(__('Grant Leaves'), function () {
- frm.trigger("grant_leaves");
- });
- }
- },
from_date: (frm)=>{
if (frm.doc.from_date && !frm.doc.to_date) {
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
@@ -22,73 +14,7 @@
"filters": {
"company": frm.doc.company,
}
- }
- })
- },
- grant_leaves: function(frm) {
- var d = new frappe.ui.Dialog({
- title: __('Grant Leaves'),
- fields: [
- {
- "label": "Filter Employees By (Optional)",
- "fieldname": "sec_break",
- "fieldtype": "Section Break",
- },
- {
- "label": "Employee Grade",
- "fieldname": "grade",
- "fieldtype": "Link",
- "options": "Employee Grade"
- },
- {
- "label": "Department",
- "fieldname": "department",
- "fieldtype": "Link",
- "options": "Department"
- },
- {
- "fieldname": "col_break",
- "fieldtype": "Column Break",
- },
- {
- "label": "Designation",
- "fieldname": "designation",
- "fieldtype": "Link",
- "options": "Designation"
- },
- {
- "label": "Employee",
- "fieldname": "employee",
- "fieldtype": "Link",
- "options": "Employee"
- },
- {
- "fieldname": "sec_break",
- "fieldtype": "Section Break",
- },
- {
- "label": "Add unused leaves from previous allocations",
- "fieldname": "carry_forward",
- "fieldtype": "Check"
- }
- ],
- primary_action: function() {
- var data = d.get_values();
-
- frappe.call({
- doc: frm.doc,
- method: "grant_leave_allocation",
- args: data,
- callback: function(r) {
- if(!r.exc) {
- d.hide();
- frm.reload_doc();
- }
- }
- });
- },
- primary_action_label: __('Grant')
+ };
});
- d.show();
- }
+ },
});
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index 0973ac7..28a33f6 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -7,24 +7,10 @@
from frappe import _
from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil
from frappe.model.document import Document
-from erpnext.hr.utils import validate_overlap, get_employee_leave_policy
+from erpnext.hr.utils import validate_overlap
from frappe.utils.background_jobs import enqueue
-from six import iteritems
class LeavePeriod(Document):
- def get_employees(self, args):
- conditions, values = [], []
- for field, value in iteritems(args):
- if value:
- conditions.append("{0}=%s".format(field))
- values.append(value)
-
- condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
-
- employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec
- .format(condition=condition_str), tuple(values)))
-
- return employees
def validate(self):
self.validate_dates()
@@ -33,96 +19,3 @@
def validate_dates(self):
if getdate(self.from_date) >= getdate(self.to_date):
frappe.throw(_("To date can not be equal or less than from date"))
-
-
- def grant_leave_allocation(self, grade=None, department=None, designation=None,
- employee=None, carry_forward=0):
- employee_records = self.get_employees({
- "grade": grade,
- "department": department,
- "designation": designation,
- "name": employee
- })
-
- if employee_records:
- if len(employee_records) > 20:
- frappe.enqueue(grant_leave_alloc_for_employees, timeout=600,
- employee_records=employee_records, leave_period=self, carry_forward=carry_forward)
- else:
- grant_leave_alloc_for_employees(employee_records, self, carry_forward)
- else:
- frappe.msgprint(_("No Employee Found"))
-
-def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0):
- leave_allocations = []
- existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name)
- leave_type_details = get_leave_type_details()
- count = 0
- for employee in employee_records.keys():
- if employee in existing_allocations_for:
- continue
- count +=1
- leave_policy = get_employee_leave_policy(employee)
- if leave_policy:
- for leave_policy_detail in leave_policy.leave_policy_details:
- if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
- leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type,
- leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee))
- leave_allocations.append(leave_allocation)
- frappe.db.commit()
- frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves..."))
-
- if leave_allocations:
- frappe.msgprint(_("Leaves has been granted sucessfully"))
-
-def get_existing_allocations(employees, leave_period):
- leave_allocations = frappe.db.sql_list("""
- SELECT DISTINCT
- employee
- FROM `tabLeave Allocation`
- WHERE
- leave_period=%s
- AND employee in (%s)
- AND carry_forward=0
- AND docstatus=1
- """ % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees)
- if leave_allocations:
- frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}")
- .format("\n".join(leave_allocations)))
- return leave_allocations
-
-def get_leave_type_details():
- leave_type_details = frappe._dict()
- leave_types = frappe.get_all("Leave Type",
- fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
- for d in leave_types:
- leave_type_details.setdefault(d.name, d)
- return leave_type_details
-
-def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining):
- ''' Creates leave allocation for the given employee in the provided leave period '''
- if carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
- carry_forward = 0
-
- # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
- if getdate(date_of_joining) > getdate(leave_period.from_date):
- remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1))
- new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
-
- # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
- if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
- new_leaves_allocated = 0
-
- allocation = frappe.get_doc(dict(
- doctype="Leave Allocation",
- employee=employee,
- leave_type=leave_type,
- from_date=leave_period.from_date,
- to_date=leave_period.to_date,
- new_leaves_allocated=new_leaves_allocated,
- leave_period=leave_period.name,
- carry_forward=carry_forward
- ))
- allocation.save(ignore_permissions = True)
- allocation.submit()
- return allocation.name
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 1762cf9..b5857bc 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -5,43 +5,11 @@
import frappe, erpnext
import unittest
-from frappe.utils import today, add_months
-from erpnext.hr.doctype.employee.test_employee import make_employee
-from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
class TestLeavePeriod(unittest.TestCase):
- def setUp(self):
- frappe.db.sql("delete from `tabLeave Period`")
-
- def test_leave_grant(self):
- leave_type = "_Test Leave Type"
-
- # create the leave policy
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "leave_policy_details": [{
- "leave_type": leave_type,
- "annual_allocation": 20
- }]
- }).insert()
- leave_policy.submit()
-
- # create employee and assign the leave period
- employee = "test_leave_period@employee.com"
- employee_doc_name = make_employee(employee)
- frappe.db.set_value("Employee", employee_doc_name, "leave_policy", leave_policy.name)
-
- # clear the already allocated leave
- frappe.db.sql('''delete from `tabLeave Allocation` where employee=%s''', "test_leave_period@employee.com")
-
- # create the leave period
- leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
-
- # test leave_allocation
- leave_period.grant_leave_allocation(employee=employee_doc_name)
- self.assertEqual(get_leave_balance_on(employee_doc_name, leave_type, today()), 20)
+ pass
def create_leave_period(from_date, to_date, company=None):
leave_period = frappe.db.get_value('Leave Period',
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/hr/doctype/leave_policy_assignment/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/hr/doctype/leave_policy_assignment/__init__.py
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.js
new file mode 100644
index 0000000..7c32a0d
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.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('Leave Policy Assignment', {
+ onload: function(frm) {
+ frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+ },
+
+ refresh: function(frm) {
+ if (frm.doc.docstatus === 1 && frm.doc.leaves_allocated === 0) {
+ frm.add_custom_button(__("Grant Leave"), function() {
+
+ frappe.call({
+ doc: frm.doc,
+ method: "grant_leave_alloc_for_employee",
+ callback: function(r) {
+ let leave_allocations = r.message;
+ let msg = frm.events.get_success_message(leave_allocations);
+ frappe.msgprint(msg);
+ cur_frm.refresh();
+ }
+ });
+ });
+ }
+ },
+
+ get_success_message: function(leave_allocations) {
+ let msg = __("Leaves has been granted successfully");
+ msg += "<br><table class='table table-bordered'>";
+ msg += "<tr><th>"+__('Leave Type')+"</th><th>"+__("Leave Allocation")+"</th><th>"+__("Leaves Granted")+"</th><tr>";
+ for (let key in leave_allocations) {
+ msg += "<tr><th>"+key+"</th><td>"+leave_allocations[key]["name"]+"</td><td>"+leave_allocations[key]["leaves"]+"</td></tr>";
+ }
+ msg += "</table>";
+ return msg;
+ },
+
+ assignment_based_on: function(frm) {
+ if (frm.doc.assignment_based_on) {
+ frm.events.set_effective_date(frm);
+ } else {
+ frm.set_value("effective_from", '');
+ frm.set_value("effective_to", '');
+ }
+ },
+
+ leave_period: function(frm) {
+ if (frm.doc.leave_period) {
+ frm.events.set_effective_date(frm);
+ }
+ },
+
+ set_effective_date: function(frm) {
+ if (frm.doc.assignment_based_on == "Leave Period" && frm.doc.leave_period) {
+ frappe.model.with_doc("Leave Period", frm.doc.leave_period, function () {
+ let from_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "from_date");
+ let to_date = frappe.model.get_value("Leave Period", frm.doc.leave_period, "to_date");
+ frm.set_value("effective_from", from_date);
+ frm.set_value("effective_to", to_date);
+
+ });
+ } else if (frm.doc.assignment_based_on == "Joining Date" && frm.doc.employee) {
+ frappe.model.with_doc("Employee", frm.doc.employee, function () {
+ let from_date = frappe.model.get_value("Employee", frm.doc.employee, "date_of_joining");
+ frm.set_value("effective_from", from_date);
+ frm.set_value("effective_to", frappe.datetime.add_months(frm.doc.effective_from, 12));
+ });
+ }
+ frm.refresh();
+ }
+
+});
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
new file mode 100644
index 0000000..ecebb3b
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -0,0 +1,160 @@
+{
+ "actions": [],
+ "autoname": "HR-LPOL-ASSGN-.#####",
+ "creation": "2020-08-19 13:02:43.343666",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "employee",
+ "employee_name",
+ "company",
+ "leave_policy",
+ "carry_forward",
+ "column_break_5",
+ "assignment_based_on",
+ "leave_period",
+ "effective_from",
+ "effective_to",
+ "leaves_allocated",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "label": "Employee name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "leave_policy",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Leave Policy",
+ "options": "Leave Policy",
+ "reqd": 1
+ },
+ {
+ "fieldname": "assignment_based_on",
+ "fieldtype": "Select",
+ "label": "Assignment based on",
+ "options": "\nLeave Period\nJoining Date"
+ },
+ {
+ "depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+ "fieldname": "leave_period",
+ "fieldtype": "Link",
+ "label": "Leave Period",
+ "mandatory_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+ "options": "Leave Period"
+ },
+ {
+ "fieldname": "effective_from",
+ "fieldtype": "Date",
+ "label": "Effective From",
+ "read_only_depends_on": "eval:doc.assignment_based_on",
+ "reqd": 1
+ },
+ {
+ "fieldname": "effective_to",
+ "fieldtype": "Date",
+ "label": "Effective To",
+ "read_only_depends_on": "eval:doc.assignment_based_on == \"Leave Period\"",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Leave Policy Assignment",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "carry_forward",
+ "fieldtype": "Check",
+ "label": "Add unused leaves from previous allocations"
+ },
+ {
+ "default": "0",
+ "fieldname": "leaves_allocated",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Leaves Allocated"
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-10-15 15:18:15.227848",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Leave Policy Assignment",
+ "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
+ },
+ {
+ "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/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
new file mode 100644
index 0000000..a5068bc
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -0,0 +1,163 @@
+# -*- 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 _, bold
+from frappe.utils import getdate, date_diff, comma_and, formatdate
+from math import ceil
+import json
+from six import string_types
+
+class LeavePolicyAssignment(Document):
+
+ def validate(self):
+ self.validate_policy_assignment_overlap()
+ self.set_dates()
+
+ def set_dates(self):
+ if self.assignment_based_on == "Leave Period":
+ self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
+ elif self.assignment_based_on == "Joining Date":
+ self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+ def validate_policy_assignment_overlap(self):
+ leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
+ "employee": self.employee,
+ "name": ("!=", self.name),
+ "docstatus": 1,
+ "effective_to": (">=", self.effective_from),
+ "effective_from": ("<=", self.effective_to)
+ })
+
+ if len(leave_policy_assignments):
+ frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
+ .format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+
+ def grant_leave_alloc_for_employee(self):
+ if self.leaves_allocated:
+ frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
+ else:
+ leave_allocations = {}
+ leave_type_details = get_leave_type_details()
+
+ leave_policy = frappe.get_doc("Leave Policy", self.leave_policy)
+ date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
+
+ for leave_policy_detail in leave_policy.leave_policy_details:
+ if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
+ leave_allocation, new_leaves_allocated = self.create_leave_allocation(
+ leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
+ leave_type_details, date_of_joining
+ )
+
+ leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
+
+ self.db_set("leaves_allocated", 1)
+ return leave_allocations
+
+ def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ # Creates leave allocation for the given employee in the provided leave period
+ carry_forward = self.carry_forward
+ if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
+ carry_forward = 0
+
+ new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
+ leave_type_details, date_of_joining)
+
+ allocation = frappe.get_doc(dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ leave_type=leave_type,
+ from_date=self.effective_from,
+ to_date=self.effective_to,
+ new_leaves_allocated=new_leaves_allocated,
+ leave_period=self.leave_period or None,
+ leave_policy_assignment = self.name,
+ leave_policy = self.leave_policy,
+ carry_forward=carry_forward
+ ))
+ allocation.save(ignore_permissions = True)
+ allocation.submit()
+ return allocation.name, new_leaves_allocated
+
+ def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
+ if getdate(date_of_joining) > getdate(self.effective_from):
+ remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
+ new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
+
+ # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
+ if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1:
+ new_leaves_allocated = 0
+
+ return new_leaves_allocated
+
+@frappe.whitelist()
+def grant_leave_for_multiple_employees(leave_policy_assignments):
+ leave_policy_assignments = json.loads(leave_policy_assignments)
+ not_granted = []
+ for assignment in leave_policy_assignments:
+ try:
+ frappe.get_doc("Leave Policy Assignment", assignment).grant_leave_alloc_for_employee()
+ except Exception:
+ not_granted.append(assignment)
+
+ if len(not_granted):
+ msg = _("Leave not Granted for Assignments:")+ bold(comma_and(not_granted)) + _(". Please Check documents")
+ else:
+ msg = _("Leave granted Successfully")
+ frappe.msgprint(msg)
+
+@frappe.whitelist()
+def create_assignment_for_multiple_employees(employees, data):
+
+ if isinstance(employees, string_types):
+ employees= json.loads(employees)
+
+ if isinstance(data, string_types):
+ data = frappe._dict(json.loads(data))
+
+ docs_name = []
+ for employee in employees:
+ assignment = frappe.new_doc("Leave Policy Assignment")
+ assignment.employee = employee
+ assignment.assignment_based_on = data.assignment_based_on or None
+ assignment.leave_policy = data.leave_policy
+ assignment.effective_from = getdate(data.effective_from) or None
+ assignment.effective_to = getdate(data.effective_to) or None
+ assignment.leave_period = data.leave_period or None
+ assignment.carry_forward = data.carry_forward
+
+ assignment.save()
+ assignment.submit()
+ docs_name.append(assignment.name)
+ return docs_name
+
+
+def automatically_allocate_leaves_based_on_leave_policy():
+ today = getdate()
+ automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_single_value(
+ 'HR Settings', 'automatically_allocate_leaves_based_on_leave_policy'
+ )
+
+ pending_assignments = frappe.get_list(
+ "Leave Policy Assignment",
+ filters = {"docstatus": 1, "leaves_allocated": 0, "effective_from": today}
+ )
+
+ if len(pending_assignments) and automatically_allocate_leaves_based_on_leave_policy:
+ for assignment in pending_assignments:
+ frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
+
+
+def get_leave_type_details():
+ leave_type_details = frappe._dict()
+ leave_types = frappe.get_all("Leave Type",
+ fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"])
+ for d in leave_types:
+ leave_type_details.setdefault(d.name, d)
+ return leave_type_details
+
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
new file mode 100644
index 0000000..468f243
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_list.js
@@ -0,0 +1,138 @@
+frappe.listview_settings['Leave Policy Assignment'] = {
+ onload: function (list_view) {
+ let me = this;
+ list_view.page.add_inner_button(__("Bulk Leave Policy Assignment"), function () {
+ me.dialog = new frappe.ui.form.MultiSelectDialog({
+ doctype: "Employee",
+ target: cur_list,
+ setters: {
+ company: '',
+ department: '',
+ },
+ data_fields: [{
+ fieldname: 'leave_policy',
+ fieldtype: 'Link',
+ options: 'Leave Policy',
+ label: __('Leave Policy'),
+ reqd: 1
+ },
+ {
+ fieldname: 'assignment_based_on',
+ fieldtype: 'Select',
+ options: ["", "Leave Period"],
+ label: __('Assignment Based On'),
+ onchange: () => {
+ if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period") {
+ cur_dialog.set_df_property("effective_from", "read_only", 1);
+ cur_dialog.set_df_property("leave_period", "reqd", 1);
+ cur_dialog.set_df_property("effective_to", "read_only", 1);
+ } else {
+ cur_dialog.set_df_property("effective_from", "read_only", 0);
+ cur_dialog.set_df_property("leave_period", "reqd", 0);
+ cur_dialog.set_df_property("effective_to", "read_only", 0);
+ cur_dialog.set_value("effective_from", "");
+ cur_dialog.set_value("effective_to", "");
+ }
+ }
+ },
+ {
+ fieldname: "leave_period",
+ fieldtype: 'Link',
+ options: "Leave Period",
+ label: __('Leave Period'),
+ depends_on: doc => {
+ return doc.assignment_based_on == 'Leave Period';
+ },
+ onchange: () => {
+ if (cur_dialog.fields_dict.leave_period.value) {
+ me.set_effective_date();
+ }
+ }
+ },
+ {
+ fieldtype: "Column Break"
+ },
+ {
+ fieldname: 'effective_from',
+ fieldtype: 'Date',
+ label: __('Effective From'),
+ reqd: 1
+ },
+ {
+ fieldname: 'effective_to',
+ fieldtype: 'Date',
+ label: __('Effective To'),
+ reqd: 1
+ },
+ {
+ fieldname: 'carry_forward',
+ fieldtype: 'Check',
+ label: __('Add unused leaves from previous allocations')
+ }
+ ],
+ get_query() {
+ return {
+ filters: {
+ status: ['=', 'Active']
+ }
+ };
+ },
+ add_filters_group: 1,
+ primary_action_label: "Assign",
+ action(employees, data) {
+ frappe.call({
+ method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.create_assignment_for_multiple_employees',
+ async: false,
+ args: {
+ employees: employees,
+ data: data
+ }
+ });
+ cur_dialog.hide();
+ }
+ });
+ });
+
+ list_view.page.add_inner_button(__("Grant Leaves"), function () {
+ me.dialog = new frappe.ui.form.MultiSelectDialog({
+ doctype: "Leave Policy Assignment",
+ target: cur_list,
+ setters: {
+ company: '',
+ employee: '',
+ },
+ get_query() {
+ return {
+ filters: {
+ docstatus: ['=', 1],
+ leaves_allocated: ['=', 0]
+ }
+ };
+ },
+ add_filters_group: 1,
+ primary_action_label: "Grant Leaves",
+ action(leave_policy_assignments) {
+ frappe.call({
+ method: 'erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.grant_leave_for_multiple_employees',
+ async: false,
+ args: {
+ leave_policy_assignments: leave_policy_assignments
+ }
+ });
+ me.dialog.hide();
+ }
+ });
+ });
+ },
+
+ set_effective_date: function () {
+ if (cur_dialog.fields_dict.assignment_based_on.value === "Leave Period" && cur_dialog.fields_dict.leave_period.value) {
+ frappe.model.with_doc("Leave Period", cur_dialog.fields_dict.leave_period.value, function () {
+ let from_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "from_date");
+ let to_date = frappe.model.get_value("Leave Period", cur_dialog.fields_dict.leave_period.value, "to_date");
+ cur_dialog.set_value("effective_from", from_date);
+ cur_dialog.set_value("effective_to", to_date);
+ });
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
new file mode 100644
index 0000000..c7bc6fb
--- /dev/null
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -0,0 +1,103 @@
+# -*- 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.leave_application.test_leave_application import get_leave_period, get_employee
+from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
+from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
+
+class TestLeavePolicyAssignment(unittest.TestCase):
+
+ def setUp(self):
+ for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+ frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+ def test_grant_leaves(self):
+ leave_period = get_leave_period()
+ employee = get_employee()
+
+ # create the leave policy with leave type "_Test Leave Type", allocation = 10
+ leave_policy = create_leave_policy()
+ leave_policy.submit()
+
+
+ data = {
+ "assignment_based_on": "Leave Period",
+ "leave_policy": leave_policy.name,
+ "leave_period": leave_period.name
+ }
+
+ leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+ leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+ leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+ leave_policy_assignment_doc.reload()
+
+ self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+ leave_allocation = frappe.get_list("Leave Allocation", filters={
+ "employee": employee.name,
+ "leave_policy":leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1})[0]
+
+ leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+ self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
+ self.assertEqual(leave_alloc_doc.leave_type, "_Test Leave Type")
+ self.assertEqual(leave_alloc_doc.from_date, leave_period.from_date)
+ self.assertEqual(leave_alloc_doc.to_date, leave_period.to_date)
+ self.assertEqual(leave_alloc_doc.leave_policy, leave_policy.name)
+ self.assertEqual(leave_alloc_doc.leave_policy_assignment, leave_policy_assignments[0])
+
+ def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
+ leave_period = get_leave_period()
+ employee = get_employee()
+
+ # create the leave policy with leave type "_Test Leave Type", allocation = 10
+ leave_policy = create_leave_policy()
+ leave_policy.submit()
+
+
+ data = {
+ "assignment_based_on": "Leave Period",
+ "leave_policy": leave_policy.name,
+ "leave_period": leave_period.name
+ }
+
+ leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+
+ leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
+ leave_policy_assignment_doc.grant_leave_alloc_for_employee()
+ leave_policy_assignment_doc.reload()
+
+
+ # every leave is allocated no more leave can be granted now
+ self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+
+ leave_allocation = frappe.get_list("Leave Allocation", filters={
+ "employee": employee.name,
+ "leave_policy":leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1})[0]
+
+ leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
+
+ # User all allowed to grant leave when there is no allocation against assignment
+ leave_alloc_doc.cancel()
+ leave_alloc_doc.delete()
+
+ leave_policy_assignment_doc.reload()
+
+
+ # User are now allowed to grant leave
+ self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
+
+ def tearDown(self):
+ for doctype in ["Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+ frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+
+
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index 0af832f..a209291 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -15,6 +15,8 @@
"column_break_3",
"is_carry_forward",
"is_lwp",
+ "is_ppl",
+ "fraction_of_daily_salary_per_leave",
"is_optional_leave",
"allow_negative",
"include_holiday",
@@ -31,6 +33,7 @@
"is_earned_leave",
"earned_leave_frequency",
"column_break_22",
+ "based_on_date_of_joining",
"rounding"
],
"fields": [
@@ -77,6 +80,7 @@
},
{
"default": "0",
+ "depends_on": "eval:doc.is_ppl == 0",
"fieldname": "is_lwp",
"fieldtype": "Check",
"label": "Is Leave Without Pay"
@@ -183,12 +187,33 @@
{
"fieldname": "column_break_22",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.is_earned_leave",
+ "description": "If checked, leave will be granted on the day of joining every month.",
+ "fieldname": "based_on_date_of_joining",
+ "fieldtype": "Check",
+ "label": "Based On Date Of Joining"
+ },
+ {
+ "depends_on": "eval:doc.is_lwp == 0",
+ "fieldname": "is_ppl",
+ "fieldtype": "Check",
+ "label": "Is Partially Paid Leave"
+ },
+ {
+ "depends_on": "eval:doc.is_ppl == 1",
+ "fieldname": "fraction_of_daily_salary_per_leave",
+ "fieldtype": "Float",
+ "label": "Fraction of Daily Salary per Leave",
+ "mandatory_depends_on": "eval:doc.is_ppl == 1"
}
],
"icon": "fa fa-flag",
"idx": 1,
"links": [],
- "modified": "2019-12-12 12:48:37.780254",
+ "modified": "2020-10-15 15:49:47.555105",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Type",
diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py
index c0d1296..21f180b 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.py
+++ b/erpnext/hr/doctype/leave_type/leave_type.py
@@ -21,3 +21,9 @@
leave_allocation = [l['name'] for l in leave_allocation]
if leave_allocation:
frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
+
+ if self.is_lwp and self.is_ppl:
+ frappe.throw(_("Leave Type can be either without pay or partial pay"))
+
+ if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1):
+ frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index 0c4f435..7fef297 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -18,9 +18,14 @@
"allow_encashment": args.allow_encashment or 0,
"is_earned_leave": args.is_earned_leave or 0,
"is_lwp": args.is_lwp or 0,
+ "is_ppl":args.is_ppl or 0,
"is_carry_forward": args.is_carry_forward or 0,
"expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
"encashment_threshold_days": args.encashment_threshold_days or 5,
"earning_component": "Leave Encashment"
})
+
+ if leave_type.is_ppl:
+ leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
+
return leave_type
\ No newline at end of file
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 1c2801b..473193d 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -87,5 +87,5 @@
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'])) \
- + """ <b><a href="#Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
+ + """ <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
frappe.throw(msg, OverlapError)
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 8d95924..e2aa7a4 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -211,23 +211,10 @@
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)) \
- + """ <b><a href="#Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc) \
+ + """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc) \
+ _(") for {0}").format(exists_for)
frappe.throw(msg)
-def get_employee_leave_policy(employee):
- leave_policy = frappe.db.get_value("Employee", employee, "leave_policy")
- if not leave_policy:
- employee_grade = frappe.db.get_value("Employee", employee, "grade")
- if employee_grade:
- leave_policy = frappe.db.get_value("Employee Grade", employee_grade, "default_leave_policy")
- if not leave_policy:
- frappe.throw(_("Employee {0} of grade {1} have no default leave policy").format(employee, employee_grade))
- if leave_policy:
- return frappe.get_doc("Leave Policy", leave_policy)
- else:
- frappe.throw(_("Please set leave policy for employee {0} in Employee / Grade record").format(employee))
-
def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
existing_record = frappe.db.exists(doctype, {
"payroll_period": payroll_period,
@@ -300,43 +287,68 @@
def allocate_earned_leaves():
'''Allocate earned leaves to Employees'''
- e_leave_types = frappe.get_all("Leave Type",
- fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"],
- filters={'is_earned_leave' : 1})
+ e_leave_types = get_earned_leaves()
today = getdate()
- divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
for e_leave_type in e_leave_types:
- leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s
- between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1)
+
+ leave_allocations = get_leave_allocations(today, e_leave_type.name)
+
for allocation in leave_allocations:
- leave_policy = get_employee_leave_policy(allocation.employee)
- if not leave_policy:
+
+ if not allocation.leave_policy_assignment and not allocation.leave_policy:
continue
- if not e_leave_type.earned_leave_frequency == "Monthly":
- if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency):
- continue
+
+ leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
+ "Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
+
annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
- 'parent': leave_policy.name,
+ 'parent': leave_policy,
'leave_type': e_leave_type.name
}, fieldname=['annual_allocation'])
- if annual_allocation:
- earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
- if e_leave_type.rounding == "0.5":
- earned_leaves = round(earned_leaves * 2) / 2
- else:
- earned_leaves = round(earned_leaves)
- allocation = frappe.get_doc('Leave Allocation', allocation.name)
- new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+ from_date=allocation.from_date
- if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
- new_allocation = e_leave_type.max_leaves_allowed
+ if e_leave_type.based_on_date_of_joining_date:
+ from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
- if new_allocation == allocation.total_leaves_allocated:
- continue
- allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
- create_additional_leave_ledger_entry(allocation, earned_leaves, today)
+ if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
+ update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
+
+def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
+ divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12}
+ if annual_allocation:
+ earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency]
+ if e_leave_type.rounding == "0.5":
+ earned_leaves = round(earned_leaves * 2) / 2
+ else:
+ earned_leaves = round(earned_leaves)
+
+ allocation = frappe.get_doc('Leave Allocation', allocation.name)
+ new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+
+ if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
+ new_allocation = e_leave_type.max_leaves_allowed
+
+ if new_allocation != allocation.total_leaves_allocated:
+ allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+ today_date = today()
+ create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+
+
+def get_leave_allocations(date, leave_type):
+ return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
+ from `tabLeave Allocation`
+ where
+ %s between from_date and to_date and docstatus=1
+ and leave_type=%s""",
+ (date, leave_type), as_dict=1)
+
+
+def get_earned_leaves():
+ return frappe.get_all("Leave Type",
+ fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
+ filters={'is_earned_leave' : 1})
def create_additional_leave_ledger_entry(allocation, leaves, date):
''' Create leave ledger entry for leave types '''
@@ -345,24 +357,32 @@
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
-def check_frequency_hit(from_date, to_date, frequency):
- '''Return True if current date matches frequency'''
- from_dt = get_datetime(from_date)
- to_dt = get_datetime(to_date)
+def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
+ import calendar
from dateutil import relativedelta
- rd = relativedelta.relativedelta(to_dt, from_dt)
- months = rd.months
- if frequency == "Quarterly":
- if not months % 3:
+
+ from_date = get_datetime(from_date)
+ to_date = get_datetime(to_date)
+ rd = relativedelta.relativedelta(to_date, from_date)
+ #last day of month
+ last_day = calendar.monthrange(to_date.year, to_date.month)[1]
+
+ if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
+ if frequency == "Monthly":
return True
- elif frequency == "Half-Yearly":
- if not months % 6:
+ elif frequency == "Quarterly" and rd.months % 3:
return True
- elif frequency == "Yearly":
- if not months % 12:
+ elif frequency == "Half-Yearly" and rd.months % 6:
return True
+ elif frequency == "Yearly" and rd.months % 12:
+ return True
+
+ if frappe.flags.in_test:
+ return True
+
return False
+
def get_salary_assignment(employee, date):
assignment = frappe.db.sql("""
select * from `tabSalary Structure Assignment`
@@ -454,3 +474,10 @@
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
+
+def grant_leaves_automatically():
+ automatically_allocate_leaves_based_on_leave_policy = frappe.db.get_singles_value("HR Settings", "automatically_allocate_leaves_based_on_leave_policy")
+ if automatically_allocate_leaves_based_on_leave_policy:
+ lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
+ for assignment in lpa:
+ frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
new file mode 100644
index 0000000..7f1af84
--- /dev/null
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -0,0 +1,946 @@
+{
+ "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": "Payroll",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Salary Structure",
+ "link_to": "Salary Structure",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Salary Structure, Employee",
+ "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"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Payroll Period",
+ "link_to": "Payroll Period",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Income Tax Slab",
+ "link_to": "Income Tax Slab",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Salary Component",
+ "link_to": "Salary Component",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Additional Salary",
+ "link_to": "Additional Salary",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Retention Bonus",
+ "link_to": "Retention Bonus",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Incentive",
+ "link_to": "Employee Incentive",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Salary Slip",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Salary Register",
+ "link_to": "Salary Register",
+ "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": "2020-12-01 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/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json
deleted file mode 100644
index 7f59348..0000000
--- a/erpnext/loan_management/desk_page/loan/loan.json
+++ /dev/null
@@ -1,64 +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]"
- }
- ],
- "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,
- "icon": "loan",
- "idx": 0,
- "is_standard": 1,
- "label": "Loan",
- "modified": "2020-10-17 12:59:50.336085",
- "modified_by": "Administrator",
- "module": "Loan Management",
- "name": "Loan",
- "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"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index e8ecf01..acf09f5 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -26,11 +26,11 @@
"disbursed_amount",
"column_break_11",
"maximum_loan_amount",
- "is_term_loan",
"repayment_method",
"repayment_periods",
"monthly_repayment_amount",
"repayment_start_date",
+ "is_term_loan",
"account_info",
"mode_of_payment",
"payment_account",
@@ -332,6 +332,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:doc.is_secured_loan",
"fetch_from": "loan_application.maximum_loan_amount",
"fieldname": "maximum_loan_amount",
"fieldtype": "Currency",
@@ -352,7 +353,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-05 10:04:00.762975",
+ "modified": "2020-11-24 12:27:23.208240",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 8405d6e..cd40a66 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -13,6 +13,8 @@
class Loan(AccountsController):
def validate(self):
+ if self.applicant_type == 'Employee' and self.repay_from_salary:
+ validate_employee_currency_with_company_currency(self.applicant, self.company)
self.set_loan_amount()
self.validate_loan_amount()
self.set_missing_fields()
@@ -329,5 +331,14 @@
return unpledge_request
-
-
+def validate_employee_currency_with_company_currency(applicant, company):
+ from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
+ if not applicant:
+ frappe.throw(_("Please select Applicant"))
+ if not company:
+ frappe.throw(_("Please select Company"))
+ employee_currency = get_employee_currency(applicant)
+ company_currency = erpnext.get_company_currency(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))
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 10a7b11..a63d065 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -19,6 +19,7 @@
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestLoan(unittest.TestCase):
def setUp(self):
@@ -44,6 +45,7 @@
create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
self.applicant1 = make_employee("robert_loan@loan.com")
+ make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR')
if not frappe.db.exists("Customer", "_Test Loan Customer"):
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index bac6e63..e59db4c 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -127,6 +127,7 @@
target_doc.loan_account = account_details.loan_account
target_doc.interest_income_account = account_details.interest_income_account
target_doc.penalty_income_account = account_details.penalty_income_account
+ target_doc.loan_application = source_name
doclist = get_mapped_doc("Loan Application", source_name, {
diff --git a/erpnext/loan_management/doctype/loan_application/test_loan_application.py b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
index 687c580..2a659e9 100644
--- a/erpnext/loan_management/doctype/loan_application/test_loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
@@ -5,7 +5,7 @@
import frappe
import unittest
-from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_employee, make_salary_structure
from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan_accounts
class TestLoanApplication(unittest.TestCase):
@@ -14,6 +14,7 @@
create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
'Interest Income Account - _TC', 'Penalty Income Account - _TC', 'Repay Over Number of Periods', 18)
self.applicant = make_employee("kate_loan@loan.com", "_Test Company")
+ make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant, currency='INR')
self.create_loan_application()
def create_loan_application(self):
@@ -29,7 +30,6 @@
})
loan_application.insert()
-
def test_loan_totals(self):
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 233862b..f341e81 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -171,10 +171,10 @@
return security_value
@frappe.whitelist()
-def get_disbursal_amount(loan):
- loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
- "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
- filters= { "name": loan })[0]
+def get_disbursal_amount(loan, on_current_security_price=0):
+ loan_details = frappe.get_value("Loan", loan, ["loan_amount", "disbursed_amount", "total_payment",
+ "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan",
+ "maximum_loan_amount"], as_dict=1)
if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
'status': 'Pending'}):
@@ -188,9 +188,12 @@
- flt(loan_details.total_principal_paid)
security_value = 0.0
- if loan_details.is_secured_loan:
+ if loan_details.is_secured_loan and on_current_security_price:
security_value = get_total_pledged_security_value(loan)
+ if loan_details.is_secured_loan and not on_current_security_price:
+ security_value = flt(loan_details.maximum_loan_amount)
+
if not security_value and not loan_details.is_secured_loan:
security_value = flt(loan_details.loan_amount)
diff --git a/erpnext/loan_management/workspace/loan/loan.json b/erpnext/loan_management/workspace/loan/loan.json
new file mode 100644
index 0000000..80d8a80
--- /dev/null
+++ b/erpnext/loan_management/workspace/loan/loan.json
@@ -0,0 +1,244 @@
+{
+ "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_standard": 1,
+ "label": "Loan",
+ "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": "2020-12-01 13:38:36.597212",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan",
+ "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"
+ }
+ ]
+}
\ 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 3dd86a3..0000000
--- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
+++ /dev/null
@@ -1,125 +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,
- "icon": "organization",
- "idx": 0,
- "is_standard": 1,
- "label": "Manufacturing",
- "modified": "2020-06-30 18:40:04.454826",
- "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/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 1c4b7a1..42662f6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -134,7 +134,7 @@
frm.set_intro(__('This is a Template BOM and will be used to make the work order for {0} of the item {1}',
[
`<a class="variants-intro">variants</a>`,
- `<a href="#Form/Item/${frm.doc.item}">${frm.doc.item}</a>`,
+ `<a href="/app/item/${frm.doc.item}">${frm.doc.item}</a>`,
]), true);
frm.$wrapper.find(".variants-intro").on("click", () => {
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8888a96..6363242 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -169,8 +169,8 @@
'qty' : args.get("qty") or args.get("stock_qty") or 1,
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
- 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0,
- 'sourced_by_supplier' : args['sourced_by_supplier'] or 0
+ 'include_item_in_manufacturing': cint(args.get('transfer_for_manufacture')),
+ 'sourced_by_supplier' : args.get('sourced_by_supplier', 0)
}
return ret_item
diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html
index c782f7b..6cd5f8c 100644
--- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html
+++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html
@@ -12,11 +12,11 @@
<hr style="margin: 15px -15px;">
<p>
{% if data.value %}
- <a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="#Form/BOM/{{ data.value }}">
+ <a style="margin-right: 7px; margin-bottom: 7px" class="btn btn-default btn-xs" href="/app/Form/BOM/{{ data.value }}">
{{ __("Open BOM {0}", [data.value.bold()]) }}</a>
{% endif %}
{% if data.item_code %}
- <a class="btn btn-default btn-xs" href="#Form/Item/{{ data.item_code }}">
+ <a class="btn btn-default btn-xs" href="/app/Form/Item/{{ data.item_code }}">
{{ __("Open Item {0}", [data.item_code.bold()]) }}</a>
{% endif %}
</p>
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index b051b32..4e8dd41 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -31,6 +31,16 @@
}
}
+ frm.set_query("quality_inspection", function() {
+ return {
+ query: "erpnext.stock.doctype.quality_inspection.quality_inspection.quality_inspection_query",
+ filters: {
+ "item_code": frm.doc.production_item,
+ "reference_name": frm.doc.name
+ }
+ };
+ });
+
frm.trigger("toggle_operation_number");
if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 575e719..5713f69 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -20,6 +20,7 @@
"production_item",
"item_name",
"for_quantity",
+ "quality_inspection",
"wip_warehouse",
"column_break_12",
"employee",
@@ -305,11 +306,19 @@
"label": "Sequence Id",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "depends_on": "eval:!doc.__islocal;",
+ "fieldname": "quality_inspection",
+ "fieldtype": "Link",
+ "label": "Quality Inspection",
+ "no_copy": 1,
+ "options": "Quality Inspection"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-10-14 12:58:25.327897",
+ "modified": "2020-11-19 18:26:50.531664",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 4dfa78b..d15d81e 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -353,17 +353,19 @@
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
- if filters.get("work_order"):
- args = {"parent": filters.get("work_order")}
- if txt:
- args["operation"] = ("like", "%{0}%".format(txt))
+ if not filters.get("work_order"):
+ frappe.msgprint(_("Please select a Work Order first."))
+ return []
+ args = {"parent": filters.get("work_order")}
+ if txt:
+ args["operation"] = ("like", "%{0}%".format(txt))
- return frappe.get_all("Work Order Operation",
- filters = args,
- fields = ["distinct operation as operation"],
- limit_start = start,
- limit_page_length = page_len,
- order_by="idx asc", as_list=1)
+ return frappe.get_all("Work Order Operation",
+ filters = args,
+ fields = ["distinct operation as operation"],
+ limit_start = start,
+ limit_page_length = page_len,
+ order_by="idx asc", as_list=1)
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
diff --git a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
index cf07698..f4877fd 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card_calendar.js
@@ -8,7 +8,17 @@
"allDay": "allDay",
"progress": "progress"
},
- gantt: true,
+ gantt: {
+ field_map: {
+ "start": "started_time",
+ "end": "started_time",
+ "id": "name",
+ "title": "subject",
+ "color": "color",
+ "allDay": "allDay",
+ "progress": "progress"
+ }
+ },
filters: [
{
"fieldtype": "Link",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 3833e86..8f9dd05 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -319,7 +319,7 @@
frappe.flags.mute_messages = False
if wo_list:
- wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
+ wo_list = ["""<a href="/app/Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in wo_list]
msgprint(_("{0} created").format(comma_and(wo_list)))
else :
@@ -423,7 +423,7 @@
frappe.flags.mute_messages = False
if material_request_list:
- material_request_list = ["""<a href="#Form/Material Request/{0}">{1}</a>""".format(m.name, m.name) \
+ material_request_list = ["""<a href="/app/Form/Material Request/{0}">{1}</a>""".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/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index e539279..2bf3fbf 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -491,6 +491,39 @@
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
+ def test_partial_material_consumption(self):
+ frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
+ wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
+
+ ste_cancel_list = []
+ ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
+ ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+
+ ste_cancel_list.extend([ste1, ste2])
+
+ s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
+ s.submit()
+ ste_cancel_list.append(s)
+
+ ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+ ste1.submit()
+ ste_cancel_list.append(ste1)
+
+ print(wo_order.name)
+ ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
+ self.assertEquals(ste3.fg_completed_qty, 2)
+
+ expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
+ for row in ste3.items:
+ self.assertEquals(row.qty, expected_qty.get(row.item_code))
+
+ for ste_doc in ste_cancel_list:
+ ste_doc.cancel()
+
+ frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
+
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 9ce465c..a6086fb 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -545,7 +545,8 @@
var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) {
- if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
+ let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
+ if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}
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 2ac6fa0..7beecac 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js
@@ -25,11 +25,11 @@
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
- if (column.id == "Item"){
- if (data["Enough Parts to Build"] > 0){
- value = `<a style='color:green' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
+ if (column.id == "item") {
+ if (data["enough_parts_to_build"] > 0) {
+ value = `<a style='color:green' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
} else {
- value = `<a style='color:red' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
+ value = `<a style='color:red' href="/app/item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
}
}
return value
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index ebc01c6..806d268 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -124,7 +124,7 @@
if self.filters.include_subassembly_raw_materials else "(bom_item.qty / bom.quantity)")
raw_materials = frappe.db.sql(""" SELECT bom_item.parent, bom_item.item_code,
- bom_item.item_name as raw_material_name, {0} as required_qty
+ bom_item.item_name as raw_material_name, {0} as required_qty_per_unit
FROM
`tabBOM` as bom, `tab{1}` as bom_item
WHERE
@@ -208,7 +208,7 @@
warehouses = self.mrp_warehouses or []
for d in self.raw_materials_dict.get(key):
if self.filters.based_on != "Work Order":
- d.required_qty = d.required_qty * data.qty_to_manufacture
+ d.required_qty = d.required_qty_per_unit * data.qty_to_manufacture
if not warehouses:
warehouses = [data.warehouse]
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
new file mode 100644
index 0000000..a355203
--- /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/modules.txt b/erpnext/modules.txt
index 1e2aeea..62f5dce 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -25,4 +25,5 @@
Quality Management
Communication
Loan Management
-Payroll
\ No newline at end of file
+Payroll
+Telephony
\ 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 24d655a..0000000
--- a/erpnext/non_profit/desk_page/non_profit/non_profit.json
+++ /dev/null
@@ -1,82 +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,
- "hide_custom": 0,
- "icon": "non-profit",
- "idx": 0,
- "is_standard": 1,
- "label": "Non Profit",
- "modified": "2020-06-30 18:35:52.770917",
- "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/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 44b975e..25d6b53 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -59,7 +59,7 @@
frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({
'fullname': self.member_name,
- 'email': self.email_id or self.user,
+ 'email': self.email_id or self.email,
'phone': None
}))
@@ -177,4 +177,4 @@
mobile=mobile
))
- return member.name
\ No newline at end of file
+ return member.name
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 4c85cb6..7d15aba 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -70,7 +70,7 @@
settings = frappe.get_doc("Membership Settings")
if not member.customer:
- frappe.throw(_("No customer linked to member {}", [member.name]))
+ frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
if not settings.debit_account:
frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json
new file mode 100644
index 0000000..da2a514
--- /dev/null
+++ b/erpnext/non_profit/workspace/non_profit/non_profit.json
@@ -0,0 +1,224 @@
+{
+ "category": "Domains",
+ "charts": [],
+ "creation": "2020-03-02 17:23:47.811421",
+ "developer_mode_only": 0,
+ "disable_user_customization": 0,
+ "docstatus": 0,
+ "doctype": "Workspace",
+ "extends_another_page": 0,
+ "hide_custom": 0,
+ "icon": "non-profit",
+ "idx": 0,
+ "is_standard": 1,
+ "label": "Non Profit",
+ "links": [
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Loan Management",
+ "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": "Grant Application",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Grant Application",
+ "link_to": "Grant Application",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Membership",
+ "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": "Membership",
+ "link_to": "Membership",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Membership Type",
+ "link_to": "Membership Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Membership Settings",
+ "link_to": "Membership Settings",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Volunteer",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "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": "Volunteer Type",
+ "link_to": "Volunteer Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Chapter",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Chapter",
+ "link_to": "Chapter",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Donor",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Donor",
+ "link_to": "Donor",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Donor Type",
+ "link_to": "Donor Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ }
+ ],
+ "modified": "2020-12-01 13:38:38.351409",
+ "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 97177de0..a597b49 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -450,7 +450,6 @@
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate
-erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
erpnext.patches.v9_0.add_healthcare_domain
erpnext.patches.v9_0.set_variant_item_description
erpnext.patches.v9_0.set_uoms_in_variant_field
@@ -691,6 +690,7 @@
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.reload_doctype('Dashboard')
+execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
@@ -733,6 +733,11 @@
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
+erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
erpnext.patches.v13_0.update_custom_fields_for_shopify
execute:frappe.delete_doc("Report", "Quoted Item Comparison")
+erpnext.patches.v13_0.updates_for_multi_currency_payroll
+erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
+erpnext.patches.v13_0.add_po_to_global_search
+erpnext.patches.v13_0.update_returned_qty_in_pr_dn
diff --git a/erpnext/patches/v11_0/create_salary_structure_assignments.py b/erpnext/patches/v11_0/create_salary_structure_assignments.py
index c51c381..a908c16 100644
--- a/erpnext/patches/v11_0/create_salary_structure_assignments.py
+++ b/erpnext/patches/v11_0/create_salary_structure_assignments.py
@@ -8,8 +8,8 @@
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import DuplicateAssignment
def execute():
- frappe.reload_doc('Payroll', 'doctype', 'salary_structure')
- frappe.reload_doc("Payroll", "doctype", "salary_structure_assignment")
+ frappe.reload_doc('Payroll', 'doctype', 'Salary Structure')
+ frappe.reload_doc("Payroll", "doctype", "Salary Structure Assignment")
frappe.db.sql("""
delete from `tabSalary Structure Assignment`
where salary_structure in (select name from `tabSalary Structure` where is_active='No' or docstatus!=1)
@@ -33,6 +33,13 @@
AND employee in (select name from `tabEmployee` where ifNull(status, '') != 'Left')
""".format(cols), as_dict=1)
+ all_companies = frappe.db.get_all("Company", fields=["name", "default_currency"])
+ for d in all_companies:
+ company = d.name
+ company_currency = d.default_currency
+
+ frappe.db.sql("""update `tabSalary Structure` set currency = %s where company=%s""", (company_currency, company))
+
for d in ss_details:
try:
joining_date, relieving_date = frappe.db.get_value("Employee", d.employee,
@@ -42,6 +49,7 @@
from_date = joining_date
elif relieving_date and getdate(from_date) > relieving_date:
continue
+ company_currency = frappe.db.get_value('Company', d.company, 'default_currency')
s = frappe.new_doc("Salary Structure Assignment")
s.employee = d.employee
@@ -52,6 +60,7 @@
s.base = d.get("base")
s.variable = d.get("variable")
s.company = d.company
+ s.currency = company_currency
# to migrate the data of the old employees
s.flags.old_employee = True
diff --git a/erpnext/patches/v13_0/add_po_to_global_search.py b/erpnext/patches/v13_0/add_po_to_global_search.py
new file mode 100644
index 0000000..1c60b18
--- /dev/null
+++ b/erpnext/patches/v13_0/add_po_to_global_search.py
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+import frappe
+
+
+def execute():
+ global_search_settings = frappe.get_single("Global Search Settings")
+
+ if "Purchase Order" in (
+ dt.document_type for dt in global_search_settings.allowed_in_global_search
+ ):
+ return
+
+ global_search_settings.append(
+ "allowed_in_global_search", {"document_type": "Purchase Order"}
+ )
+
+ global_search_settings.save(ignore_permissions=True)
diff --git a/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
new file mode 100644
index 0000000..90dc0e2
--- /dev/null
+++ b/erpnext/patches/v13_0/create_leave_policy_assignment_based_on_employee_current_leave_policy.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ if "leave_policy" in frappe.db.get_table_columns("Employee"):
+ employees_with_leave_policy = frappe.db.sql("SELECT name, leave_policy FROM `tabEmployee` WHERE leave_policy IS NOT NULL and leave_policy != ''", as_dict = 1)
+
+ employee_with_assignment = []
+ leave_policy =[]
+
+ #for employee
+
+ for employee in employees_with_leave_policy:
+ alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": employee.leave_policy, "docstatus": 1})
+ if not alloc:
+ create_assignment(employee.name, employee.leave_policy)
+
+ employee_with_assignment.append(employee.name)
+ leave_policy.append(employee.leave_policy)
+
+
+ if "default_leave_policy" in frappe.db.get_table_columns("Employee"):
+ employee_grade_with_leave_policy = frappe.db.sql("SELECT name, default_leave_policy FROM `tabEmployee Grade` WHERE default_leave_policy IS NOT NULL and default_leave_policy!=''", as_dict = 1)
+
+ #for whole employee Grade
+
+ for grade in employee_grade_with_leave_policy:
+ employees = get_employee_with_grade(grade.name)
+ for employee in employees:
+
+ if employee not in employee_with_assignment: #Will ensure no duplicate
+ alloc = frappe.db.exists("Leave Allocation", {"employee":employee.name, "leave_policy": grade.default_leave_policy, "docstatus": 1})
+ if not alloc:
+ create_assignment(employee.name, grade.default_leave_policy)
+ leave_policy.append(grade.default_leave_policy)
+
+ #for old Leave allocation and leave policy from allocation, which may got updated in employee grade.
+ leave_allocations = frappe.db.sql("SELECT leave_policy, leave_period, employee FROM `tabLeave Allocation` WHERE leave_policy IS NOT NULL and leave_policy != '' and docstatus = 1 ", as_dict = 1)
+
+ for allocation in leave_allocations:
+ if allocation.leave_policy not in leave_policy:
+ create_assignment(allocation.employee, allocation.leave_policy, leave_period=allocation.leave_period,
+ allocation_exists=True)
+
+def create_assignment(employee, leave_policy, leave_period=None, allocation_exists = False):
+
+ filters = {"employee":employee, "leave_policy": leave_policy}
+ if leave_period:
+ filters["leave_period"] = leave_period
+
+ frappe.reload_doc('hr', 'doctype', 'leave_policy_assignment')
+
+ if not frappe.db.exists("Leave Policy Assignment" , filters):
+ lpa = frappe.new_doc("Leave Policy Assignment")
+ lpa.employee = employee
+ lpa.leave_policy = leave_policy
+
+ lpa.flags.ignore_mandatory = True
+ if allocation_exists:
+ lpa.assignment_based_on = 'Leave Period'
+ lpa.leave_period = leave_period
+ lpa.leaves_allocated = 1
+
+ lpa.save()
+ if allocation_exists:
+ lpa.submit()
+ #Updating old Leave Allocation
+ frappe.db.sql("Update `tabLeave Allocation` set leave_policy_assignment = %s", lpa.name)
+
+
+def get_employee_with_grade(grade):
+ return frappe.get_list("Employee", filters = {"grade": grade})
+
+
+
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index 96a6362..fa1dfed 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -29,7 +29,7 @@
'response_by_variance': response_by_variance,
'resolution_by_variance': resolution_by_variance,
'first_response_time': mins_to_first_response
- })
+ }, update_modified=False)
# commit after every 100 updates
count += 1
if count%100 == 0:
@@ -44,7 +44,7 @@
count = 0
for entry in opportunities:
mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
- frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response)
+ frappe.db.set_value('Opportunity', entry.name, 'first_response_time', mins_to_first_response, update_modified=False)
# commit after every 100 updates
count += 1
if count%100 == 0:
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index 7723942..561e967 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -5,6 +5,8 @@
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
+from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries
+from frappe.model.naming import make_autoname
def execute():
@@ -18,15 +20,29 @@
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
frappe.reload_doc('accounts', 'doctype', 'gl_entry')
+ frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
updated_loan_types = []
+ loans_to_close = []
+
+ # Update old loan status as closed
+ if frappe.db.has_column('Repayment Schedule', 'paid'):
+ loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
+ where paid = 0 and docstatus = 1""", as_dict=1)
+
+ loans_to_close = [d.parent for d in loans_list]
+
+ if loans_to_close:
+ frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
- 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'])
+ 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
+ filters={'docstatus': 1, 'status': ('!=', 'Closed')})
for loan in loans:
# Update details in Loan Types and Loan
loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
+ loan_type = loan.loan_type
group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
@@ -38,7 +54,26 @@
penalty_account = create_account(company=loan.company, account_type='Income Account',
account_name='Penalty Account', parent_account=group_income_account)
- if not loan_type_company:
+ # Same loan type used for multiple companies
+ if loan_type_company and loan_type_company != loan.company:
+ # get loan type for appropriate company
+ loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
+ 'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
+ 'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
+ 'penalty_income_account': loan.penalty_income_account}, 'name')
+
+ if not loan_type_name:
+ loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
+
+ # update loan type in loan
+ frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
+ loan.name))
+
+ loan_type = loan_type_name
+ if loan_type_name not in updated_loan_types:
+ updated_loan_types.append(loan_type_name)
+
+ elif not loan_type_company:
loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
@@ -49,8 +84,9 @@
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
updated_loan_types.append(loan.loan_type)
+ loan_type = loan.loan_type
- if loan.loan_type in updated_loan_types:
+ if loan_type in updated_loan_types:
if loan.status == 'Fully Disbursed':
status = 'Disbursed'
elif loan.status == 'Repaid/Closed':
@@ -64,25 +100,48 @@
'status': status
})
- process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type,
+ process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
loan=loan.name)
- payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
- FROM `tabJournal Entry` j, `tabJournal Entry Account` a
- WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
- and account = %s
- ''', (loan.name, loan.loan_account), as_dict=1)
- for payment in payments:
- repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant,
- loan.loan_type, loan.company)
+ if frappe.db.has_column('Repayment Schedule', 'paid'):
+ total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
+ ['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
- repayment_entry.amount_paid = payment.debit_in_account_currency
- repayment_entry.posting_date = payment.posting_date
- repayment_entry.save()
- repayment_entry.submit()
+ accrued_entries = get_accrued_interest_entries(loan.name)
+ for entry in accrued_entries:
+ interest_paid = 0
+ principal_paid = 0
- jv = frappe.get_doc('Journal Entry', payment.name)
- jv.flags.ignore_links = True
- jv.cancel()
+ if total_interest > entry.interest_amount:
+ interest_paid = entry.interest_amount
+ else:
+ interest_paid = total_interest
+ if total_principal > entry.payable_principal_amount:
+ principal_paid = entry.payable_principal_amount
+ else:
+ principal_paid = total_principal
+
+ frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+ SET paid_principal_amount = `paid_principal_amount` + %s,
+ paid_interest_amount = `paid_interest_amount` + %s
+ WHERE name = %s""",
+ (principal_paid, interest_paid, entry.name))
+
+ total_principal -= principal_paid
+ total_interest -= interest_paid
+
+def create_loan_type(loan, loan_type_name, penalty_account):
+ loan_type_doc = frappe.new_doc('Loan Type')
+ loan_type_doc.loan_name = make_autoname("Loan Type-.####")
+ loan_type_doc.is_term_loan = 1
+ loan_type_doc.company = loan.company
+ loan_type_doc.mode_of_payment = loan.mode_of_payment
+ loan_type_doc.payment_account = loan.payment_account
+ loan_type_doc.loan_account = loan.loan_account
+ loan_type_doc.interest_income_account = loan.interest_income_account
+ loan_type_doc.penalty_income_account = penalty_account
+ loan_type_doc.submit()
+
+ return loan_type_doc.name
diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
new file mode 100644
index 0000000..7f42cd9
--- /dev/null
+++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
@@ -0,0 +1,27 @@
+# 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('stock', 'doctype', 'purchase_receipt')
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+ frappe.reload_doc('stock', 'doctype', 'delivery_note')
+ frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
+
+ def update_from_return_docs(doctype):
+ for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
+ # Update original receipt/delivery document from return
+ return_doc = frappe.get_cached_doc(doctype, return_doc.name)
+ return_doc.update_prevdoc_status()
+ return_against = frappe.get_doc(doctype, return_doc.return_against)
+ return_against.update_billing_status()
+
+ # Set received qty in stock uom in PR, as returned qty is checked against it
+ frappe.db.sql(""" update `tabPurchase Receipt Item`
+ set received_stock_qty = received_qty * conversion_factor
+ where docstatus = 1 """)
+
+ for doctype in ('Purchase Receipt', 'Delivery Note'):
+ update_from_return_docs(doctype)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
new file mode 100644
index 0000000..340bf49
--- /dev/null
+++ b/erpnext/patches/v13_0/updates_for_multi_currency_payroll.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+
+ frappe.reload_doc('Accounts', 'doctype', 'Salary Component Account')
+ if frappe.db.has_column('Salary Component Account', 'default_account'):
+ rename_field("Salary Component Account", "default_account", "account")
+
+ doctype_list = [
+ {
+ 'module':'HR',
+ 'doctype':'Employee Advance'
+ },
+ {
+ 'module':'HR',
+ 'doctype':'Leave Encashment'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Additional Salary'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Employee Benefit Application'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Employee Benefit Claim'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Employee Incentive'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Employee Tax Exemption Declaration'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Employee Tax Exemption Proof Submission'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Income Tax Slab'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Payroll Entry'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Retention Bonus'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Salary Structure'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Salary Structure Assignment'
+ },
+ {
+ 'module':'Payroll',
+ 'doctype':'Salary Slip'
+ },
+ ]
+
+ for item in doctype_list:
+ frappe.reload_doc(item['module'], 'doctype', item['doctype'])
+
+ # update company in employee advance based on employee company
+ for dt in ['Employee Incentive', 'Leave Encashment', 'Employee Benefit Application', 'Employee Benefit Claim']:
+ frappe.db.sql("""
+ update `tab{doctype}`
+ set company = (select company from tabEmployee where name=`tab{doctype}`.employee)
+ """.format(doctype=dt))
+
+ # update exchange rate for employee advance
+ frappe.db.sql("update `tabEmployee Advance` set exchange_rate=1")
+
+ # get all companies and it's currency
+ all_companies = frappe.db.get_all("Company", fields=["name", "default_currency", "default_payroll_payable_account"])
+ for d in all_companies:
+ company = d.name
+ company_currency = d.default_currency
+ default_payroll_payable_account = d.default_payroll_payable_account
+
+ if not default_payroll_payable_account:
+ default_payroll_payable_account = frappe.db.get_value("Account",
+ {"account_name": _("Payroll Payable"), "company": company, "account_currency": company_currency, "is_group": 0})
+
+ # update currency in following doctypes based on company currency
+ doctypes_for_currency = ['Employee Advance', 'Leave Encashment', 'Employee Benefit Application',
+ 'Employee Benefit Claim', 'Employee Incentive', 'Additional Salary',
+ 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
+ 'Income Tax Slab', 'Retention Bonus', 'Salary Structure']
+
+ for dt in doctypes_for_currency:
+ frappe.db.sql("""update `tab{doctype}` set currency = %s where company=%s"""
+ .format(doctype=dt), (company_currency, company))
+
+ # update fields in payroll entry
+ frappe.db.sql("""
+ update `tabPayroll Entry`
+ set currency = %s,
+ exchange_rate = 1,
+ payroll_payable_account=%s
+ where company=%s
+ """, (company_currency, default_payroll_payable_account, company))
+
+ # update fields in Salary Structure Assignment
+ frappe.db.sql("""
+ update `tabSalary Structure Assignment`
+ set currency = %s,
+ payroll_payable_account=%s
+ where company=%s
+ """, (company_currency, default_payroll_payable_account, company))
+
+ # update fields in Salary Slip
+ frappe.db.sql("""
+ update `tabSalary Slip`
+ set currency = %s,
+ exchange_rate = 1,
+ base_hour_rate = hour_rate,
+ base_gross_pay = gross_pay,
+ base_total_deduction = total_deduction,
+ base_net_pay = net_pay,
+ base_rounded_total = rounded_total,
+ base_total_in_words = total_in_words
+ where company=%s
+ """, (company_currency, company))
diff --git a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
index 6e92ffb..910814f 100644
--- a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
+++ b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
@@ -7,19 +7,23 @@
def execute():
parent_list = []
count = 0
- for data in frappe.db.sql("""
- select
+
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+
+ for data in frappe.db.sql("""
+ select
`tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name,
`tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx,
`tabPurchase Receipt Item`.parent
- from
+ from
`tabPurchase Receipt Item`, `tabPurchase Receipt`
where
`tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and
`tabPurchase Receipt Item`.purchase_order_item is null and
`tabPurchase Receipt Item`.purchase_order is not null and
`tabPurchase Receipt`.is_return = 1""", as_dict=1):
- name = frappe.db.get_value('Purchase Order Item',
+ name = frappe.db.get_value('Purchase Order Item',
{'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name')
if name:
diff --git a/erpnext/patches/v9_0/fix_subscription_next_date.py b/erpnext/patches/v9_0/fix_subscription_next_date.py
deleted file mode 100644
index 4595c8d..0000000
--- a/erpnext/patches/v9_0/fix_subscription_next_date.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (c) 2017, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.utils import getdate
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
-
-def execute():
- frappe.reload_doc('accounts', 'doctype', 'subscription')
- fields = ["name", "reference_doctype", "reference_document",
- "start_date", "frequency", "repeat_on_day"]
-
- for d in fields:
- if not frappe.db.has_column('Subscription', d):
- return
-
- doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
- for data in frappe.get_all('Subscription',
- fields = fields,
- filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
-
- recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
- if recurring_id:
- frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
- .format(data.reference_doctype), (data.name, recurring_id))
-
- date_field = 'transaction_date'
- if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
- date_field = 'posting_date'
-
- start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
-
- if start_date and getdate(start_date) != getdate(data.start_date):
- last_ref_date = frappe.db.sql("""
- select {0}
- from `tab{1}`
- where subscription=%s and docstatus < 2
- order by creation desc
- limit 1
- """.format(date_field, data.reference_doctype), data.name)[0][0]
-
- next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
-
- frappe.db.set_value("Subscription", data.name, {
- "start_date": start_date,
- "next_schedule_date": next_schedule_date
- }, None)
\ No newline at end of file
diff --git a/erpnext/payroll/desk_page/payroll/payroll.json b/erpnext/payroll/desk_page/payroll/payroll.json
deleted file mode 100644
index 8fe8c44..0000000
--- a/erpnext/payroll/desk_page/payroll/payroll.json
+++ /dev/null
@@ -1,85 +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,
- "icon": "money-coins-1",
- "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/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index d56cd4e..7737e6c 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -13,4 +13,48 @@
};
});
},
+
+ employee: function(frm) {
+ if (frm.doc.employee) {
+ frappe.run_serially([
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('set_company')
+ ]);
+ } else {
+ frm.set_value("company", null);
+ }
+ },
+
+ set_company: function(frm) {
+ frappe.call({
+ method: "frappe.client.get_value",
+ args: {
+ doctype: "Employee",
+ fieldname: "company",
+ filters: {
+ name: frm.doc.employee
+ }
+ },
+ callback: function(data) {
+ if (data.message) {
+ frm.set_value("company", data.message.company);
+ }
+ }
+ });
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ },
});
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 69cb5da..2b29f66 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -11,20 +11,21 @@
"employee",
"employee_name",
"salary_component",
- "overwrite_salary_structure_amount",
- "deduct_full_tax_on_selected_payroll_date",
+ "type",
+ "amount",
"ref_doctype",
"ref_docname",
+ "amended_from",
"column_break_5",
"company",
- "is_recurring",
+ "department",
+ "currency",
"from_date",
"to_date",
"payroll_date",
- "type",
- "department",
- "amount",
- "amended_from"
+ "is_recurring",
+ "overwrite_salary_structure_amount",
+ "deduct_full_tax_on_selected_payroll_date"
],
"fields": [
{
@@ -59,6 +60,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
+ "options": "currency",
"reqd": 1
},
{
@@ -159,11 +161,22 @@
"label": "Reference Document",
"options": "ref_doctype",
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 21:10:50.374063",
+ "modified": "2020-10-20 17:51:13.419716",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index e3dc907..f5af677 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -22,10 +22,15 @@
def validate(self):
self.validate_dates()
+ self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
if self.amount < 0:
frappe.throw(_("Amount should not be less than zero."))
+ def validate_salary_structure(self):
+ if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+ frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
def validate_recurring_additional_salary_overlap(self):
if self.is_recurring:
additional_salaries = frappe.db.sql("""
diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
index de26543..4d47f25 100644
--- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/test_additional_salary.py
@@ -8,6 +8,7 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_employee_salary_slip, setup_test
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestAdditionalSalary(unittest.TestCase):
@@ -15,12 +16,19 @@
def setUp(self):
setup_test()
+ def tearDown(self):
+ for dt in ["Salary Slip", "Additional Salary", "Salary Structure Assignment", "Salary Structure"]:
+ frappe.db.sql("delete from `tab%s`" % dt)
+
def test_recurring_additional_salary(self):
+ amount = 0
+ salary_component = None
emp_id = make_employee("test_additional@salary.com")
frappe.db.set_value("Employee", emp_id, "relieving_date", add_days(nowdate(), 1800))
+ salary_structure = make_salary_structure("Test Salary Structure Additional Salary", "Monthly", employee=emp_id)
add_sal = get_additional_salary(emp_id)
-
- ss = make_employee_salary_slip("test_additional@salary.com", "Monthly")
+
+ ss = make_employee_salary_slip("test_additional@salary.com", "Monthly", salary_structure=salary_structure.name)
for earning in ss.earnings:
if earning.salary_component == "Recurring Salary Component":
amount = earning.amount
@@ -29,8 +37,6 @@
self.assertEqual(amount, add_sal.amount)
self.assertEqual(salary_component, add_sal.salary_component)
-
-
def get_additional_salary(emp_id):
create_salary_component("Recurring Salary Component")
add_sal = frappe.new_doc("Additional Salary")
@@ -40,6 +46,7 @@
add_sal.from_date = add_days(nowdate(), -50)
add_sal.to_date = add_days(nowdate(), 180)
add_sal.amount = 5000
+ add_sal.currency = erpnext.get_default_currency()
add_sal.save()
add_sal.submit()
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
index f509df3..6756cd9 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.js
@@ -3,7 +3,12 @@
frappe.ui.form.on('Employee Benefit Application', {
employee: function(frm) {
- frm.trigger('set_earning_component');
+ if (frm.doc.employee) {
+ frappe.run_serially([
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('set_earning_component')
+ ]);
+ }
var method, args;
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
@@ -38,9 +43,26 @@
});
},
+ get_employee_currency: function(frm) {
+ if (frm.doc.employee) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ }
+ },
+
payroll_period: function(frm) {
var method, args;
- if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
+ if (frm.doc.employee && frm.doc.date && frm.doc.payroll_period) {
method = "erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
args = {
employee: frm.doc.employee,
@@ -60,11 +82,14 @@
method: method,
args: args,
callback: function (data) {
- if(!data.exc){
- if(data.message){
+ if (!data.exc) {
+ if (data.message) {
frm.set_value("max_benefits", data.message);
+ } else {
+ frm.set_value("max_benefits", 0);
}
}
+ frm.refresh_fields();
}
});
};
@@ -82,14 +107,19 @@
var tbl = doc.employee_benefits || [];
var pro_rata_dispensed_amount = 0;
var total_amount = 0;
- for(var i = 0; i < tbl.length; i++){
- if(cint(tbl[i].amount) > 0) {
- total_amount += flt(tbl[i].amount);
- }
- if(tbl[i].pay_against_benefit_claim != 1){
- pro_rata_dispensed_amount += flt(tbl[i].amount);
+ if (doc.max_benefits === 0) {
+ doc.employee_benefits = [];
+ } else {
+ for (var i = 0; i < tbl.length; i++) {
+ if (cint(tbl[i].amount) > 0) {
+ total_amount += flt(tbl[i].amount);
+ }
+ if (tbl[i].pay_against_benefit_claim != 1) {
+ pro_rata_dispensed_amount += flt(tbl[i].amount);
+ }
}
}
+
doc.total_amount = total_amount;
doc.remaining_benefit = doc.max_benefits - total_amount;
doc.pro_rata_dispensed_amount = pro_rata_dispensed_amount;
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index b0c1bd6..4c45580 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -10,17 +10,20 @@
"field_order": [
"employee",
"employee_name",
+ "currency",
"max_benefits",
"remaining_benefit",
"column_break_2",
"date",
"payroll_period",
"department",
+ "company",
"amended_from",
"section_break_4",
"employee_benefits",
"totals",
"total_amount",
+ "column_break",
"pro_rata_dispensed_amount"
],
"fields": [
@@ -43,12 +46,14 @@
"fieldname": "max_benefits",
"fieldtype": "Currency",
"label": "Max Benefits (Yearly)",
+ "options": "currency",
"read_only": 1
},
{
"fieldname": "remaining_benefit",
"fieldtype": "Currency",
"label": "Remaining Benefits (Yearly)",
+ "options": "currency",
"read_only": 1
},
{
@@ -108,18 +113,42 @@
"fieldname": "total_amount",
"fieldtype": "Currency",
"label": "Total Amount",
+ "options": "currency",
"read_only": 1
},
{
"fieldname": "pro_rata_dispensed_amount",
"fieldtype": "Currency",
"label": "Dispensed Amount (Pro-rated)",
+ "options": "currency",
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break",
+ "fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:58:31.271922",
+ "modified": "2020-12-14 15:52:08.566418",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application",
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index ef844fb..27df30a 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -33,8 +33,8 @@
benefit_given = get_sal_slip_total_benefit_given(self.employee, payroll_period, component = benefit.earning_component)
benefit_claim_remining = benefit_claimed - benefit_given
if benefit_claimed > 0 and benefit_claim_remining > benefit.amount:
- frappe.throw(_("An amount of {0} already claimed for the component {1},\
- set the amount equal or greater than {2}").format(benefit_claimed, benefit.earning_component, benefit_claim_remining))
+ frappe.throw(_("An amount of {0} already claimed for the component {1}, set the amount equal or greater than {2}").format(
+ benefit_claimed, benefit.earning_component, benefit_claim_remining))
def validate_remaining_benefit_amount(self):
# check salary structure earnings have flexi component (sum of max_benefit_amount)
@@ -62,11 +62,11 @@
if pro_rata_amount == 0 and non_pro_rata_amount == 0:
frappe.throw(_("Please add the remaining benefits {0} to any of the existing component").format(self.remaining_benefit))
elif non_pro_rata_amount > 0 and non_pro_rata_amount < rounded(self.remaining_benefit):
- frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application \
- as pro-rata component").format(non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
+ frappe.throw(_("You can claim only an amount of {0}, the rest amount {1} should be in the application as pro-rata component").format(
+ non_pro_rata_amount, self.remaining_benefit - non_pro_rata_amount))
elif non_pro_rata_amount == 0:
- frappe.throw(_("Please add the remaining benefits {0} to the application as \
- pro-rata component").format(self.remaining_benefit))
+ frappe.throw(_("Please add the remaining benefits {0} to the application as pro-rata component").format(
+ self.remaining_benefit))
def validate_max_benefit_for_component(self):
if self.employee_benefits:
@@ -115,7 +115,7 @@
if max_benefits and max_benefits > 0:
have_depends_on_payment_days = False
per_day_amount_total = 0
- payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[0]
+ payroll_period_days = get_payroll_period_days(on_date, on_date, employee)[1]
payroll_period_obj = frappe.get_doc("Payroll Period", payroll_period)
# Get all salary slip flexi amount in the payroll period
@@ -239,4 +239,17 @@
""", salary_structure)
else:
frappe.throw(_("Salary Structure not found for employee {0} and date {1}")
- .format(filters['employee'], filters['date']))
\ No newline at end of file
+ .format(filters['employee'], filters['date']))
+
+@frappe.whitelist()
+def get_earning_components_max_benefits(employee, date, earning_component):
+ salary_structure = get_assigned_salary_structure(employee, date)
+ amount = frappe.db.sql("""
+ select amount
+ from `tabSalary Detail`
+ where parent = %s and is_flexible_benefit = 1
+ and salary_component = %s
+ order by name
+ """, salary_structure, earning_component)
+
+ return amount if amount else 0
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
index fa6b4da..c93d356 100644
--- a/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
+++ b/erpnext/payroll/doctype/employee_benefit_application_detail/employee_benefit_application_detail.json
@@ -33,6 +33,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Max Benefit Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -40,12 +41,13 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
+ "options": "currency",
"reqd": 1
}
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 23:45:00.519134",
+ "modified": "2020-09-29 16:22:15.783854",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application Detail",
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
index 6db6cb8..ea9ccd5 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
@@ -12,5 +12,24 @@
},
employee: function(frm) {
frm.set_value("earning_component", null);
+ if (frm.doc.employee) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.set_df_property('currency', 'hidden', 0);
+ }
+ }
+ });
+ }
+ if (!frm.doc.earning_component) {
+ frm.doc.max_amount_eligible = null;
+ frm.doc.claimed_amount = null;
+ }
+ frm.refresh_fields();
}
});
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index ae4c218..da24aac 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -12,6 +12,8 @@
"department",
"column_break_3",
"claim_date",
+ "currency",
+ "company",
"benefit_type_and_amount",
"earning_component",
"max_amount_eligible",
@@ -76,6 +78,7 @@
"fieldname": "max_amount_eligible",
"fieldtype": "Currency",
"label": "Max Amount Eligible",
+ "options": "currency",
"read_only": 1
},
{
@@ -92,6 +95,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Claimed Amount",
+ "options": "currency",
"reqd": 1
},
{
@@ -119,11 +123,29 @@
"fieldname": "attachments",
"fieldtype": "Attach",
"label": "Attachments"
+ },
+ {
+ "default": "Company:company:default_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Currency",
+ "options": "Currency",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.company",
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 23:01:50.791676",
+ "modified": "2020-11-25 11:49:56.097352",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Claim",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
index db0f83a..182ce0f 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
@@ -11,12 +11,57 @@
};
});
+ if (!frm.doc.company) return;
frm.set_query("salary_component", function() {
return {
- filters: {
- "type": "Earning"
- }
+ query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+ filters: {type: "earning", company: frm.doc.company}
};
});
- }
+
+ },
+
+ employee: function(frm) {
+ if (frm.doc.employee) {
+ frappe.run_serially([
+ () => frm.trigger('get_employee_currency'),
+ () => frm.trigger('set_company')
+ ]);
+ } else {
+ frm.set_value("company", null);
+ }
+ },
+
+ set_company: function(frm) {
+ frappe.call({
+ method: "frappe.client.get_value",
+ args: {
+ doctype: "Employee",
+ fieldname: "company",
+ filters: {
+ name: frm.doc.employee
+ }
+ },
+ callback: function(data) {
+ if (data.message) {
+ frm.set_value("company", data.message.company);
+ }
+ }
+ });
+ },
+
+ get_employee_currency: function(frm) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ },
});
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index 204c9a4..e5b1052 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -7,10 +7,12 @@
"engine": "InnoDB",
"field_order": [
"employee",
- "incentive_amount",
"employee_name",
- "salary_component",
+ "company",
+ "currency",
+ "incentive_amount",
"column_break_5",
+ "salary_component",
"payroll_date",
"department",
"amended_from"
@@ -28,6 +30,7 @@
"fieldname": "incentive_amount",
"fieldtype": "Currency",
"label": "Incentive Amount",
+ "options": "currency",
"reqd": 1
},
{
@@ -70,11 +73,29 @@
"label": "Salary Component",
"options": "Salary Component",
"reqd": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:42:51.209630",
+ "modified": "2020-10-20 17:22:16.468042",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Incentive",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index 84a97f6..ead3db1 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -4,14 +4,23 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.model.document import Document
class EmployeeIncentive(Document):
+ def validate(self):
+ self.validate_salary_structure()
+
+ def validate_salary_structure(self):
+ if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
+ frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
+
def on_submit(self):
company = frappe.db.get_value('Employee', self.employee, 'company')
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = self.employee
+ additional_salary.currency = self.currency
additional_salary.salary_component = self.salary_component
additional_salary.overwrite_salary_structure_amount = 0
additional_salary.amount = self.incentive_amount
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index de7c348..83d4ae5 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -14,6 +14,7 @@
"column_break_2",
"payroll_period",
"company",
+ "currency",
"amended_from",
"section_break_8",
"declarations",
@@ -92,6 +93,7 @@
"fieldname": "total_declared_amount",
"fieldtype": "Currency",
"label": "Total Declared Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -102,12 +104,22 @@
"fieldname": "total_exemption_amount",
"fieldtype": "Currency",
"label": "Total Exemption Amount",
+ "options": "currency",
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:49:43.829892",
+ "modified": "2020-10-20 16:42:24.493761",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Declaration",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
index 9549fd1..0609d19 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
@@ -22,6 +22,7 @@
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
+ "currency": erpnext.get_default_currency(),
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
@@ -39,6 +40,7 @@
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
+ "currency": erpnext.get_default_currency(),
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
@@ -54,6 +56,7 @@
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
+ "currency": erpnext.get_default_currency(),
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
@@ -70,6 +73,7 @@
"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
+ "currency": erpnext.get_default_currency(),
"declarations": [
dict(exemption_sub_category = "_Test Sub Category",
exemption_category = "_Test Category",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
index 8c2f9aa..723a3df 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
@@ -35,6 +35,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Maximum Exempted Amount",
+ "options": "currency",
"read_only": 1,
"reqd": 1
},
@@ -43,12 +44,13 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Declared Amount",
+ "options": "currency",
"reqd": 1
}
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 23:41:03.638739",
+ "modified": "2020-10-20 16:43:09.606265",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Declaration Category",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
index 715d755..497f35c 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
@@ -54,5 +54,9 @@
});
});
}
+ },
+
+ currency: function(frm) {
+ frm.refresh_fields();
}
});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index b62b5aa..53f18cb 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -11,6 +11,7 @@
"employee",
"employee_name",
"department",
+ "currency",
"column_break_2",
"submission_date",
"payroll_period",
@@ -97,6 +98,7 @@
"fieldname": "total_actual_amount",
"fieldtype": "Currency",
"label": "Total Actual Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -107,6 +109,7 @@
"fieldname": "exemption_amount",
"fieldtype": "Currency",
"label": "Total Exemption Amount",
+ "options": "currency",
"read_only": 1
},
{
@@ -126,11 +129,20 @@
"options": "Employee Tax Exemption Proof Submission",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:53:10.412321",
+ "modified": "2020-10-20 16:47:03.410020",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Proof Submission",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
index c1f5320..2fd8b94 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
@@ -34,6 +34,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Maximum Exemption Amount",
+ "options": "currency",
"read_only": 1,
"reqd": 1
},
@@ -48,12 +49,13 @@
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
- "label": "Actual Amount"
+ "label": "Actual Amount",
+ "options": "currency"
}
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 23:37:08.265600",
+ "modified": "2020-10-20 16:47:31.480870",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Proof Submission Detail",
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
index 73a54eb..7d780d3 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.js
@@ -2,5 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('Income Tax Slab', {
-
+ currency: function(frm) {
+ frm.refresh_fields();
+ }
});
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
index 6337d5a..9fa261d 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
@@ -9,8 +9,9 @@
"effective_from",
"company",
"column_break_3",
- "allow_tax_exemption",
+ "currency",
"standard_tax_exemption_amount",
+ "allow_tax_exemption",
"disabled",
"amended_from",
"taxable_salary_slabs_section",
@@ -70,7 +71,7 @@
"fieldname": "standard_tax_exemption_amount",
"fieldtype": "Currency",
"label": "Standard Tax Exemption Amount",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"fieldname": "company",
@@ -90,11 +91,20 @@
"fieldtype": "Table",
"label": "Other Taxes and Charges",
"options": "Income Tax Slab Other Charges"
+ },
+ {
+ "default": "Company:company:default_currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 20:27:13.425084",
+ "modified": "2020-10-19 13:54:24.728075",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Income Tax Slab",
diff --git a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
index 7f21204..0dba338 100644
--- a/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
+++ b/erpnext/payroll/doctype/income_tax_slab_other_charges/income_tax_slab_other_charges.json
@@ -45,7 +45,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Min Taxable Income",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"fieldname": "column_break_7",
@@ -57,12 +57,12 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Max Taxable Income",
- "options": "Company:company:default_currency"
+ "options": "currency"
}
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 23:33:17.931912",
+ "modified": "2020-10-19 13:45:12.850090",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Income Tax Slab Other Charges",
diff --git a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
index bb68e18..8a55224 100644
--- a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
+++ b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
@@ -52,7 +52,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 23:25:13.779032",
+ "modified": "2020-09-30 12:40:07.999878",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Employee Detail",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 1abc869..cb48abb 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -17,6 +17,16 @@
}
};
});
+
+ frm.set_query("payroll_payable_account", function() {
+ return {
+ filters: {
+ "company": frm.doc.company,
+ "root_type": "Liability",
+ "is_group": 0,
+ }
+ };
+ });
},
refresh: function(frm) {
@@ -139,6 +149,36 @@
frm.events.clear_employee_table(frm);
},
+ currency: function (frm) {
+ var company_currency;
+ if (!frm.doc.company) {
+ company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
+ } else {
+ company_currency = erpnext.get_currency(frm.doc.company);
+ }
+ if (frm.doc.currency) {
+ if (company_currency != frm.doc.currency) {
+ frappe.call({
+ method: "erpnext.setup.utils.get_exchange_rate",
+ args: {
+ from_currency: frm.doc.currency,
+ to_currency: company_currency,
+ },
+ callback: function(r) {
+ frm.set_value("exchange_rate", flt(r.message));
+ frm.set_df_property('exchange_rate', 'hidden', 0);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+ + " = [?] " + company_currency);
+ }
+ });
+ } else {
+ frm.set_value("exchange_rate", 1.0);
+ frm.set_df_property('exchange_rate', 'hidden', 1);
+ frm.set_df_property("exchange_rate", "description", "" );
+ }
+ }
+ },
+
department: function (frm) {
frm.events.clear_employee_table(frm);
},
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 31a8996..7a48dd1 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -11,8 +11,11 @@
"column_break0",
"posting_date",
"payroll_frequency",
- "column_break1",
"company",
+ "column_break1",
+ "currency",
+ "exchange_rate",
+ "payroll_payable_account",
"section_break_8",
"branch",
"department",
@@ -257,12 +260,37 @@
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "company",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Currency",
+ "options": "Currency",
+ "reqd": 1
+ },
+ {
+ "depends_on": "company",
+ "fieldname": "exchange_rate",
+ "fieldtype": "Float",
+ "label": "Exchange Rate",
+ "precision": "9",
+ "reqd": 1
+ },
+ {
+ "depends_on": "company",
+ "fieldname": "payroll_payable_account",
+ "fieldtype": "Link",
+ "label": "Payroll Payable Account",
+ "options": "Account",
+ "reqd": 1
}
],
"icon": "fa fa-cog",
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 20:06:06.953904",
+ "modified": "2020-10-23 13:00:33.753228",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll Entry",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index a3d12c3..8c2d974 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -3,7 +3,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe.model.document import Document
from dateutil.relativedelta import relativedelta
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff
@@ -51,13 +51,15 @@
where
docstatus = 1 and
is_active = 'Yes'
- and company = %(company)s and
+ and company = %(company)s
+ and currency = %(currency)s and
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
{condition}""".format(condition=condition),
- {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+ {"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
if sal_struct:
cond += "and t2.salary_structure IN %(sal_struct)s "
+ cond += "and t2.payroll_payable_account = %(payroll_payable_account)s "
cond += "and %(from_date)s >= t2.from_date"
emp_list = frappe.db.sql("""
select
@@ -68,14 +70,26 @@
t1.name = t2.employee
and t2.docstatus = 1
%s order by t2.from_date desc
- """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date}, as_dict=True)
+ """ % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
return emp_list
def fill_employee_details(self):
self.set('employees', [])
employees = self.get_emp_list()
if not employees:
- frappe.throw(_("No employees for the mentioned criteria"))
+ error_msg = _("No employees found for the mentioned criteria:<br>Company: {0}<br> Currency: {1}<br>Payroll Payable Account: {2}").format(
+ frappe.bold(self.company), frappe.bold(self.currency), frappe.bold(self.payroll_payable_account))
+ if self.branch:
+ error_msg += "<br>" + _("Branch: {0}").format(frappe.bold(self.branch))
+ if self.department:
+ error_msg += "<br>" + _("Department: {0}").format(frappe.bold(self.department))
+ if self.designation:
+ error_msg += "<br>" + _("Designation: {0}").format(frappe.bold(self.designation))
+ if self.start_date:
+ error_msg += "<br>" + _("Start date: {0}").format(frappe.bold(self.start_date))
+ if self.end_date:
+ error_msg += "<br>" + _("End date: {0}").format(frappe.bold(self.end_date))
+ frappe.throw(error_msg, title=_("No employees found"))
for d in employees:
self.append('employees', d)
@@ -123,7 +137,9 @@
"posting_date": self.posting_date,
"deduct_tax_for_unclaimed_employee_benefits": self.deduct_tax_for_unclaimed_employee_benefits,
"deduct_tax_for_unsubmitted_tax_exemption_proof": self.deduct_tax_for_unsubmitted_tax_exemption_proof,
- "payroll_entry": self.name
+ "payroll_entry": self.name,
+ "exchange_rate": self.exchange_rate,
+ "currency": self.currency
})
if len(emp_list) > 30:
frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
@@ -160,10 +176,10 @@
def get_salary_component_account(self, salary_component):
account = frappe.db.get_value("Salary Component Account",
- {"parent": salary_component, "company": self.company}, "default_account")
+ {"parent": salary_component, "company": self.company}, "account")
if not account:
- frappe.throw(_("Please set default account in Salary Component {0}")
+ frappe.throw(_("Please set account in Salary Component {0}")
.format(salary_component))
return account
@@ -203,21 +219,11 @@
account_dict[(account, key[1])] = account_dict.get((account, key[1]), 0) + amount
return account_dict
- def get_default_payroll_payable_account(self):
- payroll_payable_account = frappe.get_cached_value('Company',
- {"company_name": self.company}, "default_payroll_payable_account")
-
- if not payroll_payable_account:
- frappe.throw(_("Please set Default Payroll Payable Account in Company {0}")
- .format(self.company))
-
- return payroll_payable_account
-
def make_accrual_jv_entry(self):
self.check_permission('write')
earnings = self.get_salary_component_total(component_type = "earnings") or {}
deductions = self.get_salary_component_total(component_type = "deductions") or {}
- default_payroll_payable_account = self.get_default_payroll_payable_account()
+ payroll_payable_account = self.payroll_payable_account
jv_name = ""
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
@@ -230,14 +236,19 @@
journal_entry.posting_date = self.posting_date
accounts = []
+ currencies = []
payable_amount = 0
+ multi_currency = 0
+ company_currency = erpnext.get_company_currency(self.company)
# Earnings
for acc_cc, amount in earnings.items():
+ exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount += flt(amount, precision)
accounts.append({
"account": acc_cc[0],
- "debit_in_account_currency": flt(amount, precision),
+ "debit_in_account_currency": flt(amt, precision),
+ "exchange_rate": flt(exchange_rate),
"party_type": '',
"cost_center": acc_cc[1] or self.cost_center,
"project": self.project
@@ -245,25 +256,32 @@
# Deductions
for acc_cc, amount in deductions.items():
+ exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
payable_amount -= flt(amount, precision)
accounts.append({
"account": acc_cc[0],
- "credit_in_account_currency": flt(amount, precision),
+ "credit_in_account_currency": flt(amt, precision),
+ "exchange_rate": flt(exchange_rate),
"cost_center": acc_cc[1] or self.cost_center,
"party_type": '',
"project": self.project
})
# Payable amount
+ exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
accounts.append({
- "account": default_payroll_payable_account,
- "credit_in_account_currency": flt(payable_amount, precision),
+ "account": payroll_payable_account,
+ "credit_in_account_currency": flt(payable_amt, precision),
+ "exchange_rate": flt(exchange_rate),
"party_type": '',
"cost_center": self.cost_center
})
journal_entry.set("accounts", accounts)
- journal_entry.title = default_payroll_payable_account
+ if len(currencies) > 1:
+ multi_currency = 1
+ journal_entry.multi_currency = multi_currency
+ journal_entry.title = payroll_payable_account
journal_entry.save()
try:
@@ -271,10 +289,24 @@
jv_name = journal_entry.name
self.update_salary_slip_status(jv_name = jv_name)
except Exception as e:
- frappe.msgprint(e)
+ if type(e) in (str, list, tuple):
+ frappe.msgprint(e)
+ raise
return jv_name
+ def get_amount_and_exchange_rate_for_journal_entry(self, account, amount, company_currency, currencies):
+ conversion_rate = 1
+ exchange_rate = self.exchange_rate
+ account_currency = frappe.db.get_value('Account', account, 'account_currency')
+ if account_currency not in currencies:
+ currencies.append(account_currency)
+ if account_currency == company_currency:
+ conversion_rate = self.exchange_rate
+ exchange_rate = 1
+ amount = flt(amount) * flt(conversion_rate)
+ return exchange_rate, amount
+
def make_payment_entry(self):
self.check_permission('write')
@@ -303,31 +335,43 @@
self.create_journal_entry(salary_slip_total, "salary")
def create_journal_entry(self, je_payment_amount, user_remark):
- default_payroll_payable_account = self.get_default_payroll_payable_account()
+ payroll_payable_account = self.payroll_payable_account
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
+ accounts = []
+ currencies = []
+ multi_currency = 0
+ company_currency = erpnext.get_company_currency(self.company)
+
+ exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
+ accounts.append({
+ "account": self.payment_account,
+ "bank_account": self.bank_account,
+ "credit_in_account_currency": flt(amount, precision),
+ "exchange_rate": flt(exchange_rate),
+ })
+
+ exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
+ accounts.append({
+ "account": payroll_payable_account,
+ "debit_in_account_currency": flt(amount, precision),
+ "exchange_rate": flt(exchange_rate),
+ "reference_type": self.doctype,
+ "reference_name": self.name
+ })
+
+ if len(currencies) > 1:
+ multi_currency = 1
+
journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Bank Entry'
journal_entry.user_remark = _('Payment of {0} from {1} to {2}')\
.format(user_remark, self.start_date, self.end_date)
journal_entry.company = self.company
journal_entry.posting_date = self.posting_date
+ journal_entry.multi_currency = multi_currency
- payment_amount = flt(je_payment_amount, precision)
-
- journal_entry.set("accounts", [
- {
- "account": self.payment_account,
- "bank_account": self.bank_account,
- "credit_in_account_currency": payment_amount
- },
- {
- "account": default_payroll_payable_account,
- "debit_in_account_currency": payment_amount,
- "reference_type": self.doctype,
- "reference_name": self.name
- }
- ])
+ journal_entry.set("accounts", accounts)
journal_entry.save(ignore_permissions = True)
def update_salary_slip_status(self, jv_name = None):
@@ -496,6 +540,21 @@
if publish_progress:
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
title = _("Creating Salary Slips..."))
+ else:
+ salary_slip_name = frappe.db.sql(
+ '''SELECT
+ name
+ FROM `tabSalary Slip`
+ WHERE company=%s
+ AND start_date >= %s
+ AND end_date <= %s
+ AND employee = %s
+ ''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
+
+ salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
+ salary_slip_doc.exchange_rate = args.exchange_rate
+ salary_slip_doc.set_totals()
+ salary_slip_doc.db_update()
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
payroll_entry.db_set("salary_slips_created", 1)
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index b0f225d..54106c8 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -10,8 +10,8 @@
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates, get_end_date
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
- make_earning_salary_component, make_deduction_salary_component, create_account
-from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+ make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
+from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
@@ -34,10 +34,47 @@
get_salary_component_account(data.name)
employee = frappe.db.get_value("Employee", {'company': company})
- make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company)
+ company_doc = frappe.get_doc('Company', company)
+ make_salary_structure("_Test Salary Structure", "Monthly", employee, company=company, currency=company_doc.default_currency)
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
- make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date)
+ make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account=company_doc.default_payroll_payable_account,
+ currency=company_doc.default_currency)
+
+ def test_multi_currency_payroll_entry(self): # pylint: disable=no-self-use
+ company = erpnext.get_default_company()
+ employee = make_employee("test_muti_currency_employee@payroll.com", company=company)
+ for data in frappe.get_all('Salary Component', fields = ["name"]):
+ if not frappe.db.get_value('Salary Component Account',
+ {'parent': data.name, 'company': company}, 'name'):
+ get_salary_component_account(data.name)
+
+ company_doc = frappe.get_doc('Company', company)
+ salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
+ create_salary_structure_assignment(employee, salary_structure.name, company=company)
+ frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
+ salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
+ dates = get_start_end_dates('Monthly', nowdate())
+ payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
+ payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
+ payroll_entry.make_payment_entry()
+
+ salary_slip.load_from_db()
+
+ payroll_je = salary_slip.journal_entry
+ payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
+
+ self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
+ self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
+
+ payment_entry = frappe.db.sql('''
+ Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
+ Where je.name = jea.parent
+ And jea.reference_name = %s
+ ''', (payroll_entry.name), as_dict=1)
+
+ self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_debit)
+ self.assertEqual(salary_slip.base_net_pay, payment_entry[0].total_credit)
def test_payroll_entry_with_employee_cost_center(self): # pylint: disable=no-self-use
for data in frappe.get_all('Salary Component', fields = ["name"]):
@@ -52,24 +89,32 @@
"company": "_Test Company"
}).insert()
+ frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee1@example.com' """)
+ frappe.db.sql("""delete from `tabEmployee` where employee_name='test_employee2@example.com' """)
+ frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 1' """)
+ frappe.db.sql("""delete from `tabSalary Structure` where name='_Test Salary Structure 2' """)
+
employee1 = make_employee("test_employee1@example.com", payroll_cost_center="_Test Cost Center - _TC",
department="cc - _TC", company="_Test Company")
employee2 = make_employee("test_employee2@example.com", payroll_cost_center="_Test Cost Center 2 - _TC",
department="cc - _TC", company="_Test Company")
- make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company")
- make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company")
-
if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
- create_account(account_name="_Test Payroll Payable",
- company="_Test Company", parent_account="Current Liabilities - _TC")
- frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
- "_Test Payroll Payable - _TC")
+ create_account(account_name="_Test Payroll Payable",
+ company="_Test Company", parent_account="Current Liabilities - _TC")
+
+ if not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") or \
+ frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
+ frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
+ "_Test Payroll Payable - _TC")
+
+ make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
+ make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
dates = get_start_end_dates('Monthly', nowdate())
if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
- pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
- department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
+ pe = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, payable_account="_Test Payroll Payable - _TC",
+ currency=frappe.db.get_value("Company", "_Test Company", "default_currency"), department="cc - _TC", company="_Test Company", payment_account="Cash - _TC", cost_center="Main - _TC")
je = frappe.db.get_value("Salary Slip", {"payroll_entry": pe.name}, "journal_entry")
je_entries = frappe.db.sql("""
select account, cost_center, debit, credit
@@ -121,7 +166,7 @@
employee_doc.save()
salary_structure = "Test Salary Structure for Loan"
- make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company")
+ make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1
@@ -133,8 +178,8 @@
dates = get_start_end_dates('Monthly', nowdate())
- make_payroll_entry(company="_Test Company", start_date=dates.start_date,
- end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
+ make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
+ currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
name = frappe.db.get_value('Salary Slip',
{'posting_date': nowdate(), 'employee': applicant}, 'name')
@@ -165,6 +210,9 @@
payroll_entry.payroll_frequency = "Monthly"
payroll_entry.branch = args.branch or None
payroll_entry.department = args.department or None
+ payroll_entry.payroll_payable_account = args.payable_account
+ payroll_entry.currency = args.currency
+ payroll_entry.exchange_rate = args.exchange_rate or 1
if args.cost_center:
payroll_entry.cost_center = args.cost_center
@@ -212,3 +260,11 @@
}).insert()
return holiday_list_name
+
+def get_salary_slip(user, period, salary_structure):
+ salary_slip = make_employee_salary_slip(user, period, salary_structure)
+ salary_slip.exchange_rate = 70
+ salary_slip.calculate_net_pay()
+ salary_slip.db_update()
+
+ return salary_slip
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js b/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
index 8ff5515..092cbd8 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
+++ b/erpnext/payroll/doctype/payroll_entry/test_set_salary_components.js
@@ -9,45 +9,45 @@
() => {
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
row.company = 'For Testing';
- row.default_account = 'Salary - FT';
+ row.account = 'Salary - FT';
},
() => cur_frm.save(),
() => frappe.timeout(2),
- () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+ () => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
() => frappe.set_route('Form', 'Salary Component', 'Basic'),
() => {
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
row.company = 'For Testing';
- row.default_account = 'Salary - FT';
+ row.account = 'Salary - FT';
},
() => cur_frm.save(),
() => frappe.timeout(2),
- () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+ () => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
() => frappe.set_route('Form', 'Salary Component', 'Income Tax'),
() => {
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
row.company = 'For Testing';
- row.default_account = 'Salary - FT';
+ row.account = 'Salary - FT';
},
() => cur_frm.save(),
() => frappe.timeout(2),
- () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+ () => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
() => frappe.set_route('Form', 'Salary Component', 'Arrear'),
() => {
var row = frappe.model.add_child(cur_frm.doc, "Salary Component Account", "accounts");
row.company = 'For Testing';
- row.default_account = 'Salary - FT';
+ row.account = 'Salary - FT';
},
() => cur_frm.save(),
() => frappe.timeout(2),
- () => assert.equal(cur_frm.doc.accounts[0].default_account, 'Salary - FT'),
+ () => assert.equal(cur_frm.doc.accounts[0].account, 'Salary - FT'),
() => frappe.set_route('Form', 'Company', 'For Testing'),
() => cur_frm.set_value('default_payroll_payable_account', 'Payroll Payable - FT'),
diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.py b/erpnext/payroll/doctype/payroll_period/payroll_period.py
index d7893d0..1c8cc53 100644
--- a/erpnext/payroll/doctype/payroll_period/payroll_period.py
+++ b/erpnext/payroll/doctype/payroll_period/payroll_period.py
@@ -41,7 +41,7 @@
if overlap_doc:
msg = _("A {0} exists between {1} and {2} (").format(self.doctype,
formatdate(self.start_date), formatdate(self.end_date)) \
- + """ <b><a href="#Form/{0}/{1}">{1}</a></b>""".format(self.doctype, overlap_doc[0].name) \
+ + """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(self.doctype, overlap_doc[0].name) \
+ _(") for {0}").format(self.company)
frappe.throw(msg)
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
index 64e726d..f8bb40a 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
@@ -4,9 +4,13 @@
frappe.ui.form.on('Retention Bonus', {
setup: function(frm) {
frm.set_query("employee", function() {
+ if (!frm.doc.company) {
+ frappe.msgprint(__("Please Select Company First"));
+ }
return {
filters: {
- "status": "Active"
+ "status": "Active",
+ "company": frm.doc.company
}
};
});
@@ -18,5 +22,22 @@
}
};
});
+ },
+
+ employee: function(frm) {
+ if (frm.doc.employee) {
+ frappe.call({
+ method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+ args: {
+ employee: frm.doc.employee,
+ },
+ callback: function(r) {
+ if (r.message) {
+ frm.set_value('currency', r.message);
+ frm.refresh_fields();
+ }
+ }
+ });
+ }
}
});
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index da884c2..6647230 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -17,7 +17,8 @@
"column_break_6",
"employee_name",
"department",
- "date_of_joining"
+ "date_of_joining",
+ "currency"
],
"fields": [
{
@@ -46,6 +47,7 @@
"fieldname": "bonus_amount",
"fieldtype": "Currency",
"label": "Bonus Amount",
+ "options": "currency",
"reqd": 1
},
{
@@ -89,11 +91,22 @@
"label": "Salary Component",
"options": "Salary Component",
"reqd": 1
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 22:42:05.251951",
+ "modified": "2020-10-20 17:27:47.003134",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Retention Bonus",
@@ -151,7 +164,6 @@
"share": 1
}
],
- "quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index c455eb3..dbf7514 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -3,7 +3,7 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
- frm.set_query("default_account", "accounts", function(doc, cdt, cdn) {
+ frm.set_query("account", "accounts", function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
return {
filters: {
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index eedb56e..5c1eb61 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -147,7 +147,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"default": "0",
@@ -160,7 +160,7 @@
"fieldname": "default_amount",
"fieldtype": "Currency",
"label": "Default Amount",
- "options": "Company:company:default_currency",
+ "options": "currency",
"print_hide": 1
},
{
@@ -169,6 +169,7 @@
"hidden": 1,
"label": "Additional Amount",
"no_copy": 1,
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -177,6 +178,7 @@
"fieldname": "tax_on_flexible_benefit",
"fieldtype": "Currency",
"label": "Tax on flexible benefit",
+ "options": "currency",
"read_only": 1
},
{
@@ -184,6 +186,7 @@
"fieldname": "tax_on_additional_salary",
"fieldtype": "Currency",
"label": "Tax on additional salary",
+ "options": "currency",
"read_only": 1
},
{
@@ -227,7 +230,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-10-07 20:39:41.619283",
+ "modified": "2020-11-25 13:12:41.081106",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index 7b69dbe..f7e22c6 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -13,12 +13,12 @@
];
});
- frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
+ frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function() {
return {
filters: {
employee: frm.doc.employee
}
- }
+ };
};
frm.set_query("salary_component", "earnings", function() {
@@ -26,7 +26,7 @@
filters: {
type: "earning"
}
- }
+ };
});
frm.set_query("salary_component", "deductions", function() {
@@ -34,18 +34,18 @@
filters: {
type: "deduction"
}
- }
+ };
});
frm.set_query("employee", function() {
- return{
+ return {
query: "erpnext.controllers.queries.employee_query"
- }
+ };
});
},
- start_date: function(frm){
- if(frm.doc.start_date){
+ start_date: function(frm) {
+ if (frm.doc.start_date) {
frm.trigger("set_end_date");
}
},
@@ -54,7 +54,7 @@
frm.events.get_emp_and_working_day_details(frm);
},
- set_end_date: function(frm){
+ set_end_date: function(frm) {
frappe.call({
method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
args: {
@@ -66,22 +66,93 @@
frm.set_value('end_date', r.message.end_date);
}
}
- })
+ });
},
company: function(frm) {
var company = locals[':Company'][frm.doc.company];
- if(!frm.doc.letter_head && company.default_letter_head) {
+ if (!frm.doc.letter_head && company.default_letter_head) {
frm.set_value('letter_head', company.default_letter_head);
}
+ frm.trigger("set_dynamic_labels");
+ },
+
+ set_dynamic_labels: function(frm) {
+ var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
+ frappe.run_serially([
+ () => frm.events.set_exchange_rate(frm, company_currency),
+ () => frm.events.change_form_labels(frm, company_currency),
+ () => frm.events.change_grid_labels(frm),
+ () => frm.refresh_fields()
+ ]);
+ },
+
+ set_exchange_rate: function(frm, company_currency) {
+ if (frm.doc.docstatus === 0) {
+ if (frm.doc.currency) {
+ var from_currency = frm.doc.currency;
+ if (from_currency != company_currency) {
+ frm.events.hide_loan_section(frm);
+ frappe.call({
+ method: "erpnext.setup.utils.get_exchange_rate",
+ args: {
+ from_currency: from_currency,
+ to_currency: company_currency,
+ },
+ callback: function(r) {
+ frm.set_value("exchange_rate", flt(r.message));
+ frm.set_df_property('exchange_rate', 'hidden', 0);
+ frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+ + " = [?] " + company_currency);
+ }
+ });
+ } else {
+ frm.set_value("exchange_rate", 1.0);
+ frm.set_df_property('exchange_rate', 'hidden', 1);
+ frm.set_df_property("exchange_rate", "description", "" );
+ }
+ }
+ }
},
+ exchange_rate: function(frm) {
+ calculate_totals(frm);
+ },
+
+ hide_loan_section: function(frm) {
+ frm.set_df_property('section_break_43', 'hidden', 1);
+ },
+
+ change_form_labels: function(frm, company_currency) {
+ frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
+ "base_net_pay", "base_rounded_total", "base_total_in_words"],
+ company_currency);
+
+ frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"],
+ frm.doc.currency);
+
+ // toggle fields
+ frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
+ "base_net_pay", "base_rounded_total", "base_total_in_words"],
+ frm.doc.currency != company_currency);
+ },
+
+ change_grid_labels: function(frm) {
+ frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
+ "tax_on_additional_salary"], frm.doc.currency, "earnings");
+
+ frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
+ "tax_on_additional_salary"], frm.doc.currency, "deductions");
+ },
+
refresh: function(frm) {
- frm.trigger("toggle_fields")
+ frm.trigger("toggle_fields");
var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
- cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
- cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
+ frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
+ frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
+ calculate_totals(frm);
+ frm.trigger("set_dynamic_labels");
},
salary_slip_based_on_timesheet: function(frm) {
@@ -98,12 +169,12 @@
frm.events.get_emp_and_working_day_details(frm);
},
- leave_without_pay: function(frm){
+ leave_without_pay: function(frm) {
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({
method: 'process_salary_based_on_working_days',
doc: frm.doc,
- callback: function(r, rt) {
+ callback: function() {
frm.refresh();
}
});
@@ -118,51 +189,94 @@
},
get_emp_and_working_day_details: function(frm) {
- return frappe.call({
- method: 'get_emp_and_working_day_details',
- doc: frm.doc,
- callback: function(r, rt) {
- frm.refresh();
- if (r.message){
- frm.fields_dict.absent_days.set_description("Unmarked Days is treated as "+ r.message +". You can can change this in " + frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true));
+ if (frm.doc.employee) {
+ return frappe.call({
+ method: 'get_emp_and_working_day_details',
+ doc: frm.doc,
+ callback: function(r) {
+ if (r.message[1] !== "Leave" && r.message[0]) {
+ frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as {0}. You can can change this in {1}", [r.message, frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)]));
+ }
+ frm.refresh();
}
- }
- });
+ });
+ }
}
});
frappe.ui.form.on('Salary Slip Timesheet', {
- time_sheet: function(frm, dt, dn) {
- total_work_hours(frm, dt, dn);
+ time_sheet: function(frm) {
+ calculate_totals(frm);
},
- timesheets_remove: function(frm, dt, dn) {
- total_work_hours(frm, dt, dn);
+ timesheets_remove: function(frm) {
+ calculate_totals(frm);
}
});
-// calculate total working hours, earnings based on hourly wages and totals
-var total_work_hours = function(frm, dt, dn) {
- var total_working_hours = 0.0;
- $.each(frm.doc["timesheets"] || [], function(i, timesheet) {
- total_working_hours += timesheet.working_hours;
- });
- frm.set_value('total_working_hours', total_working_hours);
-
- var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate;
-
- frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => {
- var gross_pay = 0.0;
- $.each(frm.doc["earnings"], function(i, earning) {
- if (earning.salary_component == r.salary_component) {
- earning.amount = wages_amount;
- frm.refresh_fields('earnings');
+var calculate_totals = function(frm) {
+ if (frm.doc.earnings || frm.doc.deductions) {
+ frappe.call({
+ method: "set_totals",
+ doc: frm.doc,
+ callback: function() {
+ frm.refresh_fields();
}
- gross_pay += earning.amount;
});
- frm.set_value('gross_pay', gross_pay);
+ }
+};
- frm.doc.net_pay = flt(frm.doc.gross_pay) - flt(frm.doc.total_deduction);
- frm.doc.rounded_total = Math.round(frm.doc.net_pay);
- refresh_many(['net_pay', 'rounded_total']);
- });
-}
+frappe.ui.form.on('Salary Detail', {
+ amount: function(frm) {
+ calculate_totals(frm);
+ },
+
+ earnings_remove: function(frm) {
+ calculate_totals(frm);
+ },
+
+ deductions_remove: function(frm) {
+ calculate_totals(frm);
+ },
+
+ salary_component: function(frm, cdt, cdn) {
+ var child = locals[cdt][cdn];
+ if (child.salary_component) {
+ frappe.call({
+ method: "frappe.client.get",
+ args: {
+ doctype: "Salary Component",
+ name: child.salary_component
+ },
+ callback: function(data) {
+ if (data.message) {
+ var result = data.message;
+ frappe.model.set_value(cdt, cdn, 'condition', result.condition);
+ frappe.model.set_value(cdt, cdn, 'amount_based_on_formula', result.amount_based_on_formula);
+ if (result.amount_based_on_formula === 1) {
+ frappe.model.set_value(cdt, cdn, 'formula', result.formula);
+ } else {
+ frappe.model.set_value(cdt, cdn, 'amount', result.amount);
+ }
+ frappe.model.set_value(cdt, cdn, 'statistical_component', result.statistical_component);
+ frappe.model.set_value(cdt, cdn, 'depends_on_payment_days', result.depends_on_payment_days);
+ frappe.model.set_value(cdt, cdn, 'do_not_include_in_total', result.do_not_include_in_total);
+ frappe.model.set_value(cdt, cdn, 'variable_based_on_taxable_salary', result.variable_based_on_taxable_salary);
+ frappe.model.set_value(cdt, cdn, 'is_tax_applicable', result.is_tax_applicable);
+ frappe.model.set_value(cdt, cdn, 'is_flexible_benefit', result.is_flexible_benefit);
+ refresh_field("earnings");
+ refresh_field("deductions");
+ }
+ }
+ });
+ }
+ },
+
+ amount_based_on_formula: function(frm, cdt, cdn) {
+ var child = locals[cdt][cdn];
+ if (child.amount_based_on_formula === 1) {
+ frappe.model.set_value(cdt, cdn, 'amount', null);
+ } else {
+ frappe.model.set_value(cdt, cdn, 'formula', null);
+ }
+ }
+});
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 619c45f..386618c 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -18,6 +18,8 @@
"journal_entry",
"payroll_entry",
"company",
+ "currency",
+ "exchange_rate",
"letter_head",
"section_break_10",
"start_date",
@@ -38,6 +40,7 @@
"column_break_20",
"total_working_hours",
"hour_rate",
+ "base_hour_rate",
"section_break_26",
"bank_name",
"bank_account_no",
@@ -52,8 +55,10 @@
"deductions",
"totals",
"gross_pay",
+ "base_gross_pay",
"column_break_25",
"total_deduction",
+ "base_total_deduction",
"loan_repayment",
"loans",
"section_break_43",
@@ -63,10 +68,15 @@
"total_loan_repayment",
"net_pay_info",
"net_pay",
+ "base_net_pay",
"column_break_53",
"rounded_total",
+ "base_rounded_total",
"section_break_55",
"total_in_words",
+ "column_break_69",
+ "base_total_in_words",
+ "section_break_75",
"amended_from"
],
"fields": [
@@ -205,9 +215,13 @@
{
"fieldname": "salary_structure",
"fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Salary Structure",
"options": "Salary Structure",
- "read_only": 1
+ "read_only": 1,
+ "reqd": 1,
+ "search_index": 1
},
{
"depends_on": "eval:(!doc.salary_slip_based_on_timesheet)",
@@ -265,7 +279,7 @@
"fieldname": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
- "options": "Company:company:default_currency",
+ "options": "currency",
"print_hide_if_no_value": 1
},
{
@@ -347,9 +361,7 @@
"fieldname": "gross_pay",
"fieldtype": "Currency",
"label": "Gross Pay",
- "oldfieldname": "gross_pay",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -357,15 +369,6 @@
"fieldtype": "Column Break"
},
{
- "fieldname": "total_deduction",
- "fieldtype": "Currency",
- "label": "Total Deduction",
- "oldfieldname": "total_deduction",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
"depends_on": "total_loan_repayment",
"fieldname": "loan_repayment",
"fieldtype": "Section Break",
@@ -379,6 +382,7 @@
"print_hide": 1
},
{
+ "depends_on": "eval:doc.docstatus != 0",
"fieldname": "section_break_43",
"fieldtype": "Section Break"
},
@@ -416,13 +420,10 @@
"label": "net pay info"
},
{
- "description": "Gross Pay - Total Deduction - Loan Repayment",
"fieldname": "net_pay",
"fieldtype": "Currency",
"label": "Net Pay",
- "oldfieldname": "net_pay",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -434,7 +435,7 @@
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
- "options": "Company:company:default_currency",
+ "options": "currency",
"read_only": 1
},
{
@@ -442,15 +443,6 @@
"fieldtype": "Section Break"
},
{
- "description": "Net Pay (in words) will be visible once you save the Salary Slip.",
- "fieldname": "total_in_words",
- "fieldtype": "Data",
- "label": "Total in words",
- "oldfieldname": "net_pay_in_words",
- "oldfieldtype": "Data",
- "read_only": 1
- },
- {
"fieldname": "amended_from",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -500,13 +492,99 @@
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
+ "fetch_from": "salary_structure.currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "total_deduction",
+ "fieldtype": "Currency",
+ "label": "Total Deduction",
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_in_words",
+ "fieldtype": "Data",
+ "label": "Total in words",
+ "length": 240,
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_75",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "base_hour_rate",
+ "fieldtype": "Currency",
+ "label": "Hour Rate (Company Currency)",
+ "options": "Company:company:default_currency",
+ "print_hide_if_no_value": 1
+ },
+ {
+ "fieldname": "base_gross_pay",
+ "fieldtype": "Currency",
+ "label": "Gross Pay (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "default": "1.0",
+ "fieldname": "exchange_rate",
+ "fieldtype": "Float",
+ "hidden": 1,
+ "label": "Exchange Rate",
+ "print_hide": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "base_total_deduction",
+ "fieldtype": "Currency",
+ "label": "Total Deduction (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_net_pay",
+ "fieldtype": "Currency",
+ "label": "Net Pay (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "bold": 1,
+ "fieldname": "base_rounded_total",
+ "fieldtype": "Currency",
+ "label": "Rounded Total (Company Currency)",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_total_in_words",
+ "fieldtype": "Data",
+ "label": "Total in words (Company Currency)",
+ "length": 240,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_69",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-file-text",
"idx": 9,
"is_submittable": 1,
"links": [],
- "modified": "2020-08-11 17:37:54.274384",
+ "modified": "2020-10-21 23:02:59.400249",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index cecb8cd..20365b1 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -50,16 +50,20 @@
self.calculate_net_pay()
- company_currency = erpnext.get_company_currency(self.company)
- total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
- self.total_in_words = money_in_words(total, company_currency)
-
if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
if self.salary_slip_based_on_timesheet and (self.total_working_hours > int(max_working_hours)):
frappe.msgprint(_("Total working hours should not be greater than max working hours {0}").
format(max_working_hours), alert=True)
+ def set_net_total_in_words(self):
+ doc_currency = self.currency
+ company_currency = erpnext.get_company_currency(self.company)
+ total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
+ base_total = self.base_net_pay if self.is_rounding_total_disabled() else self.base_rounded_total
+ self.total_in_words = money_in_words(total, doc_currency)
+ self.base_total_in_words = money_in_words(base_total, company_currency)
+
def on_submit(self):
if self.net_pay < 0:
frappe.throw(_("Net Pay cannot be less than 0"))
@@ -136,8 +140,8 @@
self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
self.set_time_sheet()
self.pull_sal_struct()
- consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
- return consider_unmarked_attendance_as
+ payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
+ return [payroll_based_on, consider_unmarked_attendance_as]
def set_time_sheet(self):
if self.salary_slip_based_on_timesheet:
@@ -182,6 +186,7 @@
if self.salary_slip_based_on_timesheet:
self.salary_structure = self._salary_structure_doc.name
self.hour_rate = self._salary_structure_doc.hour_rate
+ self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
self.total_working_hours = sum([d.working_hours or 0.0 for d in self.timesheets]) or 0.0
wages_amount = self.hour_rate * self.total_working_hours
@@ -210,10 +215,10 @@
frappe.throw(_("Please set Payroll based on in Payroll settings"))
if payroll_based_on == "Attendance":
- actual_lwp, absent = self.calculate_lwp_and_absent_days_based_on_attendance(holidays)
+ actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
self.absent_days = absent
else:
- actual_lwp = self.calculate_lwp_based_on_leave_application(holidays, working_days)
+ actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
if not lwp:
lwp = actual_lwp
@@ -300,7 +305,7 @@
return holidays
- def calculate_lwp_based_on_leave_application(self, holidays, working_days):
+ def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
lwp = 0
holidays = "','".join(holidays)
daily_wages_fraction_for_half_day = \
@@ -311,10 +316,12 @@
leave = frappe.db.sql("""
SELECT t1.name,
CASE WHEN (t1.half_day_date = %(dt)s or t1.to_date = t1.from_date)
- THEN t1.half_day else 0 END
+ THEN t1.half_day else 0 END,
+ t2.is_ppl,
+ t2.fraction_of_daily_salary_per_leave
FROM `tabLeave Application` t1, `tabLeave Type` t2
WHERE t2.name = t1.leave_type
- AND t2.is_lwp = 1
+ AND (t2.is_lwp = 1 or t2.is_ppl = 1)
AND t1.docstatus = 1
AND t1.employee = %(employee)s
AND ifnull(t1.salary_slip, '') = ''
@@ -327,19 +334,35 @@
""".format(holidays), {"employee": self.employee, "dt": dt})
if leave:
+ equivalent_lwp_count = 0
is_half_day_leave = cint(leave[0][1])
- lwp += (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
+ is_partially_paid_leave = cint(leave[0][2])
+ fraction_of_daily_salary_per_leave = flt(leave[0][3])
+
+ equivalent_lwp_count = (1 - daily_wages_fraction_for_half_day) if is_half_day_leave else 1
+
+ if is_partially_paid_leave:
+ equivalent_lwp_count *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+
+ lwp += equivalent_lwp_count
return lwp
- def calculate_lwp_and_absent_days_based_on_attendance(self, holidays):
+ def calculate_lwp_ppl_and_absent_days_based_on_attendance(self, holidays):
lwp = 0
absent = 0
daily_wages_fraction_for_half_day = \
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
- lwp_leave_types = dict(frappe.get_all("Leave Type", {"is_lwp": 1}, ["name", "include_holiday"], as_list=1))
+ leave_types = frappe.get_all("Leave Type",
+ or_filters=[["is_ppl", "=", 1], ["is_lwp", "=", 1]],
+ fields =["name", "is_lwp", "is_ppl", "fraction_of_daily_salary_per_leave", "include_holiday"])
+
+ leave_type_map = {}
+ for leave_type in leave_types:
+ leave_type_map[leave_type.name] = leave_type
+
attendances = frappe.db.sql('''
SELECT attendance_date, status, leave_type
FROM `tabAttendance`
@@ -351,21 +374,30 @@
''', values=(self.employee, self.start_date, self.end_date), as_dict=1)
for d in attendances:
- if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in lwp_leave_types:
+ if d.status in ('Half Day', 'On Leave') and d.leave_type and d.leave_type not in leave_type_map.keys():
continue
if formatdate(d.attendance_date, "yyyy-mm-dd") in holidays:
if d.status == "Absent" or \
- (d.leave_type and d.leave_type in lwp_leave_types and not lwp_leave_types[d.leave_type]):
+ (d.leave_type and d.leave_type in leave_type_map.keys() and not leave_type_map[d.leave_type]['include_holiday']):
continue
+ if d.leave_type:
+ fraction_of_daily_salary_per_leave = leave_type_map[d.leave_type]["fraction_of_daily_salary_per_leave"]
+
if d.status == "Half Day":
- lwp += (1 - daily_wages_fraction_for_half_day)
- elif d.status == "On Leave" and d.leave_type in lwp_leave_types:
- lwp += 1
+ equivalent_lwp = (1 - daily_wages_fraction_for_half_day)
+
+ if d.leave_type in leave_type_map.keys() and leave_type_map[d.leave_type]["is_ppl"]:
+ equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+ lwp += equivalent_lwp
+ elif d.status == "On Leave" and d.leave_type and d.leave_type in leave_type_map.keys():
+ equivalent_lwp = 1
+ if leave_type_map[d.leave_type]["is_ppl"]:
+ equivalent_lwp *= fraction_of_daily_salary_per_leave if fraction_of_daily_salary_per_leave else 1
+ lwp += equivalent_lwp
elif d.status == "Absent":
absent += 1
-
return lwp, absent
def add_earning_for_hourly_wages(self, doc, salary_component, amount):
@@ -390,15 +422,22 @@
if self.salary_structure:
self.calculate_component_amounts("earnings")
self.gross_pay = self.get_component_totals("earnings")
+ self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
if self.salary_structure:
self.calculate_component_amounts("deductions")
self.total_deduction = self.get_component_totals("deductions")
+ self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
self.set_loan_repayment()
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.rounded_total = rounded(self.net_pay)
+ self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
+ self.base_rounded_total = flt(rounded(self.base_net_pay), self.precision('base_net_pay'))
+ if self.hour_rate:
+ self.base_hour_rate = flt(flt(self.hour_rate) * flt(self.exchange_rate), self.precision('base_hour_rate'))
+ self.set_net_total_in_words()
def calculate_component_amounts(self, component_type):
if not getattr(self, '_salary_structure_doc', None):
@@ -949,9 +988,9 @@
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
if payment.total_payment > total_amount:
- frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
- against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
- frappe.bold(total_amount), frappe.bold(payment.loan)))
+ frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2} against loan {3}""")
+ .format(payment.idx, frappe.bold(payment.total_payment),
+ frappe.bold(total_amount), frappe.bold(payment.loan)))
self.total_interest_amount += payment.interest_amount
self.total_principal_amount += payment.principal_amount
@@ -1046,6 +1085,46 @@
self.get_working_days_details(lwp=self.leave_without_pay)
self.calculate_net_pay()
+ def set_totals(self):
+ self.gross_pay = 0
+ if self.salary_slip_based_on_timesheet == 1:
+ self.calculate_total_for_salary_slip_based_on_timesheet()
+ else:
+ self.total_deduction = 0
+ if self.earnings:
+ for earning in self.earnings:
+ self.gross_pay += flt(earning.amount)
+ if self.deductions:
+ for deduction in self.deductions:
+ self.total_deduction += flt(deduction.amount)
+ self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
+ self.set_base_totals()
+
+ def set_base_totals(self):
+ self.base_gross_pay = flt(self.gross_pay) * flt(self.exchange_rate)
+ self.base_total_deduction = flt(self.total_deduction) * flt(self.exchange_rate)
+ self.rounded_total = rounded(self.net_pay)
+ self.base_net_pay = flt(self.net_pay) * flt(self.exchange_rate)
+ self.base_rounded_total = rounded(self.base_net_pay)
+ self.set_net_total_in_words()
+
+ #calculate total working hours, earnings based on hourly wages and totals
+ def calculate_total_for_salary_slip_based_on_timesheet(self):
+ if self.timesheets:
+ for timesheet in self.timesheets:
+ if timesheet.working_hours:
+ self.total_working_hours += timesheet.working_hours
+
+ wages_amount = self.total_working_hours * self.hour_rate
+ self.base_hour_rate = flt(self.hour_rate) * flt(self.exchange_rate)
+ salary_component = frappe.db.get_value('Salary Structure', {'name': self.salary_structure}, 'salary_component')
+ if self.earnings:
+ for i, earning in enumerate(self.earnings):
+ if earning.salary_component == salary_component:
+ self.earnings[i].amount = wages_amount
+ self.gross_pay += self.earnings[i].amount
+ self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
+
def unlink_ref_doc_from_salary_slip(ref_no):
linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
where journal_entry=%s and docstatus < 2""", (ref_no))
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 7fe4165..5daf1d4 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -13,6 +13,8 @@
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
+from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration \
import create_payroll_period, create_exemption_category
@@ -31,7 +33,7 @@
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75)
- emp_id = make_employee("test_for_attendance@salary.com")
+ emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
@@ -53,7 +55,7 @@
mark_attendance(emp_id, add_days(first_sunday, 4), 'On Leave', leave_type='Casual Leave', ignore_validate=True) # invalid lwp
mark_attendance(emp_id, add_days(first_sunday, 7), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # invalid lwp
- ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
+ ss = make_employee_salary_slip("test_payment_days_based_on_attendance@salary.com", "Monthly", "Test Payment Based On Attendence")
self.assertEqual(ss.leave_without_pay, 1.25)
self.assertEqual(ss.absent_days, 1)
@@ -76,7 +78,7 @@
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
- emp_id = make_employee("test_for_attendance@salary.com")
+ emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
@@ -93,31 +95,40 @@
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
- ss = make_employee_salary_slip("test_for_attendance@salary.com", "Monthly")
+ leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl = 1)
+ leave_type_ppl.save()
- self.assertEqual(ss.leave_without_pay, 3)
+ alloc = create_leave_allocation(
+ employee = emp_id, from_date = add_days(first_sunday, 4),
+ to_date = add_days(first_sunday, 10), new_leaves_allocated = 3,
+ leave_type = "Test Partially Paid Leave")
+ alloc.save()
+ alloc.submit()
+
+ #two day leave ppl with fraction_of_daily_salary_per_leave = 0.5 equivalent to single day lwp
+ make_leave_application(emp_id, add_days(first_sunday, 4), add_days(first_sunday, 5), "Test Partially Paid Leave")
+
+ ss = make_employee_salary_slip("test_payment_days_based_on_leave_application@salary.com", "Monthly", "Test Payment Based On Leave Application")
+
+
+ self.assertEqual(ss.leave_without_pay, 4)
days_in_month = no_of_days[0]
no_of_holidays = no_of_days[1]
- self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 3)
-
- #Gross pay calculation based on attendances
- gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay))
-
- self.assertEqual(flt(ss.gross_pay, 2), flt(gross_pay, 2))
+ self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
def test_salary_slip_with_holidays_included(self):
no_of_days = self.get_no_of_days()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
- make_employee("test_employee@salary.com")
+ make_employee("test_salary_slip_with_holidays_included@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ {"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
- ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+ {"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "status", "Active")
+ ss = make_employee_salary_slip("test_salary_slip_with_holidays_included@salary.com", "Monthly", "Test Salary Slip With Holidays Included")
self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, no_of_days[0])
@@ -128,12 +139,12 @@
def test_salary_slip_with_holidays_excluded(self):
no_of_days = self.get_no_of_days()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
- make_employee("test_employee@salary.com")
+ make_employee("test_salary_slip_with_holidays_excluded@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ {"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
- ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+ {"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "status", "Active")
+ ss = make_employee_salary_slip("test_salary_slip_with_holidays_excluded@salary.com", "Monthly", "Test Salary Slip With Holidays Excluded")
self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1])
@@ -148,7 +159,7 @@
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
# set joinng date in the same month
- make_employee("test_employee@salary.com")
+ make_employee("test_payment_days@salary.com")
if getdate(nowdate()).day >= 15:
relieving_date = getdate(add_days(nowdate(),-10))
date_of_joining = getdate(add_days(nowdate(),-10))
@@ -163,39 +174,39 @@
relieving_date = getdate(nowdate())
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", date_of_joining)
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", date_of_joining)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
- ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+ ss = make_employee_salary_slip("test_payment_days@salary.com", "Monthly", "Test Payment Days")
self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, (no_of_days[0] - getdate(date_of_joining).day + 1))
# set relieving date in the same month
frappe.db.set_value("Employee",frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "date_of_joining", (add_days(nowdate(),-60)))
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", relieving_date)
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", relieving_date)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "status", "Left")
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Left")
ss.save()
self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, getdate(relieving_date).day)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "relieving_date", None)
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "relieving_date", None)
frappe.db.set_value("Employee", frappe.get_value("Employee",
- {"employee_name":"test_employee@salary.com"}, "name"), "status", "Active")
+ {"employee_name":"test_payment_days@salary.com"}, "name"), "status", "Active")
def test_employee_salary_slip_read_permission(self):
- make_employee("test_employee@salary.com")
+ make_employee("test_employee_salary_slip_read_permission@salary.com")
- salary_slip_test_employee = make_employee_salary_slip("test_employee@salary.com", "Monthly")
- frappe.set_user("test_employee@salary.com")
+ salary_slip_test_employee = make_employee_salary_slip("test_employee_salary_slip_read_permission@salary.com", "Monthly", "Test Employee Salary Slip Read Permission")
+ frappe.set_user("test_employee_salary_slip_read_permission@salary.com")
self.assertTrue(salary_slip_test_employee.has_permission("read"))
def test_email_salary_slip(self):
@@ -203,8 +214,8 @@
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 1)
- make_employee("test_employee@salary.com")
- ss = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+ make_employee("test_email_salary_slip@salary.com")
+ ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
ss.company = "_Test Company"
ss.save()
ss.submit()
@@ -215,8 +226,9 @@
def test_loan_repayment_salary_slip(self):
from erpnext.loan_management.doctype.loan.test_loan import create_loan_type, create_loan, make_loan_disbursement_entry, create_loan_accounts
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
+ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
- applicant = make_employee("test_loanemployee@salary.com", company="_Test Company")
+ applicant = make_employee("test_loan_repayment_salary_slip@salary.com", company="_Test Company")
create_loan_accounts()
@@ -228,6 +240,8 @@
interest_income_account='Interest Income Account - _TC',
penalty_income_account='Penalty Income Account - _TC')
+ make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR')
+ frappe.db.sql("""delete from `tabLoan""")
loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1
loan.submit()
@@ -236,7 +250,7 @@
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
- ss = make_employee_salary_slip("test_loanemployee@salary.com", "Monthly")
+ ss = make_employee_salary_slip("test_loan_repayment_salary_slip@salary.com", "Monthly", "Test Loan Repayment Salary Structure")
ss.submit()
self.assertEqual(ss.total_loan_repayment, 592)
@@ -249,7 +263,7 @@
for payroll_frequency in ["Monthly", "Bimonthly", "Fortnightly", "Weekly", "Daily"]:
make_employee(payroll_frequency + "_test_employee@salary.com")
- ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency)
+ ss = make_employee_salary_slip(payroll_frequency + "_test_employee@salary.com", payroll_frequency, payroll_frequency + "_Test Payroll Frequency")
if payroll_frequency == "Monthly":
self.assertEqual(ss.end_date, m['month_end_date'])
elif payroll_frequency == "Bimonthly":
@@ -264,6 +278,18 @@
elif payroll_frequency == "Daily":
self.assertEqual(ss.end_date, nowdate())
+ def test_multi_currency_salary_slip(self):
+ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+ applicant = make_employee("test_multi_currency_salary_slip@salary.com", company="_Test Company")
+ frappe.db.sql("""delete from `tabSalary Structure` where name='Test Multi Currency Salary Slip'""")
+ salary_structure = make_salary_structure("Test Multi Currency Salary Slip", "Monthly", employee=applicant, company="_Test Company", currency='USD')
+ salary_slip = make_salary_slip(salary_structure.name, employee = applicant)
+ salary_slip.exchange_rate = 70
+ salary_slip.calculate_net_pay()
+
+ self.assertEqual(salary_slip.gross_pay, 78000)
+ self.assertEqual(salary_slip.base_gross_pay, 78000*70)
+
def test_tax_for_payroll_period(self):
data = {}
# test the impact of tax exemption declaration, tax exemption proof submission
@@ -384,16 +410,21 @@
salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
employee = frappe.db.get_value("Employee", {"user_id": user})
- salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
- salary_slip = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
+ if not frappe.db.exists('Salary Structure', salary_structure):
+ salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
+ else:
+ salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
+ salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
- if not salary_slip:
+ if not salary_slip_name:
salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee)
salary_slip.employee_name = frappe.get_value("Employee",
{"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name")
salary_slip.payroll_frequency = payroll_frequency
salary_slip.posting_date = nowdate()
salary_slip.insert()
+ else:
+ salary_slip = frappe.get_doc('Salary Slip', salary_slip_name)
return salary_slip
@@ -434,7 +465,7 @@
sal_comp.append("accounts", {
"company": d,
- "default_account": create_account(account_name, d, parent_account)
+ "account": create_account(account_name, d, parent_account)
})
sal_comp.save()
@@ -561,7 +592,8 @@
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"payroll_period": payroll_period,
- "company": erpnext.get_default_company()
+ "company": erpnext.get_default_company(),
+ "currency": erpnext.get_default_currency()
})
declaration.append("declarations", {
"exemption_sub_category": "_Test Sub Category",
@@ -576,7 +608,8 @@
"doctype": "Employee Tax Exemption Proof Submission",
"employee": employee,
"payroll_period": payroll_period.name,
- "submission_date": submission_date
+ "submission_date": submission_date,
+ "currency": erpnext.get_default_currency()
})
proof_submission.append("tax_exemption_proofs", {
"exemption_sub_category": "_Test Sub Category",
@@ -593,13 +626,13 @@
"employee": employee,
"claimed_amount": amount,
"claim_date": claim_date,
- "earning_component": component
+ "earning_component": component,
+ "currency": erpnext.get_default_currency()
}).submit()
return claim_date
-def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False):
- if frappe.db.exists("Income Tax Slab", "Tax Slab: " + payroll_period.name):
- return
+def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()):
+ frappe.db.sql("""delete from `tabIncome Tax Slab`""")
slabs = [
{
@@ -622,6 +655,7 @@
income_tax_slab = frappe.new_doc("Income Tax Slab")
income_tax_slab.name = "Tax Slab: " + payroll_period.name
income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
+ income_tax_slab.currency = currency
if allow_tax_exemption:
income_tax_slab.allow_tax_exemption = 1
@@ -672,7 +706,8 @@
"salary_component": "Performance Bonus",
"payroll_date": salary_date,
"amount": amount,
- "type": "Earning"
+ "type": "Earning",
+ "currency": erpnext.get_default_currency()
}).submit()
return salary_date
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index ad93a2f..ba824c5 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -41,20 +41,6 @@
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet)
- frm.set_query("salary_component", "earnings", function() {
- return {
- filters: {
- type: "earning"
- }
- }
- });
- frm.set_query("salary_component", "deductions", function() {
- return {
- filters: {
- type: "deduction"
- }
- }
- });
frm.set_query("payment_account", function () {
var account_types = ["Bank", "Cash"];
return {
@@ -65,9 +51,47 @@
}
};
});
+ frm.trigger('set_earning_deduction_component');
+ },
+
+ set_earning_deduction_component: function(frm) {
+ if(!frm.doc.company) return;
+ frm.set_query("salary_component", "earnings", function() {
+ return {
+ query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+ filters: {type: "earning", company: frm.doc.company}
+ };
+ });
+ frm.set_query("salary_component", "deductions", function() {
+ return {
+ query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
+ filters: {type: "deduction", company: frm.doc.company}
+ };
+ });
+ },
+
+
+ currency: function(frm) {
+ calculate_totals(frm.doc);
+ frm.trigger("set_dynamic_labels")
+ frm.refresh()
+ },
+
+ set_dynamic_labels: function(frm) {
+ frm.set_currency_labels(["net_pay","hour_rate", "leave_encashment_amount_per_day", "max_benefits", "total_earning",
+ "total_deduction"], frm.doc.currency);
+
+ frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
+ frm.doc.currency, "earnings");
+
+ frm.set_currency_labels(["amount", "additional_amount", "tax_on_flexible_benefit", "tax_on_additional_salary"],
+ frm.doc.currency, "deductions");
+
+ frm.refresh_fields();
},
refresh: function(frm) {
+ frm.trigger("set_dynamic_labels")
frm.trigger("toggle_fields");
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
@@ -101,10 +125,12 @@
fields: [
{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
+ {fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
- {fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
+ {fieldname:"employee", fieldtype: "Link", options: "Employee", label: __("Employee")},
+ {fieldname:"payroll_payable_account", fieldtype: "Link", options: "Account", filters: {"company": frm.doc.company, "root_type": "Liability", "is_group": 0, "account_currency": frm.doc.currency}, label: __("Payroll Payable Account")},
{fieldname:'base_variable', fieldtype:'Section Break'},
{fieldname:'from_date', fieldtype:'Date', label: __('From Date'), "reqd": 1},
{fieldname:'income_tax_slab', fieldtype:'Link', label: __('Income Tax Slab'), options: 'Income Tax Slab'},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.json b/erpnext/payroll/doctype/salary_structure/salary_structure.json
index 5f94929..de56fc8 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.json
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.json
@@ -13,6 +13,7 @@
"column_break1",
"is_active",
"payroll_frequency",
+ "currency",
"is_default",
"time_sheet_earning_detail",
"salary_slip_based_on_timesheet",
@@ -26,9 +27,9 @@
"deductions",
"conditions_and_formula_variable_and_example",
"net_pay_detail",
- "column_break2",
"total_earning",
"total_deduction",
+ "column_break2",
"net_pay",
"account",
"mode_of_payment",
@@ -43,23 +44,17 @@
"label": "Company",
"options": "Company",
"remember_last_selected_value": 1,
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "letter_head",
"fieldtype": "Link",
"label": "Letter Head",
- "options": "Letter Head",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Letter Head"
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -72,9 +67,7 @@
"oldfieldname": "is_active",
"oldfieldtype": "Select",
"options": "\nYes\nNo",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"default": "Monthly",
@@ -82,9 +75,7 @@
"fieldname": "payroll_frequency",
"fieldtype": "Select",
"label": "Payroll Frequency",
- "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily",
- "show_days": 1,
- "show_seconds": 1
+ "options": "\nMonthly\nFortnightly\nBimonthly\nWeekly\nDaily"
},
{
"default": "No",
@@ -95,62 +86,46 @@
"no_copy": 1,
"options": "Yes\nNo",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "time_sheet_earning_detail",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "salary_slip_based_on_timesheet",
"fieldtype": "Check",
- "label": "Salary Slip Based on Timesheet",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Salary Slip Based on Timesheet"
},
{
"fieldname": "column_break_17",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"description": "Salary Component for timesheet based payroll.",
"fieldname": "salary_component",
"fieldtype": "Link",
"label": "Salary Component",
- "options": "Salary Component",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Component"
},
{
"fieldname": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
- "options": "Company:company:default_currency",
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency"
},
{
"fieldname": "leave_encashment_amount_per_day",
"fieldtype": "Currency",
"label": "Leave Encashment Amount Per Day",
- "options": "Company:company:default_currency",
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency"
},
{
"fieldname": "max_benefits",
"fieldtype": "Currency",
"label": "Max Benefits (Amount)",
- "options": "Company:company:default_currency",
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency"
},
{
"description": "Salary breakup based on Earning and Deduction.",
@@ -158,9 +133,7 @@
"fieldtype": "Section Break",
"oldfieldname": "earning_deduction",
"oldfieldtype": "Section Break",
- "precision": "2",
- "show_days": 1,
- "show_seconds": 1
+ "precision": "2"
},
{
"fieldname": "earnings",
@@ -168,9 +141,7 @@
"label": "Earnings",
"oldfieldname": "earning_details",
"oldfieldtype": "Table",
- "options": "Salary Detail",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Detail"
},
{
"fieldname": "deductions",
@@ -178,22 +149,16 @@
"label": "Deductions",
"oldfieldname": "deduction_details",
"oldfieldtype": "Table",
- "options": "Salary Detail",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Salary Detail"
},
{
"fieldname": "net_pay_detail",
"fieldtype": "Section Break",
- "options": "Simple",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Simple"
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1,
"width": "50%"
},
{
@@ -201,63 +166,45 @@
"fieldtype": "Currency",
"hidden": 1,
"label": "Total Earning",
- "oldfieldname": "total_earning",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency",
+ "read_only": 1
},
{
"fieldname": "total_deduction",
"fieldtype": "Currency",
"hidden": 1,
"label": "Total Deduction",
- "oldfieldname": "total_deduction",
- "oldfieldtype": "Currency",
- "options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency",
+ "read_only": 1
},
{
"fieldname": "net_pay",
"fieldtype": "Currency",
"hidden": 1,
"label": "Net Pay",
- "options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "options": "currency",
+ "read_only": 1
},
{
"fieldname": "account",
"fieldtype": "Section Break",
- "label": "Account",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Account"
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
- "options": "Mode of Payment",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Mode of Payment"
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Payment Account",
- "options": "Account",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Account"
},
{
"fieldname": "amended_from",
@@ -266,23 +213,26 @@
"no_copy": 1,
"options": "Salary Structure",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "conditions_and_formula_variable_and_example",
"fieldtype": "HTML",
- "label": "Conditions and Formula variable and example",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Conditions and Formula variable and example"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "reqd": 1
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 17:07:26.129355",
+ "modified": "2020-09-30 11:30:32.190798",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure",
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index ffc16d7..77914bb 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe.utils import flt, cint, cstr
from frappe import _
@@ -88,24 +88,26 @@
return employees
@frappe.whitelist()
- def assign_salary_structure(self, company=None, grade=None, department=None, designation=None,employee=None,
- from_date=None, base=None, variable=None, income_tax_slab=None):
- employees = self.get_employees(company= company, grade= grade,department= department,designation= designation,name=employee)
+ def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
+ payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
+ employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
if employees:
if len(employees) > 20:
frappe.enqueue(assign_salary_structure_for_employees, timeout=600,
- employees=employees, salary_structure=self,from_date=from_date,
- base=base, variable=variable, income_tax_slab=income_tax_slab)
+ employees=employees, salary_structure=self,
+ payroll_payable_account=payroll_payable_account,
+ from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
- assign_salary_structure_for_employees(employees, self, from_date=from_date,
- base=base, variable=variable, income_tax_slab=income_tax_slab)
+ assign_salary_structure_for_employees(employees, self,
+ payroll_payable_account=payroll_payable_account,
+ from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
else:
frappe.msgprint(_("No Employee Found"))
-def assign_salary_structure_for_employees(employees, salary_structure, from_date=None, base=None, variable=None, income_tax_slab=None):
+def assign_salary_structure_for_employees(employees, salary_structure, payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
salary_structures_assignments = []
existing_assignments_for = get_existing_assignments(employees, salary_structure, from_date)
count=0
@@ -115,7 +117,7 @@
count +=1
salary_structures_assignment = create_salary_structures_assignment(employee,
- salary_structure, from_date, base, variable, income_tax_slab)
+ salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab)
salary_structures_assignments.append(salary_structures_assignment)
frappe.publish_progress(count*100/len(set(employees) - set(existing_assignments_for)), title = _("Assigning Structures..."))
@@ -123,11 +125,22 @@
frappe.msgprint(_("Structures have been assigned successfully"))
-def create_salary_structures_assignment(employee, salary_structure, from_date, base, variable, income_tax_slab=None):
+def create_salary_structures_assignment(employee, salary_structure, payroll_payable_account, from_date, base, variable, income_tax_slab=None):
+ if not payroll_payable_account:
+ payroll_payable_account = frappe.db.get_value('Company', salary_structure.company, 'default_payroll_payable_account')
+ if not payroll_payable_account:
+ frappe.throw(_('Please set "Default Payroll Payable Account" in Company Defaults'))
+ payroll_payable_account_currency = frappe.db.get_value('Account', payroll_payable_account, 'account_currency')
+ company_curency = erpnext.get_company_currency(salary_structure.company)
+ if payroll_payable_account_currency != salary_structure.currency and payroll_payable_account_currency != company_curency:
+ frappe.throw(_("Invalid Payroll Payable Account. The account currency must be {0} or {1}").format(salary_structure.currency, company_curency))
+
assignment = frappe.new_doc("Salary Structure Assignment")
assignment.employee = employee
assignment.salary_structure = salary_structure.name
assignment.company = salary_structure.company
+ assignment.currency = salary_structure.currency
+ assignment.payroll_payable_account = payroll_payable_account
assignment.from_date = from_date
assignment.base = base
assignment.variable = variable
@@ -170,7 +183,8 @@
"doctype": "Salary Slip",
"field_map": {
"total_earning": "gross_pay",
- "name": "salary_structure"
+ "name": "salary_structure",
+ "currency": "currency"
}
}
}, target_doc, postprocess, ignore_child_tables=True, ignore_permissions=ignore_permissions)
@@ -188,7 +202,22 @@
filters={'salary_structure': salary_structure, 'docstatus': 1}, fields=['employee'])
if not employees:
- frappe.throw(_("There's no Employee with Salary Structure: {0}. \
- Assign {1} to an Employee to preview Salary Slip").format(salary_structure, salary_structure))
+ frappe.throw(_("There's no Employee with Salary Structure: {0}. Assign {1} to an Employee to preview Salary Slip").format(
+ salary_structure, salary_structure))
return list(set([d.employee for d in employees]))
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
+ if len(filters) < 2:
+ return {}
+
+ return frappe.db.sql("""
+ select t1.salary_component
+ from `tabSalary Component` t1, `tabSalary Component Account` t2
+ where t1.salary_component = t2.parent
+ and t1.type = %s
+ and t2.company = %s
+ order by salary_component
+ """, (filters['type'], filters['company']) )
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index e04fda8..abb6697 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -94,7 +94,8 @@
self.assertFalse(("\n" in row.formula) or ("\n" in row.condition))
def test_salary_structures_assignment(self):
- salary_structure = make_salary_structure("Salary Structure Sample", "Monthly")
+ company_currency = erpnext.get_default_currency()
+ salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", currency=company_currency)
employee = "test_assign_stucture@salary.com"
employee_doc_name = make_employee(employee)
# clear the already assigned stuctures
@@ -107,8 +108,13 @@
self.assertEqual(salary_structure_assignment.base, 5000)
self.assertEqual(salary_structure_assignment.variable, 200)
+ def test_multi_currency_salary_structure(self):
+ make_employee("test_muti_currency_employee@salary.com")
+ sal_struct = make_salary_structure("Salary Structure Multi Currency", "Monthly", currency='USD')
+ self.assertEqual(sal_struct.currency, 'USD')
+
def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
- test_tax=False, company=None):
+ test_tax=False, company=None, currency=erpnext.get_default_currency()):
if test_tax:
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
@@ -120,7 +126,8 @@
"earnings": make_earning_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
"deductions": make_deduction_salary_component(test_tax=test_tax, company_list=["_Test Company"]),
"payroll_frequency": payroll_frequency,
- "payment_account": get_random("Account")
+ "payment_account": get_random("Account", filters={'account_currency': currency}),
+ "currency": currency
}
if other_details and isinstance(other_details, dict):
details.update(other_details)
@@ -134,16 +141,16 @@
if employee and not frappe.db.get_value("Salary Structure Assignment",
{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
- create_salary_structure_assignment(employee, salary_structure, company=company)
+ create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency)
return salary_structure_doc
-def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None):
+def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
payroll_period = create_payroll_period()
- create_tax_slab(payroll_period, allow_tax_exemption=True)
+ create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee
@@ -151,8 +158,15 @@
salary_structure_assignment.variable = 5000
salary_structure_assignment.from_date = from_date or add_days(nowdate(), -1)
salary_structure_assignment.salary_structure = salary_structure
+ salary_structure_assignment.currency = currency
+ salary_structure_assignment.payroll_payable_account = get_payable_account(company)
salary_structure_assignment.company = company or erpnext.get_default_company()
salary_structure_assignment.save(ignore_permissions=True)
salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
salary_structure_assignment.submit()
return salary_structure_assignment
+
+def get_payable_account(company=None):
+ if not company:
+ company = erpnext.get_default_company()
+ return frappe.db.get_value("Company", company, "default_payroll_payable_account")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
index 818e853..6cd897e 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.js
@@ -6,9 +6,6 @@
frm.set_query("employee", function() {
return {
query: "erpnext.controllers.queries.employee_query",
- filters: {
- company: frm.doc.company
- }
}
});
frm.set_query("salary_structure", function() {
@@ -26,11 +23,25 @@
filters: {
company: frm.doc.company,
docstatus: 1,
- disabled: 0
+ disabled: 0,
+ currency: frm.doc.currency
+ }
+ };
+ });
+
+ frm.set_query("payroll_payable_account", function() {
+ var company_currency = erpnext.get_currency(frm.doc.company);
+ return {
+ filters: {
+ "company": frm.doc.company,
+ "root_type": "Liability",
+ "is_group": 0,
+ "account_currency": ["in", [frm.doc.currency, company_currency]],
}
}
});
},
+
employee: function(frm) {
if(frm.doc.employee){
frappe.call({
@@ -52,5 +63,13 @@
else{
frm.set_value("company", null);
}
+ },
+
+ company: function(frm) {
+ if (frm.doc.company) {
+ frappe.db.get_value("Company", frm.doc.company, "default_payroll_payable_account", (r) => {
+ frm.set_value("payroll_payable_account", r.default_payroll_payable_account);
+ });
+ }
}
});
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index c84e034..92bb347 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -11,11 +11,13 @@
"employee_name",
"department",
"company",
+ "payroll_payable_account",
"column_break_6",
"designation",
"salary_structure",
"from_date",
"income_tax_slab",
+ "currency",
"section_break_7",
"base",
"column_break_9",
@@ -94,7 +96,7 @@
"fieldname": "base",
"fieldtype": "Currency",
"label": "Base",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"fieldname": "column_break_9",
@@ -104,7 +106,7 @@
"fieldname": "variable",
"fieldtype": "Currency",
"label": "Variable",
- "options": "Company:company:default_currency"
+ "options": "currency"
},
{
"fieldname": "amended_from",
@@ -116,15 +118,35 @@
"read_only": 1
},
{
+ "depends_on": "salary_structure",
"fieldname": "income_tax_slab",
"fieldtype": "Link",
"label": "Income Tax Slab",
"options": "Income Tax Slab"
+ },
+ {
+ "default": "Company:company:default_currency",
+ "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
+ "fetch_from": "salary_structure.currency",
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "label": "Currency",
+ "options": "Currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "depends_on": "employee",
+ "fieldname": "payroll_payable_account",
+ "fieldtype": "Link",
+ "label": "Payroll Payable Account",
+ "options": "Account"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-22 19:58:09.964692",
+ "modified": "2020-11-30 18:07:48.251311",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure Assignment",
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
index 668e0ec..dccb5df 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
@@ -13,6 +13,8 @@
class SalaryStructureAssignment(Document):
def validate(self):
self.validate_dates()
+ self.validate_income_tax_slab()
+ self.set_payroll_payable_account()
def validate_dates(self):
joining_date, relieving_date = frappe.db.get_value("Employee", self.employee,
@@ -31,6 +33,24 @@
frappe.throw(_("From Date {0} cannot be after employee's relieving Date {1}")
.format(self.from_date, relieving_date))
+ def validate_income_tax_slab(self):
+ if not self.income_tax_slab:
+ return
+
+ income_tax_slab_currency = frappe.db.get_value('Income Tax Slab', self.income_tax_slab, 'currency')
+ if self.currency != income_tax_slab_currency:
+ frappe.throw(_("Currency of selected Income Tax Slab should be {0} instead of {1}").format(self.currency, income_tax_slab_currency))
+
+ def set_payroll_payable_account(self):
+ if not self.payroll_payable_account:
+ payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payable_account')
+ if not payroll_payable_account:
+ payroll_payable_account = frappe.db.get_value(
+ "Account", {
+ "account_name": _("Payroll Payable"), "company": self.company, "account_currency": frappe.db.get_value(
+ "Company", self.company, "default_currency"), "is_group": 0})
+ self.payroll_payable_account = payroll_payable_account
+
def get_assigned_salary_structure(employee, on_date):
if not employee or not on_date:
return None
@@ -43,3 +63,10 @@
'on_date': on_date,
})
return salary_structure[0][0] if salary_structure else None
+
+@frappe.whitelist()
+def get_employee_currency(employee):
+ employee_currency = frappe.db.get_value('Salary Structure Assignment', {'employee': employee}, 'currency')
+ if not employee_currency:
+ frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(employee))
+ return employee_currency
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
index 94eda4c..65d3824 100644
--- a/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
+++ b/erpnext/payroll/doctype/taxable_salary_slab/taxable_salary_slab.json
@@ -19,13 +19,15 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "From Amount",
+ "options": "currency",
"reqd": 1
},
{
"fieldname": "to_amount",
"fieldtype": "Currency",
"in_list_view": 1,
- "label": "To Amount"
+ "label": "To Amount",
+ "options": "currency"
},
{
"default": "0",
@@ -53,7 +55,7 @@
],
"istable": 1,
"links": [],
- "modified": "2020-06-22 18:16:07.596493",
+ "modified": "2020-10-19 13:44:39.549337",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Taxable Salary Slab",
diff --git a/erpnext/payroll/report/bank_remittance/bank_remittance.py b/erpnext/payroll/report/bank_remittance/bank_remittance.py
index 4b052bf..500543c 100644
--- a/erpnext/payroll/report/bank_remittance/bank_remittance.py
+++ b/erpnext/payroll/report/bank_remittance/bank_remittance.py
@@ -47,33 +47,39 @@
"fieldtype": "Int",
"fieldname": "employee_account_no",
"width": 50
- },
- {
+ }
+ ]
+
+ if frappe.db.has_column('Employee', 'ifsc_code'):
+ columns.append({
"label": _("IFSC Code"),
"fieldtype": "Data",
"fieldname": "bank_code",
"width": 100
- },
- {
- "label": _("Currency"),
- "fieldtype": "Data",
- "fieldname": "currency",
- "width": 50
- },
- {
- "label": _("Net Salary Amount"),
- "fieldtype": "Currency",
- "options": "currency",
- "fieldname": "amount",
- "width": 100
- }
- ]
+ })
+
+ columns += [{
+ "label": _("Currency"),
+ "fieldtype": "Data",
+ "fieldname": "currency",
+ "width": 50
+ },
+ {
+ "label": _("Net Salary Amount"),
+ "fieldtype": "Currency",
+ "options": "currency",
+ "fieldname": "amount",
+ "width": 100
+ }]
+
data = []
accounts = get_bank_accounts()
payroll_entries = get_payroll_entries(accounts, filters)
salary_slips = get_salary_slips(payroll_entries)
- get_emp_bank_ifsc_code(salary_slips)
+
+ if frappe.db.has_column('Employee', 'ifsc_code'):
+ get_emp_bank_ifsc_code(salary_slips)
for salary in salary_slips:
if salary.bank_name and salary.bank_account_no and salary.debit_acc_no and salary.status in ["Submitted", "Paid"]:
diff --git a/erpnext/payroll/report/salary_register/salary_register.js b/erpnext/payroll/report/salary_register/salary_register.js
index 885e3d1..eb4acb9 100644
--- a/erpnext/payroll/report/salary_register/salary_register.js
+++ b/erpnext/payroll/report/salary_register/salary_register.js
@@ -8,34 +8,48 @@
"label": __("From"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(),-1),
- "reqd": 1
+ "reqd": 1,
+ "width": "100px"
},
{
"fieldname":"to_date",
"label": __("To"),
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
- "reqd": 1
+ "reqd": 1,
+ "width": "100px"
+ },
+ {
+ "fieldname": "currency",
+ "fieldtype": "Link",
+ "options": "Currency",
+ "label": __("Currency"),
+ "default": erpnext.get_currency(frappe.defaults.get_default("Company")),
+ "width": "50px"
},
{
"fieldname":"employee",
"label": __("Employee"),
"fieldtype": "Link",
- "options": "Employee"
+ "options": "Employee",
+ "width": "100px"
},
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
- "default": frappe.defaults.get_user_default("Company")
+ "default": frappe.defaults.get_user_default("Company"),
+ "width": "100px",
+ "reqd": 1
},
{
"fieldname":"docstatus",
"label":__("Document Status"),
"fieldtype":"Select",
"options":["Draft", "Submitted", "Cancelled"],
- "default":"Submitted"
+ "default": "Submitted",
+ "width": "100px"
}
]
}
diff --git a/erpnext/payroll/report/salary_register/salary_register.py b/erpnext/payroll/report/salary_register/salary_register.py
index 8701085..a1b1a8c 100644
--- a/erpnext/payroll/report/salary_register/salary_register.py
+++ b/erpnext/payroll/report/salary_register/salary_register.py
@@ -2,18 +2,22 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, erpnext
from frappe.utils import flt
from frappe import _
def execute(filters=None):
if not filters: filters = {}
- salary_slips = get_salary_slips(filters)
+ currency = None
+ if filters.get('currency'):
+ currency = filters.get('currency')
+ company_currency = erpnext.get_company_currency(filters.get("company"))
+ salary_slips = get_salary_slips(filters, company_currency)
if not salary_slips: return [], []
columns, earning_types, ded_types = get_columns(salary_slips)
- ss_earning_map = get_ss_earning_map(salary_slips)
- ss_ded_map = get_ss_ded_map(salary_slips)
+ ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency)
+ ss_ded_map = get_ss_ded_map(salary_slips,currency, company_currency)
doj_map = get_employee_doj_map()
data = []
@@ -21,24 +25,30 @@
row = [ss.name, ss.employee, ss.employee_name, doj_map.get(ss.employee), ss.branch, ss.department, ss.designation,
ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
- if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
- if not ss.department == None: columns[4] = columns[4].replace('-1','120')
- if not ss.designation == None: columns[5] = columns[5].replace('-1','120')
- if not ss.leave_without_pay == None: columns[9] = columns[9].replace('-1','130')
+ if ss.branch is not None: columns[3] = columns[3].replace('-1','120')
+ if ss.department is not None: columns[4] = columns[4].replace('-1','120')
+ if ss.designation is not None: columns[5] = columns[5].replace('-1','120')
+ if ss.leave_without_pay is not None: columns[9] = columns[9].replace('-1','130')
for e in earning_types:
row.append(ss_earning_map.get(ss.name, {}).get(e))
- row += [ss.gross_pay]
+ if currency == company_currency:
+ row += [flt(ss.gross_pay) * flt(ss.exchange_rate)]
+ else:
+ row += [ss.gross_pay]
for d in ded_types:
row.append(ss_ded_map.get(ss.name, {}).get(d))
row.append(ss.total_loan_repayment)
- row += [ss.total_deduction, ss.net_pay]
-
+ if currency == company_currency:
+ row += [flt(ss.total_deduction) * flt(ss.exchange_rate), flt(ss.net_pay) * flt(ss.exchange_rate)]
+ else:
+ row += [ss.total_deduction, ss.net_pay]
+ row.append(currency or company_currency)
data.append(row)
return columns, data
@@ -46,10 +56,19 @@
def get_columns(salary_slips):
"""
columns = [
- _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
- _("Date of Joining") + "::80", _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
- _("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Payment Days") + ":Float:120"
+ _("Salary Slip ID") + ":Link/Salary Slip:150",
+ _("Employee") + ":Link/Employee:120",
+ _("Employee Name") + "::140",
+ _("Date of Joining") + "::80",
+ _("Branch") + ":Link/Branch:120",
+ _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120",
+ _("Company") + ":Link/Company:120",
+ _("Start Date") + "::80",
+ _("End Date") + "::80",
+ _("Leave Without Pay") + ":Float:130",
+ _("Payment Days") + ":Float:120",
+ _("Currency") + ":Link/Currency:80"
]
"""
columns = [
@@ -73,15 +92,15 @@
return columns, salary_components[_("Earning")], salary_components[_("Deduction")]
-def get_salary_slips(filters):
+def get_salary_slips(filters, company_currency):
filters.update({"from_date": filters.get("from_date"), "to_date":filters.get("to_date")})
- conditions, filters = get_conditions(filters)
+ conditions, filters = get_conditions(filters, company_currency)
salary_slips = frappe.db.sql("""select * from `tabSalary Slip` where %s
order by employee""" % conditions, filters, as_dict=1)
return salary_slips or []
-def get_conditions(filters):
+def get_conditions(filters, company_currency):
conditions = ""
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
@@ -92,6 +111,8 @@
if filters.get("to_date"): conditions += " and end_date <= %(to_date)s"
if filters.get("company"): conditions += " and company = %(company)s"
if filters.get("employee"): conditions += " and employee = %(employee)s"
+ if filters.get("currency") and filters.get("currency") != company_currency:
+ conditions += " and currency = %(currency)s"
return conditions, filters
@@ -103,26 +124,32 @@
FROM `tabEmployee`
"""))
-def get_ss_earning_map(salary_slips):
- ss_earnings = frappe.db.sql("""select parent, salary_component, amount
- from `tabSalary Detail` where parent in (%s)""" %
+def get_ss_earning_map(salary_slips, currency, company_currency):
+ ss_earnings = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
+ from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
ss_earning_map = {}
for d in ss_earnings:
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
- ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
+ if currency == company_currency:
+ ss_earning_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
+ else:
+ ss_earning_map[d.parent][d.salary_component] = flt(d.amount)
return ss_earning_map
-def get_ss_ded_map(salary_slips):
- ss_deductions = frappe.db.sql("""select parent, salary_component, amount
- from `tabSalary Detail` where parent in (%s)""" %
+def get_ss_ded_map(salary_slips, currency, company_currency):
+ ss_deductions = frappe.db.sql("""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
+ from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)""" %
(', '.join(['%s']*len(salary_slips))), tuple([d.name for d in salary_slips]), as_dict=1)
ss_ded_map = {}
for d in ss_deductions:
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
- ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
+ if currency == company_currency:
+ ss_ded_map[d.parent][d.salary_component] = flt(d.amount) * flt(d.exchange_rate if d.exchange_rate else 1)
+ else:
+ ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
return ss_ded_map
diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json
new file mode 100644
index 0000000..8149730
--- /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/projects/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json
deleted file mode 100644
index 3756c5b..0000000
--- a/erpnext/projects/desk_page/projects/projects.json
+++ /dev/null
@@ -1,77 +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,
- "icon": "project",
- "idx": 0,
- "is_standard": 1,
- "label": "Projects",
- "modified": "2020-06-30 18:38:40.130763",
- "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/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 8c6a9cf..002ddb2 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -49,7 +49,10 @@
},
callback: function (r) {
if (r.message.length > 0) {
- frappe.msgprint(__(`Cannot convert it to non-group. The following child Tasks exist: ${r.message.join(", ")}.`));
+ let message = __('Cannot convert Task to non-group because the following child Tasks exist: {0}.',
+ [r.message.join(", ")]
+ );
+ frappe.msgprint(message);
frm.reload_doc();
}
}
diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js
index 941fe97..1b6c5fd 100644
--- a/erpnext/projects/doctype/task/task_list.js
+++ b/erpnext/projects/doctype/task/task_list.js
@@ -26,7 +26,7 @@
},
gantt_custom_popup_html: function(ganttobj, task) {
var html = `<h5><a style="text-decoration:underline"\
- href="#Form/Task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
+ href="/app/task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
if(task.project) html += `<p>Project: ${task.project}</p>`;
html += `<p>Progress: ${ganttobj.progress}</p>`;
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
new file mode 100644
index 0000000..dbbd7e1
--- /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 2695502..d30bc8c 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/less/call_popup.less",
+ "public/scss/point-of-sale.scss"
],
"css/marketplace.css": [
"public/less/hub.less"
@@ -27,16 +28,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",
@@ -49,11 +40,22 @@
"public/js/education/assessment_result_tool.html",
"public/js/hub/hub_factory.js",
"public/js/call_popup/call_popup.js",
- "public/js/utils/dimension_tree_filter.js"
+ "public/js/utils/dimension_tree_filter.js",
+ "public/js/telephony.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
"stock/dashboard/item_dashboard_list.html",
"stock/dashboard/item_dashboard.js"
+ ],
+ "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"
]
}
diff --git a/erpnext/public/css/pos.css b/erpnext/public/css/pos.css
deleted file mode 100644
index 47f5771..0000000
--- 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/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js
index 5e4d4a5..be1745e 100644
--- a/erpnext/public/js/call_popup/call_popup.js
+++ b/erpnext/public/js/call_popup/call_popup.js
@@ -74,7 +74,7 @@
'click': () => {
const call_summary = this.dialog.get_value('call_summary');
if (!call_summary) return;
- frappe.xcall('erpnext.communication.doctype.call_log.call_log.add_call_summary', {
+ frappe.xcall('erpnext.telephony.doctype.call_log.call_log.add_call_summary', {
'call_log': this.call_log.name,
'summary': call_summary,
}).then(() => {
@@ -85,7 +85,7 @@
<br>
<a
class="text-small text-muted"
- href="#Form/Call Log/${this.call_log.name}">
+ href="/app/call-log/${this.call_log.name}">
${__('View call log')}
</a>
`,
@@ -167,7 +167,7 @@
const issue_field = this.dialog.get_field("last_issue");
issue_field.set_value(issue.subject);
issue_field.$wrapper.append(`
- <a class="text-medium" href="#List/Issue?customer=${issue.customer}">
+ <a class="text-medium" href="/app/issue?customer=${issue.customer}">
${__('View all issues from {0}', [issue.customer])}
</a>
`);
diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js
index 26e5ab8..7ce8b09 100644
--- a/erpnext/public/js/communication.js
+++ b/erpnext/public/js/communication.js
@@ -84,7 +84,7 @@
frm.reload_doc();
frappe.show_alert({
message: __("Opportunity {0} created",
- ['<a href="#Form/Opportunity/'+r.message+'">' + r.message + '</a>']),
+ ['<a href="/app/opportunity/'+r.message+'">' + r.message + '</a>']),
indicator: 'green'
});
}
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 6e97d81..29f3595 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -146,18 +146,18 @@
if(!d.charge_type && d.account_head){
frappe.msgprint(__("Please select Charge Type first"));
frappe.model.set_value(cdt, cdn, "account_head", "");
- } else if(d.account_head && d.charge_type!=="Actual") {
+ } else if (d.account_head) {
frappe.call({
type:"GET",
method: "erpnext.controllers.accounts_controller.get_tax_rate",
args: {"account_head":d.account_head},
callback: function(r) {
- frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0);
+ if (d.charge_type!=="Actual") {
+ frappe.model.set_value(cdt, cdn, "rate", r.message.tax_rate || 0);
+ }
frappe.model.set_value(cdt, cdn, "description", r.message.account_name);
}
})
- } else if (d.charge_type == 'Actual' && d.account_head) {
- frappe.model.set_value(cdt, cdn, "description", d.account_head.split(' - ')[0]);
}
}
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 58ac38f..db85a3e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -189,6 +189,7 @@
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
+ item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
}
this._super(doc, cdt, cdn);
@@ -218,8 +219,7 @@
var is_negative_qty = false;
for(var i = 0; i<fieldnames.length; i++) {
if(item[fieldnames[i]] < 0){
- frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}",
- [item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
+ frappe.msgprint(__("Row #{0}: {1} can not be negative for item {2}", [item.idx,__(frappe.meta.get_label(cdt, fieldnames[i], cdn)), item.item_code]));
is_negative_qty = true;
break;
}
@@ -294,69 +294,6 @@
this.get_terms();
},
- link_to_mrs: function() {
- var my_items = [];
- for (var i in cur_frm.doc.items) {
- if(!cur_frm.doc.items[i].material_request){
- my_items.push(cur_frm.doc.items[i].item_code);
- }
- }
- frappe.call({
- method: "erpnext.buying.utils.get_linked_material_requests",
- args:{
- items: my_items
- },
- callback: function(r) {
- if(!r.message || r.message.length == 0) {
- frappe.throw(__("No pending Material Requests found to link for the given items."))
- }
- else {
- var i = 0;
- var item_length = cur_frm.doc.items.length;
- while (i < item_length) {
- var qty = cur_frm.doc.items[i].qty;
- (r.message[0] || []).forEach(function(d) {
- if (d.qty > 0 && qty > 0 && cur_frm.doc.items[i].item_code == d.item_code && !cur_frm.doc.items[i].material_request_item)
- {
- cur_frm.doc.items[i].material_request = d.mr_name;
- cur_frm.doc.items[i].material_request_item = d.mr_item;
- var my_qty = Math.min(qty, d.qty);
- qty = qty - my_qty;
- d.qty = d.qty - my_qty;
- cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor;
- cur_frm.doc.items[i].qty = my_qty;
-
- frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")");
- if (qty > 0)
- {
- frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
- var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items");
- item_length++;
-
- for (var key in cur_frm.doc.items[i])
- {
- newrow[key] = cur_frm.doc.items[i][key];
- }
-
- newrow.idx = item_length;
- newrow["stock_qty"] = newrow.conversion_factor*qty;
- newrow["qty"] = qty;
-
- newrow["material_request"] = "";
- newrow["material_request_item"] = "";
-
- }
- }
- });
- i++;
- }
- refresh_field("items");
- //cur_frm.save();
- }
- }
- });
- },
-
update_auto_repeat_reference: function(doc) {
if (doc.auto_repeat) {
frappe.call({
@@ -422,6 +359,62 @@
cur_frm.add_fetch('project', 'cost_center', 'cost_center');
+erpnext.buying.link_to_mrs = function(frm) {
+ frappe.call({
+ method: "erpnext.buying.utils.get_linked_material_requests",
+ args:{
+ items: frm.doc.items.map((item) => item.item_code)
+ },
+ callback: function(r) {
+ if (!r.message || r.message.length == 0) {
+ frappe.throw({
+ message: __("No pending Material Requests found to link for the given items."),
+ title: __("Note")
+ });
+ }
+
+ var item_length = frm.doc.items.length;
+ for (let item of frm.doc.items) {
+ var qty = item.qty;
+ (r.message[0] || []).forEach(function(d) {
+ if (d.qty > 0 && qty > 0 && item.item_code == d.item_code && !item.material_request_item)
+ {
+ item.material_request = d.mr_name;
+ item.material_request_item = d.mr_item;
+ var my_qty = Math.min(qty, d.qty);
+ qty = qty - my_qty;
+ d.qty = d.qty - my_qty;
+ item.stock_qty = my_qty*item.conversion_factor;
+ item.qty = my_qty;
+
+ frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + item.idx + ")");
+ if (qty > 0)
+ {
+ frappe.msgprint("Splitting " + qty + " units of " + d.item_code);
+ var newrow = frappe.model.add_child(frm.doc, item.doctype, "items");
+ item_length++;
+
+ for (var key in item)
+ {
+ newrow[key] = item[key];
+ }
+
+ newrow.idx = item_length;
+ newrow["stock_qty"] = newrow.conversion_factor*qty;
+ newrow["qty"] = qty;
+
+ newrow["material_request"] = "";
+ newrow["material_request_item"] = "";
+
+ }
+ }
+ });
+ }
+ refresh_field("items");
+ }
+ });
+}
+
erpnext.buying.get_default_bom = function(frm) {
$.each(frm.doc["items"] || [], function(i, d) {
if (d.item_code && d.bom === "") {
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 99f3995..22e7578 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -609,6 +609,15 @@
this.calculate_outstanding_amount(update_paid_amount);
},
+ is_internal_invoice: function() {
+ if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
+ if (this.frm.doc.company === this.frm.doc.represents_company) {
+ return true;
+ }
+ }
+ return false;
+ },
+
calculate_outstanding_amount: function(update_paid_amount) {
// NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
@@ -617,7 +626,7 @@
this.calculate_paid_amount();
}
- if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return;
+ if (this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return;
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1358a4b..1c84e55 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -209,6 +209,17 @@
});
}
+ if (this.frm.fields_dict.taxes_and_charges) {
+ this.frm.set_query("taxes_and_charges", function() {
+ return {
+ filters: [
+ ['company', '=', me.frm.doc.company],
+ ['docstatus', '!=', 2]
+ ]
+ };
+ });
+ }
+
},
onload: function() {
var me = this;
@@ -397,7 +408,7 @@
show_description(row_to_modify.idx, row_to_modify.item_code);
- this.frm.from_barcode = true;
+ this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
item_code: data.item_code,
qty: (row_to_modify.qty || 0) + 1
@@ -435,9 +446,10 @@
method: "erpnext.controllers.accounts_controller.get_default_taxes_and_charges",
args: {
"master_doctype": taxes_and_charges_field.options,
- "tax_template": me.frm.doc.taxes_and_charges,
+ "tax_template": me.frm.doc.taxes_and_charges || "",
"company": me.frm.doc.company
},
+ debounce: 2000,
callback: function(r) {
if(!r.exc && r.message) {
frappe.run_serially([
@@ -481,7 +493,7 @@
d.item_code = "";
}
- this.frm.from_barcode = true;
+ this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
this.item_code(doc, cdt, cdn);
},
@@ -498,11 +510,12 @@
show_batch_dialog = 1;
}
// clear barcode if setting item (else barcode will take priority)
- if(!this.frm.from_barcode) {
+ if (this.frm.from_barcode == 0) {
item.barcode = null;
}
+ this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
- this.frm.from_barcode = false;
+
if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
diff --git a/erpnext/public/js/education/assessment_result_tool.html b/erpnext/public/js/education/assessment_result_tool.html
index 9fc17f7..b591010 100644
--- a/erpnext/public/js/education/assessment_result_tool.html
+++ b/erpnext/public/js/education/assessment_result_tool.html
@@ -19,7 +19,7 @@
</thead>
<tbody>
{% for s in students %}
- <tr
+ <tr
{% if(s.assessment_details && s.docstatus && s.docstatus == 1) { %} class="text-muted" {% } %}
data-student="{{s.student}}">
@@ -29,7 +29,7 @@
<td>
<span data-student="{{s.student}}" data-criteria="{{c.assessment_criteria}}" class="student-result-grade badge" >
{% if(s.assessment_details) { %}
- {{s.assessment_details[c.assessment_criteria][1]}}
+ {{s.assessment_details[c.assessment_criteria][1]}}
{% } %}
</span>
<input type="number" class="student-result-data" style="width:70%; float:right;"
@@ -61,7 +61,7 @@
{% } %}
</span>
<span data-student="{{s.student}}" class="total-result-link" style="width: 10%; display:{% if(!s.assessment_details) { %}None{% } %}; float:right;">
- <a class="btn-open no-decoration" title="Open Link" href="#Form/Assessment Result/{% if(s.assessment_details) { %}{{s.name}}{% } %}">
+ <a class="btn-open no-decoration" title="Open Link" href="/app/Form/Assessment Result/{% if(s.assessment_details) { %}{{s.name}}{% } %}">
<i class="octicon octicon-arrow-right"></i>
</a>
</span>
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index 66ff464..a436cac 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -2,13 +2,13 @@
const docsUrl = 'https://erpnext.com/docs/';
-frappe.help.help_links['Form/Rename Tool'] = [
+frappe.help.help_links['rename tool'] = [
{ label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' },
]
//Setup
-frappe.help.help_links['List/User'] = [
+frappe.help.help_links['user'] = [
{ label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' },
{ label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' },
]
@@ -21,7 +21,7 @@
{ label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' },
]
-frappe.help.help_links['Form/System Settings'] = [
+frappe.help.help_links['system-settings'] = [
{ label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' },
]
@@ -30,64 +30,60 @@
{ label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' },
]
-frappe.help.help_links['module_setup'] = [
- { label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' },
-]
-
-frappe.help.help_links['Form/Naming Series'] = [
+frappe.help.help_links['naming-series'] = [
{ label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' },
{ label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' },
]
-frappe.help.help_links['Form/Global Defaults'] = [
+frappe.help.help_links['global-defaults'] = [
{ label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' },
]
-frappe.help.help_links['Form/Email Digest'] = [
+frappe.help.help_links['email-digest'] = [
{ label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
]
-frappe.help.help_links['List/Print Heading'] = [
+frappe.help.help_links['print-heading'] = [
{ label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
]
-frappe.help.help_links['List/Letter Head'] = [
+frappe.help.help_links['letter-head'] = [
{ label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' },
]
-frappe.help.help_links['List/Address Template'] = [
+frappe.help.help_links['address-template'] = [
{ label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' },
]
-frappe.help.help_links['List/Terms and Conditions'] = [
+frappe.help.help_links['terms-and-conditions'] = [
{ label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' },
]
-frappe.help.help_links['List/Cheque Print Template'] = [
+frappe.help.help_links['cheque-print-template'] = [
{ label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' },
]
-frappe.help.help_links['List/Email Account'] = [
+frappe.help.help_links['email-account'] = [
{ label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' },
]
-frappe.help.help_links['List/Notification'] = [
+frappe.help.help_links['notification'] = [
{ label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
]
-frappe.help.help_links['Form/Notification'] = [
+frappe.help.help_links['notification'] = [
{ label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
]
-frappe.help.help_links['List/Email Digest'] = [
+frappe.help.help_links['email-digest'] = [
{ label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
]
-frappe.help.help_links['List/Auto Email Report'] = [
+frappe.help.help_links['auto-email-report'] = [
{ label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' },
]
-frappe.help.help_links['Form/Print Settings'] = [
+frappe.help.help_links['print-settings'] = [
{ label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
]
@@ -95,66 +91,60 @@
{ label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
]
-frappe.help.help_links['List/Print Heading'] = [
+frappe.help.help_links['print-heading'] = [
{ label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
]
//setup-integrations
-frappe.help.help_links['Form/PayPal Settings'] = [
+frappe.help.help_links['paypal-settings'] = [
{ label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' },
]
-frappe.help.help_links['Form/Razorpay Settings'] = [
+frappe.help.help_links['razorpay-settings'] = [
{ label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' },
]
-frappe.help.help_links['Form/Dropbox Settings'] = [
+frappe.help.help_links['dropbox-settings'] = [
{ label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' },
]
-frappe.help.help_links['Form/LDAP Settings'] = [
+frappe.help.help_links['ldap-settings'] = [
{ label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' },
]
-frappe.help.help_links['Form/Stripe Settings'] = [
+frappe.help.help_links['stripe-settings'] = [
{ label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' },
]
//Sales
-frappe.help.help_links['Form/Quotation'] = [
+frappe.help.help_links['quotation'] = [
{ label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' },
{ label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
{ label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' },
{ label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
]
-frappe.help.help_links['List/Customer'] = [
+frappe.help.help_links['customer'] = [
{ label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
{ label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
]
-frappe.help.help_links['Form/Customer'] = [
+frappe.help.help_links['customer'] = [
{ label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
{ label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
]
-frappe.help.help_links['List/Sales Taxes and Charges Template'] = [
+frappe.help.help_links['sales-taxes-and-charges-template'] = [
{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
]
-frappe.help.help_links['Form/Sales Taxes and Charges Template'] = [
+frappe.help.help_links['sales-taxes-and-charges-template'] = [
{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
]
-frappe.help.help_links['List/Sales Order'] = [
- { label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' },
- { label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
- { label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
-]
-
-frappe.help.help_links['Form/Sales Order'] = [
+frappe.help.help_links['sales-order'] = [
{ label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' },
{ label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
{ label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
@@ -164,43 +154,34 @@
{ label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
]
-frappe.help.help_links['Form/Product Bundle'] = [
+frappe.help.help_links['product-bundle'] = [
{ label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' },
]
-frappe.help.help_links['Form/Selling Settings'] = [
+frappe.help.help_links['selling-settings'] = [
{ label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' },
]
//Buying
-frappe.help.help_links['List/Supplier'] = [
+frappe.help.help_links['supplier'] = [
{ label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' },
]
-frappe.help.help_links['Form/Supplier'] = [
- { label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' },
-]
-
-frappe.help.help_links['Form/Request for Quotation'] = [
+frappe.help.help_links['request-for-quotation'] = [
{ label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' },
{ label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' },
]
-frappe.help.help_links['Form/Supplier Quotation'] = [
+frappe.help.help_links['supplier-quotation'] = [
{ label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' },
]
-frappe.help.help_links['Form/Buying Settings'] = [
+frappe.help.help_links['buying-settings'] = [
{ label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' },
]
-frappe.help.help_links['List/Purchase Order'] = [
- { label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' },
- { label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
-
-frappe.help.help_links['Form/Purchase Order'] = [
+frappe.help.help_links['purchase-order'] = [
{ label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' },
{ label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' },
{ label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' },
@@ -208,44 +189,44 @@
{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
]
-frappe.help.help_links['List/Purchase Taxes and Charges Template'] = [
+frappe.help.help_links['purchase-taxes-and-charges-template'] = [
{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
]
-frappe.help.help_links['List/POS Profile'] = [
+frappe.help.help_links['pos-profile'] = [
{ label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
]
-frappe.help.help_links['List/Price List'] = [
+frappe.help.help_links['price-list'] = [
{ label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' },
]
-frappe.help.help_links['List/Authorization Rule'] = [
+frappe.help.help_links['authorization-rule'] = [
{ label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' },
]
-frappe.help.help_links['Form/SMS Settings'] = [
+frappe.help.help_links['sms-settings'] = [
{ label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' },
]
-frappe.help.help_links['List/Stock Reconciliation'] = [
+frappe.help.help_links['stock-reconciliation'] = [
{ label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' },
]
-frappe.help.help_links['Tree/Territory'] = [
+frappe.help.help_links['territory/view/tree'] = [
{ label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' },
]
-frappe.help.help_links['Form/Dropbox Backup'] = [
+frappe.help.help_links['dropbox-backup'] = [
{ label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' },
{ label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' },
]
-frappe.help.help_links['List/Workflow'] = [
+frappe.help.help_links['workflow'] = [
{ label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' },
]
-frappe.help.help_links['List/Company'] = [
+frappe.help.help_links['company'] = [
{ label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' },
{ label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' },
{ label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' },
@@ -253,25 +234,25 @@
//Accounts
-frappe.help.help_links['modules/Accounts'] = [
+frappe.help.help_links['space/Accounts'] = [
{ label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' },
{ label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' },
{ label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' },
]
-frappe.help.help_links['Tree/Account'] = [
+frappe.help.help_links['account/view/tree'] = [
{ label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' },
{ label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' },
]
-frappe.help.help_links['Form/Sales Invoice'] = [
+frappe.help.help_links['sales-invoice'] = [
{ label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
{ label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
]
-frappe.help.help_links['List/Sales Invoice'] = [
+frappe.help.help_links['sales-invoice'] = [
{ label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
@@ -282,43 +263,43 @@
{ label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' },
]
-frappe.help.help_links['List/POS Profile'] = [
+frappe.help.help_links['pos-profile'] = [
{ label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
]
-frappe.help.help_links['List/Purchase Invoice'] = [
+frappe.help.help_links['purchase-invoice'] = [
{ label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' },
{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
{ label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
]
-frappe.help.help_links['List/Journal Entry'] = [
+frappe.help.help_links['journal-entry'] = [
{ label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' },
{ label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' },
{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
]
-frappe.help.help_links['List/Payment Entry'] = [
+frappe.help.help_links['payment-entry'] = [
{ label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' },
]
-frappe.help.help_links['List/Payment Request'] = [
+frappe.help.help_links['payment-request'] = [
{ label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' },
]
-frappe.help.help_links['List/Asset'] = [
+frappe.help.help_links['asset'] = [
{ label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
]
-frappe.help.help_links['List/Asset Category'] = [
+frappe.help.help_links['asset-category'] = [
{ label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
]
-frappe.help.help_links['Tree/Cost Center'] = [
+frappe.help.help_links['cost-center/view/tree'] = [
{ label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
]
-frappe.help.help_links['List/Item'] = [
+frappe.help.help_links['item'] = [
{ label: 'Item', url: docsUrl + 'user/manual/en/stock/item' },
{ label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' },
{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
@@ -329,61 +310,42 @@
{ label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' },
]
-frappe.help.help_links['Form/Item'] = [
- { label: 'Item', url: docsUrl + 'user/manual/en/stock/item' },
- { label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' },
- { label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
- { label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' },
- { label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
- { label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' },
- { label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' },
- { label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' },
-]
-
-frappe.help.help_links['List/Purchase Receipt'] = [
+frappe.help.help_links['purchase-receipt'] = [
{ label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' },
{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
]
-frappe.help.help_links['List/Delivery Note'] = [
+frappe.help.help_links['delivery-note'] = [
{ label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
]
-frappe.help.help_links['Form/Delivery Note'] = [
+frappe.help.help_links['delivery-note'] = [
{ label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
]
-frappe.help.help_links['List/Installation Note'] = [
+frappe.help.help_links['installation-note'] = [
{ label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' },
]
-frappe.help.help_links['Tree'] = [
- { label: 'Managing Tree Structure Masters', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' },
-]
-frappe.help.help_links['List/Budget'] = [
+frappe.help.help_links['budget'] = [
{ label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
]
//Stock
-frappe.help.help_links['List/Material Request'] = [
+frappe.help.help_links['material-request'] = [
{ label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' },
{ label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' },
]
-frappe.help.help_links['Form/Material Request'] = [
- { label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' },
- { label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' },
-]
-
-frappe.help.help_links['Form/Stock Entry'] = [
+frappe.help.help_links['stock-entry'] = [
{ label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' },
{ label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' },
{ label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' },
@@ -391,136 +353,114 @@
{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
]
-frappe.help.help_links['List/Stock Entry'] = [
- { label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' },
-]
-
-frappe.help.help_links['Tree/Warehouse'] = [
+frappe.help.help_links['warehouse/view/tree'] = [
{ label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' },
]
-frappe.help.help_links['List/Serial No'] = [
+frappe.help.help_links['serial-no'] = [
{ label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' },
]
-frappe.help.help_links['Form/Serial No'] = [
- { label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' },
-]
-
-frappe.help.help_links['Form/Batch'] = [
+frappe.help.help_links['batch'] = [
{ label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' },
]
-frappe.help.help_links['Form/Packing Slip'] = [
+frappe.help.help_links['packing-slip'] = [
{ label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' },
]
-frappe.help.help_links['Form/Quality Inspection'] = [
+frappe.help.help_links['quality-inspection'] = [
{ label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' },
]
-frappe.help.help_links['Form/Landed Cost Voucher'] = [
+frappe.help.help_links['landed-cost-voucher'] = [
{ label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' },
]
-frappe.help.help_links['Tree/Item Group'] = [
+frappe.help.help_links['item-group/view/tree'] = [
{ label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' },
]
-frappe.help.help_links['Form/Item Attribute'] = [
+frappe.help.help_links['item-attribute'] = [
{ label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' },
]
-frappe.help.help_links['Form/UOM'] = [
+frappe.help.help_links['uom'] = [
{ label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' },
]
-frappe.help.help_links['Form/Stock Reconciliation'] = [
+frappe.help.help_links['stock-reconciliation'] = [
{ label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' },
]
//CRM
-frappe.help.help_links['Form/Lead'] = [
+frappe.help.help_links['lead'] = [
{ label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' },
]
-frappe.help.help_links['Form/Opportunity'] = [
+frappe.help.help_links['opportunity'] = [
{ label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' },
]
-frappe.help.help_links['Form/Address'] = [
+frappe.help.help_links['address'] = [
{ label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' },
]
-frappe.help.help_links['Form/Contact'] = [
+frappe.help.help_links['contact'] = [
{ label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' },
]
-frappe.help.help_links['Form/Newsletter'] = [
+frappe.help.help_links['newsletter'] = [
{ label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' },
]
-frappe.help.help_links['Form/Campaign'] = [
+frappe.help.help_links['campaign'] = [
{ label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' },
]
-frappe.help.help_links['Tree/Sales Person'] = [
+frappe.help.help_links['sales-person/view/tree'] = [
{ label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' },
]
-frappe.help.help_links['Form/Sales Person'] = [
+frappe.help.help_links['sales-person'] = [
{ label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' },
]
-//Support
-
-frappe.help.help_links['List/Feedback Trigger'] = [
- { label: 'Feedback Trigger', url: docsUrl + 'user/manual/en/setting-up/feedback/setting-up-feedback' },
-]
-
-frappe.help.help_links['List/Feedback Request'] = [
- { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' },
-]
-
-frappe.help.help_links['List/Feedback Request'] = [
- { label: 'Feedback Request', url: docsUrl + 'user/manual/en/setting-up/feedback/submit-feedback' },
-]
-
//Manufacturing
-frappe.help.help_links['Form/BOM'] = [
+frappe.help.help_links['bom'] = [
{ label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' },
{ label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' },
]
-frappe.help.help_links['Form/Work Order'] = [
+frappe.help.help_links['work-order'] = [
{ label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' },
]
-frappe.help.help_links['Form/Workstation'] = [
+frappe.help.help_links['workstation'] = [
{ label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' },
]
-frappe.help.help_links['Form/Operation'] = [
+frappe.help.help_links['operation'] = [
{ label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' },
]
-frappe.help.help_links['Form/BOM Update Tool'] = [
+frappe.help.help_links['bom-update-tool'] = [
{ label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' },
]
//Customize
-frappe.help.help_links['Form/Customize Form'] = [
+frappe.help.help_links['customize-form'] = [
{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
{ label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' },
]
-frappe.help.help_links['Form/Custom Field'] = [
+frappe.help.help_links['custom-field'] = [
{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
]
-frappe.help.help_links['Form/Custom Field'] = [
+frappe.help.help_links['custom-field'] = [
{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
]
diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue
index 057fe8b..16d0601 100644
--- a/erpnext/public/js/hub/pages/Category.vue
+++ b/erpnext/public/js/hub/pages/Category.vue
@@ -32,7 +32,7 @@
item_id_fieldname: 'name',
// Constants
- empty_state_message: __(`No items in this category yet.`),
+ empty_state_message: __('No items in this category yet.'),
search_value: '',
diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue
index ab9990a..63ae7e9 100644
--- a/erpnext/public/js/hub/pages/FeaturedItems.vue
+++ b/erpnext/public/js/hub/pages/FeaturedItems.vue
@@ -33,10 +33,8 @@
// Constants
page_title: __('Your Featured Items'),
- empty_state_message: __(`No featured items yet. Got to your
- <a href="#marketplace/published-items">
- Published Items</a>
- and feature upto 8 items that you want to highlight to your customers.`)
+ empty_state_message: __('No featured items yet. Got to your {0} and feature up to eight items that you want to highlight to your customers.',
+ [`<a href="#marketplace/published-items">${__("Published Items")}</a>`])
};
},
created() {
@@ -71,9 +69,9 @@
const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
- alert = frappe.show_alert(__(`<span>${item_name} removed.
- <a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
- grace_period/1000,
+ alert_message = __('{0} removed. {1}', [item_name,
+ `<a href="#" data-action="undo-remove"><b>${__('Undo')}</b></a>`]);
+ alert = frappe.show_alert(alert_message, grace_period / 1000,
{
'undo-remove': undo_remove.bind(this)
}
diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue
index 51ade42..93002a7 100644
--- a/erpnext/public/js/hub/pages/Item.vue
+++ b/erpnext/public/js/hub/pages/Item.vue
@@ -113,12 +113,12 @@
let stats = __('No views yet');
if (this.item.view_count) {
- const views_message = __(`${this.item.view_count} Views`);
+ const views_message = __('{0} Views', [this.item.view_count]);
const rating_html = get_rating_html(this.item.average_rating);
const rating_count =
this.item.no_of_ratings > 0
- ? `${this.item.no_of_ratings} reviews`
+ ? __('{0} reviews', [this.item.no_of_ratings])
: __('No reviews yet');
stats = [views_message, rating_html, rating_count];
@@ -310,7 +310,7 @@
return this.get_item_details();
})
.then(() => {
- frappe.show_alert(__(`${this.item.item_name} Updated`));
+ frappe.show_alert(__('{0} Updated', [this.item.item_name]));
});
},
@@ -337,7 +337,7 @@
},
unpublish_item() {
- frappe.confirm(__(`Unpublish {0}?`, [this.item.item_name]), () => {
+ frappe.confirm(__('Unpublish {0}?', [this.item.item_name]), () => {
frappe
.call('erpnext.hub_node.api.unpublish_item', {
item_code: this.item.item_code,
diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue
index 246d31b..8901b97 100644
--- a/erpnext/public/js/hub/pages/NotFound.vue
+++ b/erpnext/public/js/hub/pages/NotFound.vue
@@ -27,7 +27,7 @@
},
// Constants
- empty_state_message: __(`Sorry! I could not find what you were looking for.`)
+ empty_state_message: __('Sorry! We could not find what you were looking for.')
};
},
}
diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue
index 735f2b9..96fa0aa 100644
--- a/erpnext/public/js/hub/pages/Publish.vue
+++ b/erpnext/public/js/hub/pages/Publish.vue
@@ -75,14 +75,11 @@
// TODO: multiline translations don't work
page_title: __('Publish Items'),
search_placeholder: __('Search Items ...'),
- empty_state_message: __(`No Items selected yet. Browse and click on items below to publish.`),
- valid_items_instruction: __(`Only items with an image and description can be published. Please update them if an item in your inventory does not appear.`),
+ empty_state_message: __('No Items selected yet. Browse and click on items below to publish.'),
+ valid_items_instruction: __('Only items with an image and description can be published. Please update them if an item in your inventory does not appear.'),
last_sync_message: (hub.settings.last_sync_datetime)
- ? __(`Last sync was
- <a href="#marketplace/profile">
- ${comment_when(hub.settings.last_sync_datetime)}</a>.
- <a href="#marketplace/published-items">
- See your Published Items</a>.`)
+ ? __('Last sync was {0}.', [`<a href="#marketplace/profile">${comment_when(hub.settings.last_sync_datetime)}</a>`]) +
+ ` <a href="#marketplace/published-items">${__('See your Published Items.')}</a>`
: ''
};
},
@@ -147,11 +144,9 @@
},
add_last_sync_message() {
- this.last_sync_message = __(`Last sync was
- <a href="#marketplace/profile">
- ${comment_when(hub.settings.last_sync_datetime)}</a>.
- <a href="#marketplace/published-items">
- See your Published Items</a>.`);
+ this.last_sync_message = __('Last sync was {0}.',
+ [`<a href="#marketplace/profile">${comment_when(hub.settings.last_sync_datetime)}</a>`]
+ ) + `<a href="#marketplace/published-items">${__('See your Published Items')}</a>.`;
},
clear_last_sync_message() {
diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue
index c29675a..7007ddc 100644
--- a/erpnext/public/js/hub/pages/SavedItems.vue
+++ b/erpnext/public/js/hub/pages/SavedItems.vue
@@ -29,7 +29,7 @@
// Constants
page_title: __('Saved Items'),
- empty_state_message: __(`You haven't saved any items yet.`)
+ empty_state_message: __('You have not saved any items yet.')
};
},
created() {
@@ -64,8 +64,13 @@
const item_name = this.items.filter(item => item.hub_item_name === hub_item_name);
- alert = frappe.show_alert(__(`<span>${item_name} removed.
- <a href="#" data-action="undo-remove"><b>Undo</b></a></span>`),
+ alert = frappe.show_alert(`
+ <span>
+ ${__('{0} removed.', [item_name], 'A specific Item has been removed.')}
+ <a href="#" data-action="undo-remove">
+ <b>${__('Undo', None, 'Undo removal of item.')}</b>
+ </a>
+ </span>`,
grace_period/1000,
{
'undo-remove': undo_remove.bind(this)
diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue
index 1032842..c10841e 100644
--- a/erpnext/public/js/hub/pages/Search.vue
+++ b/erpnext/public/js/hub/pages/Search.vue
@@ -42,7 +42,10 @@
computed: {
page_title() {
return this.items.length
- ? __(`Results for "${this.search_value}" ${this.category !== 'All'? `in category ${this.category}` : ''}`)
+ ? __('Results for "{0}" {1}', [
+ this.search_value,
+ this.category !== 'All' ? __('in category {0}', [this.category]) : ''
+ ])
: __('No Items found.');
}
},
diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue
index e339eaa..c0903c6 100644
--- a/erpnext/public/js/hub/pages/Seller.vue
+++ b/erpnext/public/js/hub/pages/Seller.vue
@@ -136,7 +136,7 @@
this.init = false;
this.profile = data.profile;
this.items = data.items;
- this.item_container_heading = data.is_featured_item? "Features Items":"Popular Items";
+ this.item_container_heading = data.is_featured_item ? __('Featured Items') : __('Popular Items');
this.hub_seller = this.items[0].hub_seller;
this.recent_seller_reviews = data.recent_seller_reviews;
this.seller_product_view_stats = data.seller_product_view_stats;
@@ -147,7 +147,7 @@
this.country = __(profile.country);
this.site_name = __(profile.site_name);
- this.joined_when = __(`Joined ${comment_when(profile.creation)}`);
+ this.joined_when = __('Joined {0}', [comment_when(profile.creation)]);
this.image = profile.logo;
this.sections = [
diff --git a/erpnext/public/js/payment/payment_details.html b/erpnext/public/js/payment/payment_details.html
deleted file mode 100644
index 3e63944..0000000
--- a/erpnext/public/js/payment/payment_details.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<div class="row pos-payment-row" type="{{type}}" idx={{idx}}>
- <div class="col-xs-6" style="padding:20px">{{mode_of_payment}}</div>
- <div class="col-xs-6">
- <div class="input-group">
- <input disabled class="form-control text-right amount" idx="{{idx}}" type="text" value="{%= format_currency(amount, currency) %}">
- <span class="input-group-btn">
- <button type="button" class="btn btn-default clr" idx="{{idx}}" style="border:1px solid #d1d8dd">C</button>
- </span>
- </div>
- </div>
-</div>
\ 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 cb6971b..0000000
--- a/erpnext/public/js/payment/pos_payment.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<div class="pos_payment row">
- <div class="row" style="padding: 0px 30px;">
- <h3>{{ __("Total Amount") }}: <span class="label label-default" style="font-size:20px;padding:5px">{%= format_currency(grand_total, currency) %}</span></h3>
- </div>
- <div class="row amount-row">
- <div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> {{ __("Paid") }} <h3 class="paid_amount">{%= format_currency(paid_amount, currency) %}</h3></p>
- </div>
- <div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> {{ __("Outstanding") }} <h3 class="outstanding_amount">{%= format_currency(outstanding_amount, currency) %} </h3></p>
- </div>
- <div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> {{ __("Change") }} <input class="form-control text-right change_amount bold" type="text" idx="change_amount" value="{{format_number(change_amount, null, 2)}}">
- </p>
- </div>
- <div class="col-xs-6 col-sm-3 text-center">
- <p class="amount-label"> {{ __("Write off") }} <input class="form-control text-right write_off_amount bold" type="text" idx="write_off_amount" value="{{format_number(write_off_amount, null, 2)}}">
- </p>
- </div>
- </div>
- <hr>
- <div class="row">
- <div class="col-sm-6 ">
- <div class ="row multimode-payments" style = "margin-right:10px">
- </div>
- </div>
- <div class="col-sm-6 payment-toolbar">
- {% for(var i=0; i<3; i++) { %}
- <div class="row">
- {% for(var j=i*3; j<(i+1)*3; j++) { %}
- <button type="button" class="btn btn-default pos-keyboard-key">{{j+1}}</button>
- {% } %}
- </div>
- {% } %}
- <div class="row">
- <button type="button" class="btn btn-default delete-btn">{{ __("Del") }}</button>
- <button type="button" class="btn btn-default pos-keyboard-key">0</button>
- <button type="button" class="btn btn-default pos-keyboard-key">.</button>
- </div>
- </div>
- </div>
-</div>
diff --git a/erpnext/public/js/pos/clusterize.js b/erpnext/public/js/pos/clusterize.js
deleted file mode 100644
index 075c9ca..0000000
--- 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 = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
- 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 = '<table><tbody>' + data + '</tbody></table>';
- 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 3ba5ccb..0000000
--- a/erpnext/public/js/pos/customer_toolbar.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="pos-bill-toolbar col-xs-9" style="display: flex; width: 70%;">
- <div class="party-area" style="flex: 1;">
- <span class="edit-customer-btn text-muted" style="display: inline;">
- <a class="btn-open no-decoration" title="Edit Customer">
- <i class="octicon octicon-pencil"></i>
- </a>
- </span>
- </div>
- <button class="btn btn-default list-customers-btn" style="margin-left: 12px">
- <i class="octicon octicon-organization"></i>
- </button>
- </button> {% if (allow_delete) { %}
- <button class="btn btn-default btn-danger" style="margin: 0 5px 0 5px">
- <i class="octicon octicon-trashcan"></i>
- </button> {% } %}
-</div>
\ 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 89e2940..0000000
--- a/erpnext/public/js/pos/pos.html
+++ /dev/null
@@ -1,136 +0,0 @@
-<div class="pos">
- <div class="row">
- <div class="col-sm-5 pos-bill-wrapper">
- <div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Item Cart") }}</h6></div>
- <div class="pos-bill">
- <div class="item-cart">
- <div class="pos-list-row pos-bill-header text-muted h6">
- <span class="cell subject">
- <!--<input class="list-select-all" type="checkbox" title="{%= __("Select All") %}">-->
- {{ __("Item Name")}}
- </span>
- <span class="cell text-right">{{ __("Quantity") }}</span>
- <span class="cell text-right">{{ __("Discount") }}</span>
- <span class="cell text-right">{{ __("Rate") }}</span>
- </div>
- <div class="item-cart-items">
- <div class="no-items-message text-extra-muted">
- <span class="text-center">
- <i class="fa fa-2x fa-shopping-cart"></i>
- <p>{{ __("Tap items to add them here") }}</p>
- </span>
- </div>
- <div class="items">
- </div>
- </div>
- </div>
- </div>
- <div class="totals-area">
- <div class="pos-list-row net-total-area">
- <div class="cell"></div>
- <div class="cell text-right">{%= __("Net Total") %}</div>
- <div class="cell price-cell bold net-total text-right"></div>
- </div>
- <div class="pos-list-row tax-area">
- <div class="cell"></div>
- <div class="cell text-right">{%= __("Taxes") %}</div>
- <div class="cell price-cell text-right tax-table">
- </div>
- </div>
- {% if(allow_user_to_edit_discount) { %}
- <div class="pos-list-row discount-amount-area">
- <div class="cell"></div>
- <div class="cell text-right">{%= __("Discount") %}</div>
- <div class="cell price-cell discount-field-col">
- <div class="input-group input-group-sm">
- <span class="input-group-addon">%</span>
- <input type="text" class="form-control discount-percentage text-right">
- </div>
- <div class="input-group input-group-sm">
- <span class="input-group-addon">{%= get_currency_symbol(currency) %}</span>
- <input type="text" class="form-control discount-amount text-right" placeholder="{%= 0.00 %}">
- </div>
- </div>
- </div>
- {% } %}
- <div class="pos-list-row grand-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
- <div class="cell">
- <a class="">
- <i class="octicon octicon-chevron-down"></i>
- </a>
- </div>
- <div class="cell text-right bold">{%= __("Grand Total") %}</div>
- <div class="cell price-cell grand-total text-right lead"></div>
- </div>
- <div class="pos-list-row qty-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
- <div class="cell">
- <a class="">
- <i class="octicon octicon-chevron-down"></i>
- </a>
- </div>
- <div class="cell text-right bold">{%= __("Qty Total") %}</div>
- <div class="cell price-cell qty-total text-right lead"></div>
- </div>
- </div>
- <div class="row" style="margin-top: 30px">
- <div class="col-sm-6 selected-item">
-
- </div>
- <div class="col-xs-6 numeric_keypad hidden-xs" style="display:none">
- {% var chartData = ["Qty", "Disc", "Price"] %} {% for(var i=0; i
- <3; i++) { %} <div class="row text-right">
- {% for(var j=i*3; j
- <(i+1)*3; j++) { %} <button type="button" class="btn btn-default numeric-keypad" val="{{j+1}}">{{j+1}}</button>
- {% } %}
- <button type="button" {% if((!allow_user_to_edit_rate && __(chartData[i]) == __("Price")) || (!allow_user_to_edit_discount && __(chartData[i]) == __("Disc"))) { %} disabled {% } %} id="pos-item-{{ chartData[i].toLowerCase() }}" class="btn text-center btn-default numeric-keypad pos-operation">{{ __(chartData[i]) }}</button>
- </div>
- {% } %}
- <div class="row text-right">
- <button type="button" class="btn btn-default numeric-keypad numeric-del">{{ __("Del") }}</button>
- <button type="button" class="btn btn-default numeric-keypad" val="0">0</button>
- <button type="button" class="btn btn-default numeric-keypad" val=".">.</button>
- <button type="button" class="btn btn-primary numeric-keypad pos-pay">{{ __("Pay") }}</button>
- </div>
- </div>
- </div>
- </div>
- <div class="col-sm-5 list-customers">
- <div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Customers in Queue") }}</h6></div>
- <div class="pos-list-row pos-bill-header">
- <div class="cell subject"><input class="list-select-all" type="checkbox">{{ __("Customer") }}</div>
- <div class="cell text-left">{{ __("Status") }}</div>
- <div class="cell text-right">{{ __("Amount") }}</div>
- <div class="cell text-right">{{ __("Grand Total") }}</div>
- </div>
- <div class="list-customers-table border-left border-right border-bottom">
- <div class="no-items-message text-extra-muted">
- <span class="text-center">
- <i class="fa fa-2x fa-user"></i>
- <p>{{ __("No Customers yet!") }}</p>
- </span>
- </div>
- </div>
- </div>
- <div class="col-sm-7 pos-items-section">
- <div class="col-sm-12"><h6 class="form-section-heading uppercase">{{ __("Stock Items") }}</h6></div>
- <div class="row pos-item-area">
-
- </div>
- <span id="customer-results" style="color:#68a;"></span>
- <div class="item-list-area">
- <div class="pos-list-row pos-bill-header text-muted h6">
- <div class="cell subject search-item-group">
- <div class="dropdown">
- <a class="text-muted dropdown-toggle" data-toggle="dropdown"><span class="dropdown-text">{{ __("All Item Groups") }}</span><i class="caret"></i></a>
- <ul class="dropdown-menu">
- </ul>
- </div>
- </div>
- <div class="cell search-item"></div>
- </div>
- <div class="app-listing item-list image-view-container">
-
- </div>
- </div>
- </div>
-</div>
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 21868a6..0000000
--- a/erpnext/public/js/pos/pos_bill_item.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<div class="row pos-bill-row pos-bill-item" data-item-code="{%= item_code %}">
- <div class="col-xs-4"><h6>{%= item_code || "" %}{%= __(item_name) || "" %}</h6></div>
- <div class="col-xs-3">
- <div class="row pos-qty-row">
- <div class="col-xs-2 text-center pos-qty-btn" data-action="decrease-qty"><i class="fa fa-minus text-muted" style="font-size:12px"></i></div>
- <div class="col-xs-8">
- <div>
- <input type="tel" value="{%= qty %}" class="form-control pos-item-qty text-right">
- </div>
- {% if(actual_qty != null) { %}
- <div style="margin-top: 5px;" class="text-muted small text-right">
- {%= __("In Stock: ") %} <span>{%= actual_qty || 0.0 %}</span>
- </div>
- {% } %}
- </div>
- <div class="col-xs-2 text-center pos-qty-btn" data-action="increase-qty"><i class="fa fa-plus text-muted" style="font-size:12px"></i></div>
- </div>
- </div>
- <div class="col-xs-2 text-right">
- <div class="row input-sm">
- <input type="tel" value="{%= discount_percentage %}" class="form-control text-right pos-item-disc">
- </div>
- </div>
- <div class="col-xs-3 text-right">
- <div class="text-muted" style="margin-top: 5px;">
- {% if(enabled) { %}
- <input type="tel" value="{%= rate %}" class="form-control input-sm pos-item-price text-right">
- {% } else { %}
- <h6>{%= format_currency(rate) %}</h6>
- {% } %}
- </div>
- <p><h6>{%= amount %}</h6></p>
- </div>
-</div>
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 cb626ce..0000000
--- a/erpnext/public/js/pos/pos_bill_item_new.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="pos-list-row pos-bill-item {{ selected_class }}" data-item-code="{{ item_code }}">
- <div class="cell subject">
- <!--<input class="list-row-checkbox" type="checkbox" data-name="{{item_code}}">-->
- <a class="grey list-id" title="{{ item_name }}">{{ strip_html(__(item_name)) || item_code }}</a>
- </div>
- <div class="cell text-right">{%= qty %}</div>
- <div class="cell text-right">{%= discount_percentage %}</div>
- <div class="cell text-right">{%= format_currency(rate) %}</div>
-</div>
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 13aa520..0000000
--- a/erpnext/public/js/pos/pos_invoice_list.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="pos-list-row" invoice-name = "{{name}}">
- <div class="list-column cell subject" invoice-name = "{{name}}">
- <input class="list-delete text-left" type="checkbox" style = "margin-right:5px">
- <a class="grey list-id text-left customer-row" title="{{ customer }}">{%= customer %}</a>
- </div>
- <div class="list-column cell text-left customer-row"><span class="indicator {{data.indicator}}">{{ data.status }}</span></div>
- <div class="list-column cell text-right customer-row">{%= paid_amount %}</div>
- <div class="list-column cell text-right customer-row">{%= grand_total %}</div>
-</div>
diff --git a/erpnext/public/js/pos/pos_item.html b/erpnext/public/js/pos/pos_item.html
deleted file mode 100755
index 52f3cf6..0000000
--- a/erpnext/public/js/pos/pos_item.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<div class="pos-item-wrapper image-view-item" data-item-code="{{item_code}}">
- <div class="image-view-header doclist-row">
- <div class="list-value">
- <a class="grey list-id" data-name="{{item_code}}" title="{{ item_name || item_code}}">{{item_name || item_code}}<br>({{ __(item_stock) }})</a>
- </div>
- </div>
- <div class="image-view-body">
- <a data-item-code="{{ item_code }}"
- title="{{ item_name || item_code }}"
- >
- <div class="image-field"
- style="
- {% if (!item_image) { %}
- background-color: #fafbfc;
- {% } %}
- border: 0px;"
- >
- {% if (!item_image) { %}
- <span class="placeholder-text">
- {%= frappe.get_abbr(item_name || item_code) %}
- </span>
- {% } %}
- {% if (item_image) { %}
- <img src="{{ item_image }}" alt="{{item_name || item_code}}">
- {% } %}
- </div>
- <span class="price-info">
- {{item_price}} / {{item_uom}}
- </span>
- </a>
- </div>
-</div>
\ 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 03c7341..0000000
--- a/erpnext/public/js/pos/pos_selected_item.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<div class="pos-selected-item-action" data-item-code="{%= item_code %}" data-idx="{%= idx %}">
- <div class="pos-list-row">
- <div class="cell">{{ __("Quantity") }}:</div>
- <input type="tel" class="form-control cell pos-item-qty" value="{%= qty %}"/>
- </div>
- <div class="pos-list-row">
- <div class="cell">{{ __("Price List Rate") }}:</div>
- <input type="tel" class="form-control cell" disabled value="{%= price_list_rate %}"/>
- </div>
- <div class="pos-list-row">
- <div class="cell">{{ __("Discount") }}: %</div>
- <input type="tel" class="form-control cell pos-item-disc" {% if !allow_user_to_edit_discount %} disabled {% endif %} value="{%= discount_percentage %}">
- </div>
- <div class="pos-list-row">
- <div class="cell">{{ __("Price") }}:</div>
- <input type="tel" class="form-control cell pos-item-price" {% if !allow_user_to_edit_rate %} disabled {% endif %} value="{%= rate %}"/>
- </div>
- <div class="pos-list-row">
- <div class="cell">{{ __("Amount") }}:</div>
- <input type="tel" class="form-control cell pos-amount" disabled value="{%= amount %}"/>
- </div>
-</div>
\ 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 3752a89..0000000
--- a/erpnext/public/js/pos/pos_tax_row.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<div class="pos-list-row" style="padding-right: 0;">
- <div class="cell">{%= description %}</div>
- <div class="cell text-right bold">{%= tax_amount %}</div>
-</div>
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index 5d21190..ef03b01 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -127,11 +127,9 @@
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) {
@@ -161,7 +159,10 @@
if(r.message){
exist = r.message;
me.get_field("bank_account").set_value("");
- frappe.msgprint(__(`Account ${me.values.bank_account} already exists, enter a different name for your bank account`));
+ let message = __('Account {0} already exists. Please enter a different name for your bank account.',
+ [me.values.bank_account]
+ );
+ frappe.msgprint(message);
}
}
});
diff --git a/erpnext/public/js/telephony.js b/erpnext/public/js/telephony.js
new file mode 100644
index 0000000..6cb1207
--- /dev/null
+++ b/erpnext/public/js/telephony.js
@@ -0,0 +1,23 @@
+frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
+ make_input() {
+ this._super();
+ if (this.df.options == 'Phone') {
+ this.setup_phone();
+ }
+ },
+ setup_phone() {
+ if (frappe.phone_call.handler) {
+ this.$wrapper.find('.control-input')
+ .append(`
+ <span class="phone-btn">
+ <a class="btn-open no-decoration" title="${__('Make a call')}">
+ ${frappe.utils.icon('call')}
+ </span>
+ `)
+ .find('.phone-btn')
+ .click(() => {
+ frappe.phone_call.handler(this.get_value(), this.frm);
+ });
+ }
+ }
+});
diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss
new file mode 100644
index 0000000..c627017
--- /dev/null
+++ b/erpnext/public/scss/point-of-sale.scss
@@ -0,0 +1,1111 @@
+.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;
+ padding-top: var(--padding-md);
+ 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/quality_management/desk_page/quality/quality.json b/erpnext/quality_management/desk_page/quality/quality.json
deleted file mode 100644
index 474f052..0000000
--- a/erpnext/quality_management/desk_page/quality/quality.json
+++ /dev/null
@@ -1,89 +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,
- "icon": "quality",
- "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/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json
new file mode 100644
index 0000000..e5fef43
--- /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/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 787d557..68c8a0d 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -192,19 +192,20 @@
for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"])
- gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC':
- reverse_charge = "Y"
+ reverse_charge = ["Y"]
itc_type = 'All Other ITC'
gst_category = ['Unregistered', 'Overseas']
else:
- reverse_charge = "N"
+ gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
+ reverse_charge = ["N", "Y"]
for account_head in self.account_heads:
for category in gst_category:
- for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
- d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
+ for charge_type in reverse_charge:
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
for key in ['iamt', 'camt', 'samt', 'csamt']:
net_itc[key] += flt(d[key], 2)
@@ -264,7 +265,8 @@
def get_itc_details(self):
itc_amount = frappe.db.sql("""
- select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
+ select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
+ t.account_head, s.eligibility_for_itc, s.reverse_charge
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
@@ -387,7 +389,7 @@
tax_template = 'Purchase Taxes and Charges'
tax_amounts = frappe.db.sql("""
- select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head
+ select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
from `tab{doctype}` s , `tab{template}` t
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/doctype/uae_vat_account/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/doctype/uae_vat_account/__init__.py
diff --git a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json
new file mode 100644
index 0000000..73a8169
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.json
@@ -0,0 +1,35 @@
+{
+ "actions": [],
+ "autoname": "account",
+ "creation": "2020-09-28 11:30:45.472053",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "account"
+ ],
+ "fields": [
+ {
+ "allow_in_quick_entry": 1,
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_preview": 1,
+ "label": "Account",
+ "options": "Account"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-09-28 12:02:56.444007",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "UAE VAT Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py
new file mode 100644
index 0000000..80d6b3a
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_account/uae_vat_account.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class UAEVATAccount(Document):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/regional/doctype/uae_vat_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/regional/doctype/uae_vat_settings/__init__.py
diff --git a/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py
new file mode 100644
index 0000000..b88439f
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.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 TestUAEVATSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js
new file mode 100644
index 0000000..07a9301
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('UAE VAT Settings', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
new file mode 100644
index 0000000..ce2c1d4
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "autoname": "field:company",
+ "creation": "2020-09-25 12:48:51.463265",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "uae_vat_accounts"
+ ],
+ "fields": [
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "uae_vat_accounts",
+ "fieldtype": "Table",
+ "label": "UAE VAT Accounts",
+ "options": "UAE VAT Account",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-09-30 20:08:18.764798",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "UAE VAT Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py
new file mode 100644
index 0000000..20dc604
--- /dev/null
+++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class UAEVATSettings(Document):
+ pass
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
index 3b6a28f..364c0ee 100644
--- a/erpnext/regional/india/taxes.js
+++ b/erpnext/regional/india/taxes.js
@@ -12,6 +12,9 @@
tax_category: function(frm) {
frm.trigger('get_tax_template');
},
+ customer_address: function(frm) {
+ frm.trigger('get_tax_template');
+ },
get_tax_template: function(frm) {
if (!frm.doc.company) return;
@@ -19,6 +22,7 @@
'shipping_address': frm.doc.shipping_address || '',
'shipping_address_name': frm.doc.shipping_address_name || '',
'customer_address': frm.doc.customer_address || '',
+ 'supplier_address': frm.doc.supplier_address,
'customer': frm.doc.customer,
'supplier': frm.doc.supplier,
'supplier_gstin': frm.doc.supplier_gstin,
@@ -31,12 +35,13 @@
args: {
party_details: JSON.stringify(party_details),
doctype: frm.doc.doctype,
- company: frm.doc.company,
- return_taxes: 1
+ company: frm.doc.company
},
+ debounce: 2000,
callback: function(r) {
if(r.message) {
frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
+ frm.set_value('place_of_supply', r.message.place_of_supply);
} else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) {
frm.set_value('taxes_and_charges', '');
frm.set_value('taxes', []);
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 6164e06..f8520c2 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -12,6 +12,7 @@
from six import string_types
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
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -51,6 +52,13 @@
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
+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:
+ frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state))
+ else:
+ frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
+
def update_gst_category(doc, method):
for link in doc.links:
if link.link_doctype in ['Customer', 'Supplier']:
@@ -85,8 +93,7 @@
total += digit
factor = 2 if factor == 1 else 1
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
- frappe.throw(_("""Invalid {0}! The check digit validation has failed.
- Please ensure you've typed the {0} correctly.""".format(label)))
+ frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
@@ -150,17 +157,19 @@
return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
@frappe.whitelist()
-def get_regional_address_details(party_details, doctype, company, return_taxes=None):
+def get_regional_address_details(party_details, doctype, company):
if isinstance(party_details, string_types):
party_details = json.loads(party_details)
party_details = frappe._dict(party_details)
+ update_party_details(party_details, doctype)
+
party_details.place_of_supply = get_place_of_supply(party_details, doctype)
if is_internal_transfer(party_details, doctype):
party_details.taxes_and_charges = ''
party_details.taxes = ''
- return
+ return party_details
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
@@ -168,26 +177,26 @@
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
get_tax_template_based_on_category(master_doctype, company, party_details)
- if party_details.get('taxes_and_charges') and return_taxes:
+ if party_details.get('taxes_and_charges'):
return party_details
if not party_details.company_gstin:
- return
+ return party_details
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
master_doctype = "Purchase Taxes and Charges Template"
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
get_tax_template_based_on_category(master_doctype, company, party_details)
- if party_details.get('taxes_and_charges') and return_taxes:
+ if party_details.get('taxes_and_charges'):
return party_details
if not party_details.supplier_gstin:
- return
+ return party_details
- if not party_details.place_of_supply: return
+ if not party_details.place_of_supply: return party_details
- if not party_details.company_gstin: return
+ if not party_details.company_gstin: return party_details
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
@@ -197,12 +206,16 @@
default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
if not default_tax:
- return
+ return party_details
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
- if return_taxes:
- return party_details
+ return party_details
+
+def update_party_details(party_details, doctype):
+ for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
+ if party_details.get(address_field):
+ party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
def is_internal_transfer(party_details, doctype):
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
@@ -517,6 +530,9 @@
data.actualToStateCode = data.toStateCode
shipping_address = billing_address
+ if doc.gst_category == 'SEZ':
+ data.toStateCode = 99
+
return data
def get_item_list(data, doc):
@@ -752,4 +768,4 @@
}, account_currency, item=tax)
)
- return gl_entries
\ No newline at end of file
+ return gl_entries
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 282efe4..ad3de5f 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -78,7 +78,7 @@
place_of_supply = invoice_details.get("place_of_supply")
ecommerce_gstin = invoice_details.get("ecommerce_gstin")
- b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
+ b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin, inv),{
"place_of_supply": "",
"ecommerce_gstin": "",
"rate": "",
@@ -90,7 +90,7 @@
"invoice_value": invoice_details.get("base_grand_total"),
})
- row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
+ row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin, inv))
row["place_of_supply"] = place_of_supply
row["ecommerce_gstin"] = ecommerce_gstin
row["rate"] = rate
@@ -151,6 +151,7 @@
{select_columns}
from `tab{doctype}`
where docstatus = 1 {where_conditions}
+ and is_opening = 'No'
order by posting_date desc
""".format(select_columns=self.select_columns, doctype=self.doctype,
where_conditions=conditions), self.filters, as_dict=1)
diff --git a/erpnext/config/__init__.py b/erpnext/regional/report/uae_vat_201/__init__.py
similarity index 100%
copy from erpnext/config/__init__.py
copy to erpnext/regional/report/uae_vat_201/__init__.py
diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
new file mode 100644
index 0000000..daa6976
--- /dev/null
+++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
@@ -0,0 +1,239 @@
+# coding=utf-8
+from __future__ import unicode_literals
+
+import erpnext
+import frappe
+from unittest import TestCase
+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.stock.doctype.warehouse.test_warehouse import get_warehouse_account
+from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
+ get_total_emiratewise,
+ get_tourist_tax_return_total,
+ get_tourist_tax_return_tax,
+ get_zero_rated_total,
+ get_exempt_total,
+ get_standard_rated_expenses_total,
+ get_standard_rated_expenses_tax,
+)
+
+test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"]
+
+class TestUaeVat201(TestCase):
+ def setUp(self):
+ frappe.set_user("Administrator")
+
+ frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company UAE VAT'")
+ frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company UAE VAT'")
+
+ make_company("_Test Company UAE VAT", "_TCUV")
+ set_vat_accounts()
+
+ make_customer()
+
+ make_supplier()
+
+ create_warehouse("_Test UAE VAT Supplier Warehouse", company="_Test Company UAE VAT")
+
+ make_item("_Test UAE VAT Item", properties = {"is_zero_rated": 0, "is_exempt": 0})
+ make_item("_Test UAE VAT Zero Rated Item", properties = {"is_zero_rated": 1, "is_exempt": 0})
+ make_item("_Test UAE VAT Exempt Item", properties = {"is_zero_rated": 0, "is_exempt": 1})
+
+ make_sales_invoices()
+
+ create_purchase_invoices()
+
+ def test_uae_vat_201_report(self):
+ filters = {"company": "_Test Company UAE VAT"}
+ total_emiratewise = get_total_emiratewise(filters)
+ amounts_by_emirate = {}
+ for data in total_emiratewise:
+ emirate, amount, vat = data
+ amounts_by_emirate[emirate] = {
+ "raw_amount": amount,
+ "raw_vat_amount": vat,
+ }
+ self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],100)
+ self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5)
+ self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200)
+ self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10)
+ self.assertEqual(get_tourist_tax_return_total(filters),100)
+ self.assertEqual(get_tourist_tax_return_tax(filters),2)
+ self.assertEqual(get_zero_rated_total(filters),100)
+ self.assertEqual(get_exempt_total(filters),100)
+ self.assertEqual(get_standard_rated_expenses_total(filters),250)
+ self.assertEqual(get_standard_rated_expenses_tax(filters),1)
+
+def make_company(company_name, abbr):
+ if not frappe.db.exists("Company", company_name):
+ company = frappe.get_doc({
+ "doctype": "Company",
+ "company_name": company_name,
+ "abbr": abbr,
+ "default_currency": "AED",
+ "country": "United Arab Emirates",
+ "create_chart_of_accounts_based_on": "Standard Template",
+ })
+ company.insert()
+ else:
+ company = frappe.get_doc("Company", company_name)
+
+ company.create_default_warehouses()
+
+ if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
+ company.create_default_cost_center()
+
+ company.save()
+ return company
+
+def set_vat_accounts():
+ if not frappe.db.exists("UAE VAT Settings", "_Test Company UAE VAT"):
+ vat_accounts = frappe.get_all(
+ "Account",
+ fields=["name"],
+ filters = {
+ "company": "_Test Company UAE VAT",
+ "is_group": 0,
+ "account_type": "Tax"
+ }
+ )
+
+ uae_vat_accounts = []
+ for account in vat_accounts:
+ uae_vat_accounts.append({
+ "doctype": "UAE VAT Account",
+ "account": account.name
+ })
+
+ frappe.get_doc({
+ "company": "_Test Company UAE VAT",
+ "uae_vat_accounts": uae_vat_accounts,
+ "doctype": "UAE VAT Settings",
+ }).insert()
+
+def make_customer():
+ if not frappe.db.exists("Customer", "_Test UAE Customer"):
+ customer = frappe.get_doc({
+ "doctype": "Customer",
+ "customer_name": "_Test UAE Customer",
+ "customer_type": "Company",
+ })
+ customer.insert()
+ else:
+ customer = frappe.get_doc("Customer", "_Test UAE Customer")
+
+def make_supplier():
+ if not frappe.db.exists("Supplier", "_Test UAE Supplier"):
+ frappe.get_doc({
+ "supplier_group": "Local",
+ "supplier_name": "_Test UAE Supplier",
+ "supplier_type": "Individual",
+ "doctype": "Supplier",
+ }).insert()
+
+def create_warehouse(warehouse_name, properties=None, company=None):
+ if not company:
+ company = "_Test Company"
+
+ warehouse_id = erpnext.encode_company_abbr(warehouse_name, company)
+ if not frappe.db.exists("Warehouse", warehouse_id):
+ warehouse = frappe.new_doc("Warehouse")
+ warehouse.warehouse_name = warehouse_name
+ warehouse.parent_warehouse = "All Warehouses - _TCUV"
+ warehouse.company = company
+ warehouse.account = get_warehouse_account(warehouse_name, company)
+ if properties:
+ warehouse.update(properties)
+ warehouse.save()
+ return warehouse.name
+ else:
+ return warehouse_id
+
+def make_item(item_code, properties=None):
+ if frappe.db.exists("Item", item_code):
+ return frappe.get_doc("Item", item_code)
+
+ item = frappe.get_doc({
+ "doctype": "Item",
+ "item_code": item_code,
+ "item_name": item_code,
+ "description": item_code,
+ "item_group": "Products"
+ })
+
+ if properties:
+ item.update(properties)
+
+ item.insert()
+
+ return item
+
+def make_sales_invoices():
+ def make_sales_invoices_wrapper(emirate, item, tax = True, tourist_tax= False):
+ si = create_sales_invoice(
+ company="_Test Company UAE VAT",
+ customer = '_Test UAE Customer',
+ currency = 'AED',
+ warehouse = 'Finished Goods - _TCUV',
+ debit_to = 'Debtors - _TCUV',
+ income_account = 'Sales - _TCUV',
+ expense_account = 'Cost of Goods Sold - _TCUV',
+ cost_center = 'Main - _TCUV',
+ item = item,
+ do_not_save=1
+ )
+ si.vat_emirate = emirate
+ if tax:
+ si.append(
+ "taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "VAT 5% - _TCUV",
+ "cost_center": "Main - _TCUV",
+ "description": "VAT 5% @ 5.0",
+ "rate": 5.0
+ }
+ )
+ if tourist_tax:
+ si.tourist_tax_return = 2
+ si.submit()
+
+ #Define Item Names
+ uae_item = "_Test UAE VAT Item"
+ uae_exempt_item = "_Test UAE VAT Exempt Item"
+ uae_zero_rated_item = "_Test UAE VAT Zero Rated Item"
+
+ #Sales Invoice with standard rated expense in Dubai
+ make_sales_invoices_wrapper('Dubai', uae_item)
+ #Sales Invoice with standard rated expense in Sharjah
+ make_sales_invoices_wrapper('Sharjah', uae_item)
+ #Sales Invoice with Tourist Tax Return
+ make_sales_invoices_wrapper('Dubai', uae_item, True, True)
+ #Sales Invoice with Exempt Item
+ make_sales_invoices_wrapper('Sharjah', uae_exempt_item, False)
+ #Sales Invoice with Zero Rated Item
+ make_sales_invoices_wrapper('Sharjah', uae_zero_rated_item, False)
+
+def create_purchase_invoices():
+ pi = make_purchase_invoice(
+ company="_Test Company UAE VAT",
+ supplier = '_Test UAE Supplier',
+ supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV',
+ warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV',
+ currency = 'AED',
+ cost_center = 'Main - _TCUV',
+ expense_account = 'Cost of Goods Sold - _TCUV',
+ item = "_Test UAE VAT Item",
+ do_not_save=1,
+ uom = "Nos"
+ )
+ pi.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "VAT 5% - _TCUV",
+ "cost_center": "Main - _TCUV",
+ "description": "VAT 5% @ 5.0",
+ "rate": 5.0
+ })
+
+ pi.recoverable_standard_rated_expenses = 1
+
+ pi.submit()
diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.html b/erpnext/regional/report/uae_vat_201/uae_vat_201.html
new file mode 100644
index 0000000..d9b9968
--- /dev/null
+++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.html
@@ -0,0 +1,77 @@
+{%
+ var report_columns = report.get_columns_for_print();
+ report_columns = report_columns.filter(col => !col.hidden);
+%}
+<style>
+ .print-format {
+ padding: 10mm;
+ font-size: 8.0pt !important;
+ font-family: Tahoma, sans-serif;
+ }
+</style>
+
+<h1 style="margin-top:0; text-align: center;">{%= __(report.report_name) %}</h1>
+
+<h3 style="margin-top:0; font-weight:500">{%= __("VAT on Sales and All Other Outputs") %}</h2>
+
+<table class="table table-bordered">
+
+ <thead>
+ <th style="width: 13">{%= report_columns[0].label %}</th>
+ <th style="width: {%= 100 - (report_columns.length - 1) * 13%}%">{%= report_columns[1].label %}</th>
+
+ {% for (let i=2; i<report_columns.length; i++) { %}
+ <th style="width: 13">{%= report_columns[i].label %}</th>
+ {% } %}
+ </thead>
+
+ <tbody>
+ {% for (let j=1; j<12; j++) { %}
+ {%
+ var row = data[j];
+ %}
+ <tr >
+ {% for (let i=0; i<report_columns.length; i++) { %}
+ <td >
+ {% const fieldname = report_columns[i].fieldname; %}
+ {% if (!is_null(row[fieldname])) { %}
+ {%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
+ {% } %}
+ </td>
+ {% } %}
+ </tr>
+ {% } %}
+ </tbody>
+</table>
+
+<h3 style="margin-top:0; font-weight:500">{%= __("VAT on Expenses and All Other Inputs") %}</h2>
+
+<table class="table table-bordered">
+ <thead>
+ <th style="width: 13">{%= report_columns[0].label %}</th>
+ <th style="width: {%= 100 - (report_columns.length - 1) * 13%}%">{%= report_columns[1].label %}</th>
+
+ {% for (let i=2; i<report_columns.length; i++) { %}
+ <th style="width: 13">{%= report_columns[i].label %}</th>
+ {% } %}
+ </thead>
+
+ <tbody>
+ {% for (let j=14; j<data.length; j++) { %}
+ {%
+ var row = data[j];
+ %}
+ <tr >
+ {% for (let i=0; i<report_columns.length; i++) { %}
+ <td >
+ {% const fieldname = report_columns[i].fieldname; %}
+ {% if (!is_null(row[fieldname])) { %}
+ {%= frappe.format(row[fieldname], report_columns[i], {}, row) %}
+ {% } %}
+ </td>
+ {% } %}
+ </tr>
+ {% } %}
+ </tbody>
+
+</table>
\ No newline at end of file
diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.js b/erpnext/regional/report/uae_vat_201/uae_vat_201.js
new file mode 100644
index 0000000..5957424
--- /dev/null
+++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["UAE VAT 201"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -3),
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ if (data
+ && (data.legend=='VAT on Sales and All Other Outputs' || data.legend=='VAT on Expenses and All Other Inputs')
+ && data.legend==value) {
+ value = $(`<span>${value}</span>`);
+ var $value = $(value).css("font-weight", "bold");
+ value = $value.wrap("<p></p>").parent().html();
+ }
+ return value;
+ },
+};
diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.json b/erpnext/regional/report/uae_vat_201/uae_vat_201.json
new file mode 100644
index 0000000..8a88bcd
--- /dev/null
+++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.json
@@ -0,0 +1,22 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-09-10 08:51:02.298482",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-09-10 08:51:02.298482",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "UAE VAT 201",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "UAE VAT 201",
+ "report_type": "Script Report",
+ "roles": []
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py
new file mode 100644
index 0000000..b061423
--- /dev/null
+++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py
@@ -0,0 +1,339 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ columns = get_columns()
+ data, emirates, amounts_by_emirate = get_data(filters)
+ return columns, data
+
+def get_columns():
+ """Creates a list of dictionaries that are used to generate column headers of the data table."""
+ return [
+ {
+ "fieldname": "no",
+ "label": _("No"),
+ "fieldtype": "Data",
+ "width": 50
+ },
+ {
+ "fieldname": "legend",
+ "label": _("Legend"),
+ "fieldtype": "Data",
+ "width": 300
+ },
+ {
+ "fieldname": "amount",
+ "label": _("Amount (AED)"),
+ "fieldtype": "Currency",
+ "width": 125,
+ },
+ {
+ "fieldname": "vat_amount",
+ "label": _("VAT Amount (AED)"),
+ "fieldtype": "Currency",
+ "width": 150,
+ }
+ ]
+
+def get_data(filters = None):
+ """Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data."""
+ data = []
+ emirates, amounts_by_emirate = append_vat_on_sales(data, filters)
+ append_vat_on_expenses(data, filters)
+ return data, emirates, amounts_by_emirate
+
+def append_vat_on_sales(data, filters):
+ """Appends Sales and All Other Outputs."""
+ append_data(data, '', _('VAT on Sales and All Other Outputs'), '', '')
+
+ emirates, amounts_by_emirate = standard_rated_expenses_emiratewise(data, filters)
+
+ append_data(data, '2',
+ _('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'),
+ frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'),
+ frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency'))
+
+ append_data(data, '3', _('Supplies subject to the reverse charge provision'),
+ frappe.format(get_reverse_charge_total(filters), 'Currency'),
+ frappe.format(get_reverse_charge_tax(filters), 'Currency'))
+
+ append_data(data, '4', _('Zero Rated'),
+ frappe.format(get_zero_rated_total(filters), 'Currency'), "-")
+
+ append_data(data, '5', _('Exempt Supplies'),
+ frappe.format(get_exempt_total(filters), 'Currency'),"-")
+
+ append_data(data, '', '', '', '')
+
+ return emirates, amounts_by_emirate
+
+def standard_rated_expenses_emiratewise(data, filters):
+ """Append emiratewise standard rated expenses and vat."""
+ total_emiratewise = get_total_emiratewise(filters)
+ emirates = get_emirates()
+ amounts_by_emirate = {}
+ for emirate, amount, vat in total_emiratewise:
+ amounts_by_emirate[emirate] = {
+ "legend": emirate,
+ "raw_amount": amount,
+ "raw_vat_amount": vat,
+ "amount": frappe.format(amount, 'Currency'),
+ "vat_amount": frappe.format(vat, 'Currency'),
+ }
+ amounts_by_emirate = append_emiratewise_expenses(data, emirates, amounts_by_emirate)
+ return emirates, amounts_by_emirate
+
+def append_emiratewise_expenses(data, emirates, amounts_by_emirate):
+ """Append emiratewise standard rated expenses and vat."""
+ for no, emirate in enumerate(emirates, 97):
+ if emirate in amounts_by_emirate:
+ amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(no))
+ amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate)
+ data.append(amounts_by_emirate[emirate])
+ else:
+ append_data(data, _('1{0}').format(chr(no)),
+ _('Standard rated supplies in {0}').format(emirate),
+ frappe.format(0, 'Currency'), frappe.format(0, 'Currency'))
+ return amounts_by_emirate
+
+def append_vat_on_expenses(data, filters):
+ """Appends Expenses and All Other Inputs."""
+ append_data(data, '', _('VAT on Expenses and All Other Inputs'), '', '')
+ append_data(data, '9', _('Standard Rated Expenses'),
+ frappe.format(get_standard_rated_expenses_total(filters), 'Currency'),
+ frappe.format(get_standard_rated_expenses_tax(filters), 'Currency'))
+ append_data(data, '10', _('Supplies subject to the reverse charge provision'),
+ frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'),
+ frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency'))
+
+def append_data(data, no, legend, amount, vat_amount):
+ """Returns data with appended value."""
+ data.append({"no": no, "legend":legend, "amount": amount, "vat_amount": vat_amount})
+
+def get_total_emiratewise(filters):
+ """Returns Emiratewise Amount and Taxes."""
+ conditions = get_conditions(filters)
+ try:
+ return frappe.db.sql("""
+ select
+ s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges)
+ from
+ `tabSales Invoice Item` i inner join `tabSales Invoice` s
+ on
+ i.parent = s.name
+ where
+ s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1
+ {where_conditions}
+ group by
+ s.vat_emirate;
+ """.format(where_conditions=conditions), filters)
+ except (IndexError, TypeError):
+ return 0
+
+def get_emirates():
+ """Returns a List of emirates in the order that they are to be displayed."""
+ return [
+ 'Abu Dhabi',
+ 'Dubai',
+ 'Sharjah',
+ 'Ajman',
+ 'Umm Al Quwain',
+ 'Ras Al Khaimah',
+ 'Fujairah'
+ ]
+
+def get_filters(filters):
+ """The conditions to be used to filter data to calculate the total sale."""
+ query_filters = []
+ if filters.get("company"):
+ query_filters.append(["company", '=', filters['company']])
+ if filters.get("from_date"):
+ query_filters.append(["posting_date", '>=', filters['from_date']])
+ if filters.get("from_date"):
+ query_filters.append(["posting_date", '<=', filters['to_date']])
+ return query_filters
+
+def get_reverse_charge_total(filters):
+ """Returns the sum of the total of each Purchase invoice made."""
+ query_filters = get_filters(filters)
+ query_filters.append(['reverse_charge', '=', 'Y'])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Purchase Invoice',
+ filters = query_filters,
+ fields = ['sum(total)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_reverse_charge_tax(filters):
+ """Returns the sum of the tax of each Purchase invoice made."""
+ conditions = get_conditions_join(filters)
+ return frappe.db.sql("""
+ select sum(debit) from
+ `tabPurchase Invoice` p inner join `tabGL Entry` gl
+ on
+ gl.voucher_no = p.name
+ where
+ p.reverse_charge = "Y"
+ and p.docstatus = 1
+ and gl.docstatus = 1
+ and account in (select account from `tabUAE VAT Account` where parent=%(company)s)
+ {where_conditions} ;
+ """.format(where_conditions=conditions), filters)[0][0] or 0
+
+def get_reverse_charge_recoverable_total(filters):
+ """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge."""
+ query_filters = get_filters(filters)
+ query_filters.append(['reverse_charge', '=', 'Y'])
+ query_filters.append(['recoverable_reverse_charge', '>', '0'])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Purchase Invoice',
+ filters = query_filters,
+ fields = ['sum(total)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_reverse_charge_recoverable_tax(filters):
+ """Returns the sum of the tax of each Purchase invoice made."""
+ conditions = get_conditions_join(filters)
+ return frappe.db.sql("""
+ select
+ sum(debit * p.recoverable_reverse_charge / 100)
+ from
+ `tabPurchase Invoice` p inner join `tabGL Entry` gl
+ on
+ gl.voucher_no = p.name
+ where
+ p.reverse_charge = "Y"
+ and p.docstatus = 1
+ and p.recoverable_reverse_charge > 0
+ and gl.docstatus = 1
+ and account in (select account from `tabUAE VAT Account` where parent=%(company)s)
+ {where_conditions} ;
+ """.format(where_conditions=conditions), filters)[0][0] or 0
+
+def get_conditions_join(filters):
+ """The conditions to be used to filter data to calculate the total vat."""
+ conditions = ""
+ for opts in (("company", " and p.company=%(company)s"),
+ ("from_date", " and p.posting_date>=%(from_date)s"),
+ ("to_date", " and p.posting_date<=%(to_date)s")):
+ if filters.get(opts[0]):
+ conditions += opts[1]
+ return conditions
+
+def get_standard_rated_expenses_total(filters):
+ """Returns the sum of the total of each Purchase invoice made with recoverable reverse charge."""
+ query_filters = get_filters(filters)
+ query_filters.append(['recoverable_standard_rated_expenses', '>', 0])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Purchase Invoice',
+ filters = query_filters,
+ fields = ['sum(total)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_standard_rated_expenses_tax(filters):
+ """Returns the sum of the tax of each Purchase invoice made."""
+ query_filters = get_filters(filters)
+ query_filters.append(['recoverable_standard_rated_expenses', '>', 0])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Purchase Invoice',
+ filters = query_filters,
+ fields = ['sum(recoverable_standard_rated_expenses)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_tourist_tax_return_total(filters):
+ """Returns the sum of the total of each Sales invoice with non zero tourist_tax_return."""
+ query_filters = get_filters(filters)
+ query_filters.append(['tourist_tax_return', '>', 0])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Sales Invoice',
+ filters = query_filters,
+ fields = ['sum(total)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_tourist_tax_return_tax(filters):
+ """Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return."""
+ query_filters = get_filters(filters)
+ query_filters.append(['tourist_tax_return', '>', 0])
+ query_filters.append(['docstatus', '=', 1])
+ try:
+ return frappe.db.get_all('Sales Invoice',
+ filters = query_filters,
+ fields = ['sum(tourist_tax_return)'],
+ as_list=True,
+ limit = 1
+ )[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_zero_rated_total(filters):
+ """Returns the sum of each Sales Invoice Item Amount which is zero rated."""
+ conditions = get_conditions(filters)
+ try:
+ return frappe.db.sql("""
+ select
+ sum(i.base_amount) as total
+ from
+ `tabSales Invoice Item` i inner join `tabSales Invoice` s
+ on
+ i.parent = s.name
+ where
+ s.docstatus = 1 and i.is_zero_rated = 1
+ {where_conditions} ;
+ """.format(where_conditions=conditions), filters)[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+
+def get_exempt_total(filters):
+ """Returns the sum of each Sales Invoice Item Amount which is Vat Exempt."""
+ conditions = get_conditions(filters)
+ try:
+ return frappe.db.sql("""
+ select
+ sum(i.base_amount) as total
+ from
+ `tabSales Invoice Item` i inner join `tabSales Invoice` s
+ on
+ i.parent = s.name
+ where
+ s.docstatus = 1 and i.is_exempt = 1
+ {where_conditions} ;
+ """.format(where_conditions=conditions), filters)[0][0] or 0
+ except (IndexError, TypeError):
+ return 0
+def get_conditions(filters):
+ """The conditions to be used to filter data to calculate the total sale."""
+ conditions = ""
+ for opts in (("company", " and company=%(company)s"),
+ ("from_date", " and posting_date>=%(from_date)s"),
+ ("to_date", " and posting_date<=%(to_date)s")):
+ if filters.get(opts[0]):
+ conditions += opts[1]
+ return conditions
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 250659e..013ae5c 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -5,24 +5,30 @@
import frappe, os, json
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.permissions import add_permission, update_permission_property
from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
def setup(company=None, patch=True):
make_custom_fields()
add_print_formats()
-
+ add_custom_roles_for_reports()
+ add_permissions()
if company:
create_sales_tax(company)
def make_custom_fields():
+ is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
+ fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
+ print_hide=1)
+ is_exempt = dict(fieldname='is_exempt', label='Is Exempt',
+ fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated',
+ print_hide=1)
+
invoice_fields = [
dict(fieldname='vat_section', label='VAT Details', fieldtype='Section Break',
insert_after='group_same_items', print_hide=1, collapsible=1),
dict(fieldname='permit_no', label='Permit Number',
fieldtype='Data', insert_after='vat_section', print_hide=1),
- dict(fieldname='reverse_charge_applicable', label='Reverse Charge Applicable',
- fieldtype='Select', insert_after='permit_no', print_hide=1,
- options='Y\nN', default='N')
]
purchase_invoice_fields = [
@@ -31,7 +37,16 @@
fetch_from='company.tax_id', print_hide=1),
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
fieldtype='Read Only', insert_after='supplier_name',
- fetch_from='supplier.supplier_name_in_arabic', print_hide=1)
+ fetch_from='supplier.supplier_name_in_arabic', print_hide=1),
+ dict(fieldname='recoverable_standard_rated_expenses', print_hide=1, default='0',
+ label='Recoverable Standard Rated Expenses (AED)', insert_after='permit_no',
+ fieldtype='Currency', ),
+ dict(fieldname='reverse_charge', label='Reverse Charge Applicable',
+ fieldtype='Select', insert_after='recoverable_standard_rated_expenses', print_hide=1,
+ options='Y\nN', default='N'),
+ dict(fieldname='recoverable_reverse_charge', label='Recoverable Reverse Charge (Percentage)',
+ insert_after='reverse_charge', fieldtype='Percent', print_hide=1,
+ depends_on="eval:doc.reverse_charge=='Y'", default='100.000'),
]
sales_invoice_fields = [
@@ -41,6 +56,11 @@
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
fieldtype='Read Only', insert_after='customer_name',
fetch_from='customer.customer_name_in_arabic', print_hide=1),
+ dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select',
+ options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain',
+ fetch_from='company_address.emirate'),
+ dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)',
+ insert_after='vat_emirate', fieldtype='Currency', print_hide=1, default='0'),
]
invoice_item_fields = [
@@ -67,6 +87,12 @@
'Item': [
dict(fieldname='tax_code', label='Tax Code',
fieldtype='Data', insert_after='item_group'),
+ dict(fieldname='is_zero_rated', label='Is Zero Rated',
+ fieldtype='Check', insert_after='tax_code',
+ print_hide=1),
+ dict(fieldname='is_exempt', label='Is Exempt',
+ fieldtype='Check', insert_after='is_zero_rated',
+ print_hide=1)
],
'Customer': [
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
@@ -76,13 +102,17 @@
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
fieldtype='Data', insert_after='supplier_name'),
],
+ 'Address': [
+ dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state',
+ options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain')
+ ],
'Purchase Invoice': purchase_invoice_fields + invoice_fields,
'Purchase Order': purchase_invoice_fields + invoice_fields,
'Purchase Receipt': purchase_invoice_fields + invoice_fields,
'Sales Invoice': sales_invoice_fields + invoice_fields,
'Sales Order': sales_invoice_fields + invoice_fields,
'Delivery Note': sales_invoice_fields + invoice_fields,
- 'Sales Invoice Item': invoice_item_fields + delivery_date_field,
+ 'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,
@@ -101,3 +131,25 @@
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """)
+
+def add_custom_roles_for_reports():
+ """Add Access Control to UAE VAT 201."""
+ if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 201')):
+ frappe.get_doc(dict(
+ doctype='Custom Role',
+ report='UAE VAT 201',
+ roles= [
+ dict(role='Accounts User'),
+ dict(role='Accounts Manager'),
+ dict(role='Auditor')
+ ]
+ )).insert()
+
+def add_permissions():
+ """Add Permissions for UAE VAT Settings and UAE VAT Account."""
+ for doctype in ('UAE VAT Settings', 'UAE VAT Account'):
+ add_permission(doctype, 'All', 0)
+ for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
+ add_permission(doctype, role, 0)
+ update_permission_property(doctype, role, 0, 'write', 1)
+ update_permission_property(doctype, role, 0, 'create', 1)
diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py
index a0425f6..7d5fd6e 100644
--- a/erpnext/regional/united_arab_emirates/utils.py
+++ b/erpnext/regional/united_arab_emirates/utils.py
@@ -1,6 +1,8 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import flt
+from frappe import _
+import erpnext
+from frappe.utils import flt, round_based_on_smallest_currency_fraction, money_in_words
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from six import iteritems
@@ -26,4 +28,134 @@
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount"))
- row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
\ No newline at end of file
+ row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
+
+def get_account_currency(account):
+ """Helper function to get account currency."""
+ if not account:
+ return
+ def generator():
+ account_currency, company = frappe.get_cached_value(
+ "Account",
+ account,
+ ["account_currency",
+ "company"]
+ )
+ if not account_currency:
+ account_currency = frappe.get_cached_value('Company', company, "default_currency")
+
+ return account_currency
+
+ return frappe.local_cache("account_currency", account, generator)
+
+def get_tax_accounts(company):
+ """Get the list of tax accounts for a specific company."""
+ tax_accounts_dict = frappe._dict()
+ tax_accounts_list = frappe.get_all("UAE VAT Account",
+ filters={"parent": company},
+ fields=["Account"]
+ )
+
+ if not tax_accounts_list and not frappe.flags.in_test:
+ frappe.throw(_('Please set Vat Accounts for Company: "{0}" in UAE VAT Settings').format(company))
+ for tax_account in tax_accounts_list:
+ for account, name in tax_account.items():
+ tax_accounts_dict[name] = name
+
+ return tax_accounts_dict
+
+def update_grand_total_for_rcm(doc, method):
+ """If the Reverse Charge is Applicable subtract the tax amount from the grand total and update in the form."""
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'United Arab Emirates':
+ return
+
+ if not doc.total_taxes_and_charges:
+ return
+
+ if doc.reverse_charge == 'Y':
+ tax_accounts = get_tax_accounts(doc.company)
+
+ base_vat_tax = 0
+ vat_tax = 0
+
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts:
+ base_vat_tax += tax.base_tax_amount_after_discount_amount
+ vat_tax += tax.tax_amount_after_discount_amount
+
+ doc.taxes_and_charges_added -= vat_tax
+ doc.total_taxes_and_charges -= vat_tax
+ doc.base_taxes_and_charges_added -= base_vat_tax
+ doc.base_total_taxes_and_charges -= base_vat_tax
+
+ update_totals(vat_tax, base_vat_tax, doc)
+
+def update_totals(vat_tax, base_vat_tax, doc):
+ """Update the grand total values in the form."""
+ doc.base_grand_total -= base_vat_tax
+ doc.grand_total -= vat_tax
+
+ if doc.meta.get_field("rounded_total"):
+
+ if doc.is_rounded_total_disabled():
+ doc.outstanding_amount = doc.grand_total
+
+ else:
+ doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
+ doc.currency, doc.precision("rounded_total"))
+ doc.rounding_adjustment = flt(doc.rounded_total - doc.grand_total,
+ doc.precision("rounding_adjustment"))
+ doc.outstanding_amount = doc.rounded_total or doc.grand_total
+
+ doc.in_words = money_in_words(doc.grand_total, doc.currency)
+ doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
+ doc.set_payment_schedule()
+
+def make_regional_gl_entries(gl_entries, doc):
+ """Hooked to make_regional_gl_entries in Purchase Invoice.It appends the region specific general ledger entries to the list of GL Entries."""
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'United Arab Emirates':
+ return gl_entries
+
+ if doc.reverse_charge == 'Y':
+ tax_accounts = get_tax_accounts(doc.company)
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+ gl_entries = make_gl_entry(tax, gl_entries, doc, tax_accounts)
+ return gl_entries
+
+def make_gl_entry(tax, gl_entries, doc, tax_accounts):
+ dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts:
+ account_currency = get_account_currency(tax.account_head)
+
+ gl_entries.append(doc.get_gl_dict({
+ "account": tax.account_head,
+ "cost_center": tax.cost_center,
+ "posting_date": doc.posting_date,
+ "against": doc.supplier,
+ dr_or_cr: tax.base_tax_amount_after_discount_amount,
+ dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
+ if account_currency==doc.company_currency \
+ else tax.tax_amount_after_discount_amount
+ }, account_currency, item=tax
+ ))
+ return gl_entries
+
+
+def validate_returns(doc, method):
+ """Standard Rated expenses should not be set when Reverse Charge Applicable is set."""
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+ if country != 'United Arab Emirates':
+ return
+ if doc.reverse_charge == 'Y' and flt(doc.recoverable_standard_rated_expenses) != 0:
+ frappe.throw(_(
+ "Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y"
+ ))
diff --git a/erpnext/selling/desk_page/retail/retail.json b/erpnext/selling/desk_page/retail/retail.json
deleted file mode 100644
index cdafaea..0000000
--- a/erpnext/selling/desk_page/retail/retail.json
+++ /dev/null
@@ -1,48 +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,
- "icon": "retail",
- "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 82831ab..0000000
--- a/erpnext/selling/desk_page/selling/selling.json
+++ /dev/null
@@ -1,93 +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,
- "icon": "sell",
- "idx": 0,
- "is_standard": 1,
- "label": "Selling",
- "modified": "2020-10-21 12:30:12.164433",
- "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/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 0172d9c..29214ee 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -58,6 +58,7 @@
self.set_loyalty_program()
self.check_customer_group_change()
self.validate_default_bank_account()
+ self.validate_internal_customer()
# set loyalty program tier
if frappe.db.exists('Customer', self.name):
@@ -82,6 +83,11 @@
if not is_company_account:
frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
+ def validate_internal_customer(self):
+ if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
+ frappe.throw(_("Internal Customer for company {0} already exists").format(
+ frappe.bold(self.represents_company)))
+
def on_update(self):
self.validate_name_with_customer_group()
self.create_primary_contact()
@@ -398,7 +404,7 @@
# form a list of emails and names to show to the user
credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
if not credit_controller_users_formatted:
- frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
+ frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.").format(customer))
message = """Please contact any of the following users to extend the credit limits for {0}:
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 5b85187..3eba62b 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:08",
@@ -932,7 +933,7 @@
"is_submittable": 1,
"links": [],
"max_attachments": 1,
- "modified": "2020-07-26 17:46:19.951223",
+ "modified": "2020-10-30 13:58:59.212060",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 73cc0b8..842566b 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -8,7 +8,7 @@
frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note',
'Pick List': 'Pick List',
- 'Sales Invoice': 'Invoice',
+ 'Sales Invoice': 'Sales Invoice',
'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order',
'Project': 'Project',
@@ -326,9 +326,8 @@
callback: function(r) {
if(r.message) {
frappe.msgprint({
- message: __('Work Orders Created: {0}',
- [r.message.map(function(d) {
- return repl('<a href="#Form/Work Order/%(name)s">%(name)s</a>', {name:d})
+ message: __('Work Orders Created: {0}', [r.message.map(function(d) {
+ return repl('<a href="/app/work-order/%(name)s">%(name)s</a>', {name:d})
}).join(', ')]),
indicator: 'green'
})
@@ -437,7 +436,7 @@
callback: function(r) {
if(r.message) {
frappe.msgprint(__('Material Request {0} submitted.',
- ['<a href="#Form/Material Request/'+r.message.name+'">' + r.message.name+ '</a>']));
+ ['<a href="/app/material-request/'+r.message.name+'">' + r.message.name+ '</a>']));
}
d.hide();
me.frm.reload_doc();
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 77c1787..3d64ac3 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-06-18 12:39:59",
@@ -1460,7 +1461,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-07 14:30:01.782617",
+ "modified": "2020-10-30 13:59:18.628077",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 04d85e5..9388e09 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -14,7 +14,6 @@
from frappe.desk.notifications import clear_doctype_notifications
from frappe.contacts.doctype.address.address import get_company_address
from erpnext.controllers.selling_controller import SellingController
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -418,8 +417,7 @@
def on_recurring(self, reference_doc, auto_repeat_doc):
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
- delivery_date = get_next_schedule_date(ref_doc_delivery_date,
- auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
+ delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
if delivery_date <= transaction_date:
delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)
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 428dc75..0000000
--- 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;a<t.length;a++)if(!0===n.matches(t[a]))return!0}else if(n.matches(t))return!0;return!1},_validateScanCode:function(e,t){var n,a=e.scannerDetectionData,i=a.options,o=a.options.singleScanQty,r=a.vars.firstCharTime,s=a.vars.lastCharTime,c={};switch(!0){case t.length<i.minLength:c={message:"Receieved code is shorter then minimal length"};break;case s-r>t.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<e.scannerDetectionData.vars.firstCharTime}};return d});
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index 9d44a9f..6d8ad7e 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -1,7 +1,4 @@
-/* global Clusterize */
frappe.provide('erpnext.PointOfSale');
-{% include "erpnext/selling/page/point_of_sale/pos_controller.js" %}
-frappe.provide('erpnext.queries');
frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
@@ -10,8 +7,10 @@
single_column: true
});
- wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
- window.cur_pos = wrapper.pos;
+ frappe.require('assets/js/point-of-sale.min.js', function() {
+ wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
+ window.cur_pos = wrapper.pos;
+ })
};
frappe.pages['point-of-sale'].refresh = function(wrapper) {
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index a690050..062cba1 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -62,6 +62,7 @@
`tabItem` item {bin_join_selection}
WHERE
item.disabled = 0
+ AND item.is_stock_item = 1
AND item.has_variants = 0
AND item.is_sales_item = 1
AND item.is_fixed_asset = 0
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 970d840..07f58ae 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -1,23 +1,9 @@
-{% include "erpnext/selling/page/point_of_sale/onscan.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_item_selector.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_item_cart.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_item_details.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_payment.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_past_order_list.js" %}
-{% include "erpnext/selling/page/point_of_sale/pos_past_order_summary.js" %}
-
erpnext.PointOfSale.Controller = class {
constructor(wrapper) {
this.wrapper = $(wrapper).find('.layout-main-section');
this.page = wrapper.page;
- this.load_assets();
- }
-
- load_assets() {
- // after loading assets first check if opening entry has been made
- frappe.require(['assets/erpnext/css/pos.css'], this.check_opening_entry.bind(this));
+ this.check_opening_entry();
}
fetch_opening_entry() {
@@ -36,6 +22,7 @@
}
create_opening_voucher() {
+ const me = this;
const table_fields = [
{
fieldname: "mode_of_payment", fieldtype: "Link",
@@ -45,7 +32,7 @@
{
fieldname: "opening_amount", fieldtype: "Currency",
in_list_view: 1, label: "Opening Amount",
- options: "company:company_currency",
+ options: "company:company_currency",
change: function () {
dialog.fields_dict.balance_details.df.data.some(d => {
if (d.idx == this.doc.idx) {
@@ -93,7 +80,7 @@
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."),
@@ -103,7 +90,7 @@
}
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')
@@ -111,56 +98,39 @@
dialog.show();
}
- prepare_app_defaults(data) {
+ async prepare_app_defaults(data) {
this.pos_opening = data.name;
this.company = data.company;
this.pos_profile = data.pos_profile;
this.pos_opening_time = data.period_start_date;
+ this.item_stock_map = {};
+ this.settings = {};
frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
this.allow_negative_stock = flt(message.allow_negative_stock) || false;
});
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
- this.customer_groups = profile.customer_groups.map(group => group.customer_group);
- this.cart.make_customer_selector();
+ this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
+ this.settings.hide_images = profile.hide_images;
+ this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
+ this.make_app();
});
-
- this.item_stock_map = {};
-
- this.make_app();
- }
-
- set_opening_entry_status() {
- this.page.set_title_sub(
- `<span class="indicator orange">
- <a class="text-muted" href="#Form/POS%20Opening%20Entry/${this.pos_opening}">
- Opened at ${moment(this.pos_opening_time).format("Do MMMM, h:mma")}
- </a>
- </span>`);
}
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(
- `<div class="app grid grid-cols-10 pt-8 gap-6"></div>`
+ `<div class="point-of-sale-app"></div>`
);
- this.$components_wrapper = this.wrapper.find('.app');
+ this.$components_wrapper = this.wrapper.find('.point-of-sale-app');
}
prepare_components() {
@@ -190,7 +160,7 @@
}
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);
}
@@ -199,7 +169,7 @@
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");
@@ -208,7 +178,7 @@
this.frm.save(undefined, undefined, undefined, () => {
frappe.show_alert({
- message:__("There was an error saving the document."),
+ message:__("There was an error saving the document."),
indicator:'red'
});
frappe.utils.play_sound("error");
@@ -238,12 +208,11 @@
this.item_selector = new erpnext.PointOfSale.ItemSelector({
wrapper: this.$components_wrapper,
pos_profile: this.pos_profile,
+ settings: this.settings,
events: {
item_selected: args => this.on_cart_update(args),
- get_frm: () => this.frm || {},
-
- get_allowed_item_group: () => this.item_groups
+ get_frm: () => this.frm || {}
}
})
}
@@ -251,12 +220,13 @@
init_item_cart() {
this.cart = new erpnext.PointOfSale.ItemCart({
wrapper: this.$components_wrapper,
+ settings: this.settings,
events: {
get_frm: () => this.frm,
cart_item_clicked: (item_code, batch_no, uom) => {
const item_row = this.frm.doc.items.find(
- i => i.item_code === item_code
+ i => i.item_code === item_code
&& i.uom === uom
&& (!batch_no || (batch_no && i.batch_no === batch_no))
);
@@ -273,9 +243,7 @@
this.customer_details = details;
// will add/remove LP payment method
this.payment.render_loyalty_points_payment_mode();
- },
-
- get_allowed_customer_group: () => this.customer_groups
+ }
}
})
}
@@ -356,10 +324,10 @@
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');
}
},
@@ -388,7 +356,7 @@
this.order_summary.load_summary_of(doc);
});
},
- reset_summary: () => this.order_summary.show_summary_placeholder()
+ reset_summary: () => this.order_summary.toggle_summary_placeholder(true)
}
})
}
@@ -429,8 +397,6 @@
})
}
-
-
toggle_recent_order_list(show) {
this.toggle_components(!show);
this.recent_order_list.toggle_component(show);
@@ -447,10 +413,12 @@
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()
]);
}
@@ -507,16 +475,6 @@
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");
}
@@ -539,7 +497,7 @@
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);
@@ -577,7 +535,7 @@
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 {
@@ -588,7 +546,7 @@
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)
);
@@ -617,7 +575,7 @@
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;
}
@@ -644,8 +602,7 @@
})
} else if (available_qty < qty_needed) {
frappe.show_alert({
- message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.',
- [bold_item_code, bold_warehouse, bold_available_qty]),
+ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
indicator: 'orange'
});
frappe.utils.play_sound("error");
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 7799dac..0c8ee70 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -1,12 +1,14 @@
erpnext.PointOfSale.ItemCart = class {
- constructor({ wrapper, events }) {
+ constructor({ wrapper, events, settings }) {
this.wrapper = wrapper;
this.events = events;
this.customer_info = undefined;
-
+ this.hide_images = settings.hide_images;
+ this.allowed_customer_groups = settings.customer_groups;
+
this.init_component();
}
-
+
init_component() {
this.prepare_dom();
this.init_child_components();
@@ -16,10 +18,10 @@
prepare_dom() {
this.wrapper.append(
- `<section class="col-span-4 flex flex-col shadow rounded item-cart bg-white mx-h-70 h-100"></section>`
+ `<section class="customer-cart-container"></section>`
)
- this.$component = this.wrapper.find('.item-cart');
+ this.$component = this.wrapper.find('.customer-cart-container');
}
init_child_components() {
@@ -29,32 +31,33 @@
init_customer_selector() {
this.$component.append(
- `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
+ `<div class="customer-section"></div>`
)
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(
- `<div class="cart-container flex flex-col items-center rounded flex-1 relative">
- <div class="absolute flex flex-col p-8 pt-0 w-full h-full">
- <div class="flex text-grey cart-header pt-2 pb-2 p-4 mt-2 mb-2 w-full f-shrink-0">
- <div class="flex-1">Item</div>
- <div class="mr-4">Qty</div>
- <div class="rate-list-header mr-1 text-right">Amount</div>
+ `<div class="cart-container">
+ <div class="abs-cart-container">
+ <div class="cart-label">Item Cart</div>
+ <div class="cart-header">
+ <div class="name-header">Item</div>
+ <div class="qty-header">Qty</div>
+ <div class="rate-amount-header">Amount</div>
</div>
- <div class="cart-items-section flex flex-col flex-1 scroll-y rounded w-full"></div>
- <div class="cart-totals-section flex flex-col w-full mt-4 f-shrink-0"></div>
- <div class="numpad-section flex flex-col mt-4 d-none w-full p-8 pt-0 pb-0 f-shrink-0"></div>
- </div>
+ <div class="cart-items-section"></div>
+ <div class="cart-totals-section"></div>
+ <div class="numpad-section"></div>
+ </div>
</div>`
);
this.$cart_container = this.$component.find('.cart-container');
@@ -70,54 +73,48 @@
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(
- `<div class="no-item-wrapper flex items-center h-18">
- <div class="flex-1 text-center text-grey">No items in cart</div>
- </div>`
- )
- this.$cart_items_wrapper.addClass('mt-4 border-grey border-dashed');
+ `<div class="no-item-wrapper">No items in cart</div>`
+ );
+ }
+
+ get_discount_icon() {
+ return (
+ `<svg class="discount-icon" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>`
+ );
}
make_cart_totals_section() {
this.$totals_section = this.$component.find('.cart-totals-section');
this.$totals_section.append(
- `<div class="add-discount flex items-center pt-4 pb-4 pr-4 pl-4 text-grey pointer no-select d-none">
- + Add Discount
+ `<div class="add-discount-wrapper">
+ ${this.get_discount_icon()} Add Discount
</div>
- <div class="border border-grey rounded">
- <div class="net-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
- <div class="flex flex-col">
- <div class="text-md text-dark-grey text-bold">Net Total</div>
- </div>
- <div class="flex flex-col text-right">
- <div class="text-md text-dark-grey text-bold">0.00</div>
- </div>
- </div>
- <div class="taxes"></div>
- <div class="grand-total flex justify-between items-center h-16 pr-8 pl-8 border-b-grey">
- <div class="flex flex-col">
- <div class="text-md text-dark-grey text-bold">Grand Total</div>
- </div>
- <div class="flex flex-col text-right">
- <div class="text-md text-dark-grey text-bold">0.00</div>
- </div>
- </div>
- <div class="checkout-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer rounded-b text-md text-bold">
- Checkout
- </div>
- <div class="edit-cart-btn flex items-center justify-center h-16 pr-8 pl-8 text-center text-grey no-select pointer d-none text-md text-bold">
- Edit Cart
- </div>
- </div>`
+ <div class="net-total-container">
+ <div class="net-total-label">Net Total</div>
+ <div class="net-total-value">0.00</div>
+ </div>
+ <div class="taxes-container"></div>
+ <div class="grand-total-container">
+ <div>Grand Total</div>
+ <div>0.00</div>
+ </div>
+ <div class="checkout-btn">Checkout</div>
+ <div class="edit-cart-btn">Edit Cart</div>`
)
- 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');
@@ -137,39 +134,37 @@
[ '', '', '', '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(
- `<div class="flex mb-2 justify-between">
+ `<div class="numpad-totals">
<span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span>
</div>`
)
this.$numpad_section.append(
- `<div class="numpad-btn checkout-btn flex items-center justify-center h-16 pr-8 pl-8 bg-primary
- text-center text-white no-select pointer rounded text-md text-bold mt-4" data-button-value="checkout">
- Checkout
- </div>`
+ `<div class="numpad-btn checkout-btn" data-button-value="checkout">Checkout</div>`
)
}
-
+
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 (e) {
+ 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 (e) {
+ 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);
});
@@ -178,7 +173,7 @@
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
@@ -193,23 +188,19 @@
});
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);
-
- me.$add_discount_elem.removeClass("d-none");
});
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();
});
@@ -231,7 +222,7 @@
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('+');
@@ -242,7 +233,7 @@
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();
- }
+ }
})
}
}
@@ -251,7 +242,7 @@
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
@@ -259,14 +250,15 @@
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,
@@ -279,30 +271,28 @@
}
});
}
-
+
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(`<div class="customer-search-field flex flex-1 items-center"></div>`);
+ this.$customer_section.html(`
+ <div class="customer-field"></div>
+ `);
const me = this;
const query = { query: 'erpnext.controllers.queries.customer_query' };
- const allowed_customer_group = this.events.get_allowed_customer_group() || [];
+ const allowed_customer_group = this.allowed_customer_groups || [];
if (allowed_customer_group.length) {
query.filters = {
customer_group: ['in', allowed_customer_group]
@@ -332,12 +322,12 @@
}
},
},
- 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) => {
@@ -371,9 +361,9 @@
}
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(
- `<div class="add-discount-field flex flex-1 items-center"></div>`
+ `<div class="add-discount-field"></div>`
);
const me = this;
@@ -382,14 +372,19 @@
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;
}
},
@@ -403,38 +398,36 @@
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(
- `<div class="add-discount-field flex flex-1 items-center"></div>`
+ `<div class="add-discount-field"></div>`
);
} 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(
- `<svg class="mr-2" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"
- stroke-linecap="round" stroke-linejoin="round">
- <path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
- </svg>
- <div class="edit-discount p-1 pr-3 pl-3 text-dark-grey rounded w-fit bg-green-200 mb-2">
- ${String(discount).bold()}% off
- </div>
- `
+ `<div class="edit-discount-btn">
+ ${this.get_discount_icon()} Additional ${String(discount).bold()}% discount applied
+ </div>`
);
}
}
-
+
update_customer_section() {
const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
if (customer) {
- this.$customer_section.addClass('border pr-4 pl-4').html(
- `<div class="customer-details flex flex-col">
- <div class="customer-header flex items-center rounded h-18 pointer">
- ${get_customer_image()}
- <div class="customer-name flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
- <div class="text-md text-dark-grey text-bold">${customer}</div>
+ this.$customer_section.html(
+ `<div class="customer-details">
+ <div class="customer-display">
+ ${this.get_customer_image()}
+ <div class="customer-name-desc">
+ <div class="customer-name">${customer}</div>
${get_customer_description()}
</div>
- <div class="f-shrink-0 add-remove-customer flex items-center pointer" data-customer="${escape(customer)}">
+ <div class="reset-customer-btn" data-customer="${escape(customer)}">
<svg width="32" height="32" viewBox="0 0 14 14" fill="none">
<path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
</svg>
@@ -449,29 +442,27 @@
function get_customer_description() {
if (!email_id && !mobile_no) {
- return `<div class="text-grey-200 italic">Click to add email / phone</div>`
+ return `<div class="customer-desc">Click to add email / phone</div>`
} else if (email_id && !mobile_no) {
- return `<div class="text-grey">${email_id}</div>`
+ return `<div class="customer-desc">${email_id}</div>`
} else if (mobile_no && !email_id) {
- return `<div class="text-grey">${mobile_no}</div>`
+ return `<div class="customer-desc">${mobile_no}</div>`
} else {
- return `<div class="text-grey">${email_id} | ${mobile_no}</div>`
+ return `<div class="customer-desc">${email_id} - ${mobile_no}</div>`
}
}
- function get_customer_image() {
- if (image) {
- return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
- <img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
- </div>`
- } else {
- return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200 text-md">
- ${frappe.get_abbr(customer)}
- </div>`
- }
+ }
+
+ get_customer_image() {
+ const { customer, image } = this.customer_info || {};
+ if (image) {
+ return `<div class="customer-image"><img src="${image}" alt="${image}""></div>`
+ } else {
+ return `<div class="customer-image customer-abbr">${frappe.get_abbr(customer)}</div>`
}
}
-
+
update_totals_section(frm) {
if (!frm) frm = this.events.get_frm();
@@ -481,60 +472,47 @@
const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }})
this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes);
}
-
+
render_net_total(value) {
const currency = this.events.get_frm().doc.currency;
- this.$totals_section.find('.net-total').html(
- `<div class="flex flex-col">
- <div class="text-md text-dark-grey text-bold">Net Total</div>
- </div>
- <div class="flex flex-col text-right">
- <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
- </div>`
+ this.$totals_section.find('.net-total-container').html(
+ `<div>Net Total</div><div>${format_currency(value, currency)}</div>`
)
- this.$numpad_section.find('.numpad-net-total').html(`Net Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+ this.$numpad_section.find('.numpad-net-total').html(
+ `<div>Net Total: <span>${format_currency(value, currency)}</span></div>`
+ );
}
-
+
render_grand_total(value) {
const currency = this.events.get_frm().doc.currency;
- this.$totals_section.find('.grand-total').html(
- `<div class="flex flex-col">
- <div class="text-md text-dark-grey text-bold">Grand Total</div>
- </div>
- <div class="flex flex-col text-right">
- <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
- </div>`
+ this.$totals_section.find('.grand-total-container').html(
+ `<div>Grand Total</div><div>${format_currency(value, currency)}</div>`
)
- this.$numpad_section.find('.numpad-grand-total').html(`Grand Total: <span class="text-bold">${format_currency(value, currency)}</span>`)
+ this.$numpad_section.find('.numpad-grand-total').html(
+ `<div>Grand Total: <span>${format_currency(value, currency)}</span></div>`
+ )
}
render_taxes(value, taxes) {
if (taxes.length) {
const currency = this.events.get_frm().doc.currency;
- this.$totals_section.find('.taxes').html(
- `<div class="flex items-center justify-between h-16 pr-8 pl-8 border-b-grey">
- <div class="flex overflow-hidden whitespace-nowrap">
- <div class="text-md text-dark-grey text-bold w-fit">Tax Charges</div>
- <div class="flex ml-4 text-dark-grey">
- ${
- 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 `<span class="border-grey p-1 pl-2 pr-2 rounded ${margin_left}">${description}</span>`
- }).join('')
- }
- </div>
- </div>
- <div class="flex flex-col text-right f-shrink-0 ml-4">
- <div class="text-md text-dark-grey text-bold">${format_currency(value, currency)}</div>
- </div>
- </div>`
+ this.$totals_section.find('.taxes-container').css('display', 'flex').html(
+ `${
+ taxes.map((t, i) => {
+ const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ return `<div class="tax-row">
+ <div class="tax-label">
+ ${description}
+ </div>
+ <div class="tax-value">${format_currency(value, currency)}</div>
+ </div>`
+ }).join('')
+ }`
)
} else {
- this.$totals_section.find('.taxes').html('')
+ this.$totals_section.find('.taxes-container').css('display', 'none').html('');
}
}
@@ -543,64 +521,65 @@
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(
- `<div class="cart-item-wrapper flex items-center h-18 pr-4 pl-4 rounded border-grey pointer no-select"
+ `<div class="cart-item-wrapper"
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
data-batch-no="${escape(item_data.batch_no || '')}">
- </div>`
+ </div>
+ <div class="seperator"></div>`
)
$item_to_update = this.get_cart_item(item_data);
}
$item_to_update.html(
- `<div class="flex flex-col flex-1 f-shrink-1 overflow-hidden whitespace-nowrap">
- <div class="text-md text-dark-grey text-bold">
+ `${get_item_image_html()}
+ <div class="item-name-desc">
+ <div class="item-name">
${item_data.item_name}
</div>
${get_description_html()}
</div>
- ${get_rate_discount_html()}
- </div>`
+ ${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();
@@ -610,30 +589,26 @@
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 `
- <div class="flex f-shrink-0 ml-4 items-center">
- <div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
- <span>${item_data.qty || 0}</span>
- </div>
- <div class="rate-col flex flex-col f-shrink-0 text-right">
- <div class="text-md text-dark-grey text-bold">${format_currency(item_data.amount, currency)}</div>
- <div class="text-md-0 text-dark-grey">${format_currency(item_data.rate, currency)}</div>
+ <div class="item-qty-rate">
+ <div class="item-qty"><span>${item_data.qty || 0}</span></div>
+ <div class="item-rate-amount">
+ <div class="item-rate">${format_currency(item_data.amount, currency)}</div>
+ <div class="item-amount">${format_currency(item_data.rate, currency)}</div>
</div>
</div>`
} else {
return `
- <div class="flex f-shrink-0 ml-4 text-right">
- <div class="flex w-8 h-8 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
- <span>${item_data.qty || 0}</span>
- </div>
- <div class="rate-col flex flex-col f-shrink-0 text-right">
- <div class="text-md text-dark-grey text-bold">${format_currency(item_data.rate, currency)}</div>
+ <div class="item-qty-rate">
+ <div class="item-qty"><span>${item_data.qty || 0}</span></div>
+ <div class="item-rate-amount">
+ <div class="item-rate">${format_currency(item_data.rate, currency)}</div>
</div>
</div>`
}
@@ -649,10 +624,19 @@
}
}
item_data.description = frappe.ellipsis(item_data.description, 45);
- return `<div class="text-grey">${item_data.description}</div>`
+ return `<div class="item-desc">${item_data.description}</div>`
}
return ``;
}
+
+ function get_item_image_html() {
+ const { image, item_name } = item_data;
+ if (image) {
+ return `<div class="item-image"><img src="${image}" alt="${image}""></div>`
+ } else {
+ return `<div class="item-image item-abbr">${frappe.get_abbr(item_name)}</div>`
+ }
+ }
}
scroll_to_item($item) {
@@ -660,7 +644,7 @@
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}`, value);
@@ -668,33 +652,37 @@
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);
@@ -713,7 +701,7 @@
this.prev_action = undefined;
}
this.numpad_value = '';
-
+
} else if (current_action === 'checkout') {
this.prev_action = undefined;
this.toggle_item_highlight();
@@ -739,7 +727,7 @@
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%'),
@@ -751,38 +739,38 @@
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();
}
@@ -790,7 +778,7 @@
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) {
@@ -801,48 +789,56 @@
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(
- `<div class="text-md text-dark-grey text-bold">${this.customer_info.customer}</div>
- <div class="last-transacted-on text-grey-200"></div>`
- )
-
- this.$customer_section.find('.customer-details').append(
- `<div class="customer-form">
- <div class="text-grey mt-4 mb-6">CONTACT DETAILS</div>
- <div class="grid grid-cols-2 gap-4">
- <div class="email_id-field"></div>
- <div class="mobile_no-field"></div>
- <div class="loyalty_program-field"></div>
- <div class="loyalty_points-field"></div>
+ this.$cart_container.css('display', 'none');
+ this.$customer_section.css({
+ 'height': '100%',
+ 'padding-top': '0px'
+ });
+ this.$customer_section.find('.customer-details').html(
+ `<div class="header">
+ <div class="label">Contact Details</div>
+ <div class="close-details-btn">
+ <svg width="32" height="32" viewBox="0 0 14 14" fill="none">
+ <path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
+ </svg>
</div>
- <div class="text-grey mt-4 mb-6">RECENT TRANSACTIONS</div>
- </div>`
- )
+ </div>
+ <div class="customer-display">
+ ${this.get_customer_image()}
+ <div class="customer-name-desc">
+ <div class="customer-name">${customer}</div>
+ <div class="customer-desc"></div>
+ </div>
+ </div>
+ <div class="customer-fields-container">
+ <div class="email_id-field"></div>
+ <div class="mobile_no-field"></div>
+ <div class="loyalty_program-field"></div>
+ <div class="loyalty_points-field"></div>
+ </div>
+ <div class="transactions-label">Recent Transactions</div>`
+ );
// transactions need to be in diff div from sticky elem for scrolling
- this.$customer_section.append(`<div class="customer-transactions flex-1 rounded"></div>`)
+ this.$customer_section.append(`<div class="customer-transactions"></div>`)
- 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',
@@ -864,7 +860,7 @@
},{
fieldname: 'loyalty_points',
label: __('Loyalty Points'),
- fieldtype: 'Int',
+ fieldtype: 'Data',
read_only: 1
}];
@@ -908,7 +904,7 @@
}
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
@@ -916,41 +912,45 @@
const transaction_container = this.$customer_section.find('.customer-transactions');
if (!res.length) {
- transaction_container.removeClass('flex-1 border rounded').html(
- `<div class="text-grey text-center">No recent transactions found</div>`
+ transaction_container.html(
+ `<div class="no-transactions-placeholder">No recent transactions found</div>`
)
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(
- `<div class="invoice-wrapper flex p-3 justify-between border-grey rounded pointer no-select" data-invoice-name="${escape(invoice.name)}">
- <div class="flex flex-col justify-end">
- <div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
- <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
- ${posting_datetime}
- </div>
+ `<div class="invoice-wrapper" data-invoice-name="${escape(invoice.name)}">
+ <div class="invoice-name-date">
+ <div class="invoice-name">${invoice.name}</div>
+ <div class="invoice-date">${posting_datetime}</div>
</div>
- <div class="flex flex-col text-right">
- <div class="f-shrink-0 text-md text-dark-grey text-bold ml-4">
+ <div class="invoice-total-status">
+ <div class="invoice-total">
${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
</div>
- <div class="f-shrink-0 text-grey ml-4 text-bold indicator ${indicator_color}">${invoice.status}</div>
+ <div class="invoice-status">
+ <span class="indicator-pill whitespace-nowrap ${indicator_color[invoice.status]}">
+ <span>${invoice.status}</span>
+ </span>
+ </div>
</div>
- </div>`
+ </div>
+ <div class="seperator"></div>`
)
});
- })
+ });
}
load_invoice() {
@@ -959,7 +959,7 @@
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 => {
@@ -973,20 +973,18 @@
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 a4de9f1..5461543 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -16,35 +16,36 @@
prepare_dom() {
this.wrapper.append(
- `<section class="col-span-4 flex shadow rounded item-details bg-white mx-h-70 h-100 d-none"></section>`
+ `<section class="item-details-container"></section>`
)
- this.$component = this.wrapper.find('.item-details');
+ this.$component = this.wrapper.find('.item-details-container');
}
init_child_components() {
this.$component.html(
- `<div class="details-container flex flex-col p-8 rounded w-full">
- <div class="flex justify-between mb-2">
- <div class="text-grey">ITEM DETAILS</div>
- <div class="close-btn text-grey hover-underline pointer no-select">Close</div>
+ `<div class="item-details-header">
+ <div class="label">Item Details</div>
+ <div class="close-btn">
+ <svg width="32" height="32" viewBox="0 0 14 14" fill="none">
+ <path d="M4.93764 4.93759L7.00003 6.99998M9.06243 9.06238L7.00003 6.99998M7.00003 6.99998L4.93764 9.06238L9.06243 4.93759" stroke="#8D99A6"/>
+ </svg>
</div>
- <div class="item-defaults flex">
- <div class="flex-1 flex flex-col justify-end mr-4 mb-2">
- <div class="item-name text-xl font-weight-450"></div>
- <div class="item-description text-md-0 text-grey-200"></div>
- <div class="item-price text-xl font-bold"></div>
- </div>
- <div class="item-image flex items-center justify-center w-46 h-46 bg-light-grey rounded ml-4 text-6xl text-grey-100"></div>
+ </div>
+ <div class="item-display">
+ <div class="item-name-desc-price">
+ <div class="item-name"></div>
+ <div class="item-desc"></div>
+ <div class="item-price"></div>
</div>
- <div class="discount-section flex items-center"></div>
- <div class="text-grey mt-4 mb-6">STOCK DETAILS</div>
- <div class="form-container grid grid-cols-2 row-gap-2 col-gap-4 grid-auto-row"></div>
- </div>`
+ <div class="item-image"></div>
+ </div>
+ <div class="discount-section"></div>
+ <div class="form-container"></div>`
)
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');
@@ -52,7 +53,7 @@
}
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;
@@ -104,11 +105,11 @@
}
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 ``;
@@ -118,11 +119,9 @@
this.$item_description.html(get_description_html());
this.$item_price.html(format_currency(price_list_rate, this.currency));
if (image) {
- this.$item_image.html(
- `<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">`
- );
+ this.$item_image.html(`<img src="${image}" alt="${image}">`);
} else {
- this.$item_image.html(frappe.get_abbr(item_code));
+ this.$item_image.html(`<div class="item-abbr">${frappe.get_abbr(item_name)}</div>`);
}
}
@@ -130,12 +129,8 @@
render_discount_dom(item) {
if (item.discount_percentage) {
this.$dicount_section.html(
- `<div class="text-grey line-through mr-4 text-md mb-2">
- ${format_currency(item.price_list_rate, this.currency)}
- </div>
- <div class="p-1 pr-3 pl-3 rounded w-fit text-bold bg-green-200 mb-2">
- ${item.discount_percentage}% off
- </div>`
+ `<div class="item-rate">${format_currency(item.price_list_rate, this.currency)}</div>
+ <div class="item-discount">${item.discount_percentage}% off</div>`
)
this.$item_price.html(format_currency(item.rate, this.currency));
} else {
@@ -149,9 +144,7 @@
fields_to_display.forEach((fieldname, idx) => {
this.$form_container.append(
- `<div class="">
- <div class="item_detail_field ${fieldname}-control" data-fieldname="${fieldname}"></div>
- </div>`
+ `<div class="${fieldname}-control" data-fieldname="${fieldname}"></div>`
)
const field_meta = this.item_meta.fields.find(df => df.fieldname === fieldname);
@@ -185,22 +178,15 @@
make_auto_serial_selection_btn(item) {
if (item.has_serial_no) {
- this.$form_container.append(
- `<div class="grid-filler no-select"></div>`
- )
if (!item.has_batch_no) {
this.$form_container.append(
`<div class="grid-filler no-select"></div>`
)
}
this.$form_container.append(
- `<div class="auto-fetch-btn bg-grey-100 border border-grey text-bold rounded pt-3 pb-3 pl-6 pr-8 text-grey pointer no-select mt-2"
- style="height: 3.3rem">
- Auto Fetch Serial Numbers
- </div>`
+ `<div class="btn btn-sm btn-secondary auto-fetch-btn">Auto Fetch Serial Numbers</div>`
)
- this.$form_container.find('.serial_no-control').find('textarea').css('height', '9rem');
- this.$form_container.find('.serial_no-control').parent().addClass('row-span-2');
+ this.$form_container.find('.serial_no-control').find('textarea').css('height', '6rem');
}
}
@@ -294,8 +280,13 @@
}
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
- const field_control = me[`${fieldname}_control`];
- if (field_control) {
+ 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;
+
+ if (field_control && item_code_is_same && batch_is_same && uom_is_same) {
field_control.set_value(value);
cur_pos.update_cart_html(item_row);
}
@@ -409,6 +400,6 @@
}
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 49d4281..740fd01 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -1,12 +1,16 @@
+import onScan from 'onscan.js';
+
erpnext.PointOfSale.ItemSelector = class {
- constructor({ frm, wrapper, events, pos_profile }) {
+ 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();
@@ -17,22 +21,18 @@
prepare_dom() {
this.wrapper.append(
- `<section class="col-span-6 flex shadow rounded items-selector bg-white mx-h-70 h-100">
- <div class="flex flex-col rounded w-full scroll-y">
- <div class="filter-section flex p-8 pb-2 bg-white sticky z-100">
- <div class="search-field flex f-grow-3 mr-8 items-center text-grey"></div>
- <div class="item-group-field flex f-grow-1 items-center text-grey text-bold"></div>
- </div>
- <div class="flex flex-1 flex-col p-8 pt-2">
- <div class="text-grey mb-6">ALL ITEMS</div>
- <div class="items-container grid grid-cols-4 gap-8">
- </div>
- </div>
+ `<section class="items-selector">
+ <div class="filter-section">
+ <div class="label">All Items</div>
+ <div class="search-field"></div>
+ <div class="item-group-field"></div>
</div>
+ <div class="items-container"></div>
</section>`
);
-
+
this.$component = this.wrapper.find('.items-selector');
+ this.$items_container = this.$component.find('.items-container');
}
async load_items_data() {
@@ -51,11 +51,12 @@
}
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,
@@ -65,7 +66,6 @@
render_item_list(items) {
- this.$items_container = this.$component.find('.items-container');
this.$items_container.html('');
items.forEach(item => {
@@ -75,32 +75,34 @@
}
get_item_html(item) {
+ const me = this;
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";
function get_item_image_html() {
- if (item_image) {
+ if (!me.hide_images && item_image) {
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>`
} else {
- return `<div class="flex items-center justify-center h-32 bg-light-grey text-6xl text-grey-100">
- ${frappe.get_abbr(item.item_name)}
- </div>`
+ return `<div class="item-display abbr">${frappe.get_abbr(item.item_name)}</div>`;
}
}
return (
- `<div class="item-wrapper rounded shadow pointer no-select" data-item-code="${escape(item.item_code)}"
- data-serial-no="${escape(serial_no)}" data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
+ `<div class="item-wrapper"
+ data-item-code="${escape(item.item_code)}" data-serial-no="${escape(serial_no)}"
+ data-batch-no="${escape(batch_no)}" data-uom="${escape(stock_uom)}"
title="Avaiable Qty: ${actual_qty}">
+
${get_item_image_html()}
- <div class="flex items-center pr-4 pl-4 h-10 justify-between">
- <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
+
+ <div class="item-detail">
+ <div class="item-name">
<span class="indicator ${indicator_color}"></span>
${frappe.ellipsis(item.item_name, 18)}
</div>
- <div class="f-shrink-0 text-dark-grey text-bold ml-4">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
+ <div class="item-rate">${format_currency(item.price_list_rate, item.currency, 0) || 0}</div>
</div>
</div>`
)
@@ -108,6 +110,7 @@
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('');
@@ -115,7 +118,7 @@
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,
@@ -135,7 +138,7 @@
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 : ''
}
}
},
@@ -149,6 +152,7 @@
bind_events() {
const me = this;
+ window.onScan = onScan;
onScan.attachTo(document, {
onScan: (sScancode) => {
if (this.search_field && this.$component.is(':visible')) {
@@ -165,7 +169,7 @@
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;
@@ -203,6 +207,7 @@
ignore_inputs: true,
page: cur_page.page.page
});
+
// for selecting the last filtered item on search
frappe.ui.keys.on("enter", () => {
const selector_is_visible = this.$component.is(':visible');
@@ -224,7 +229,7 @@
}
});
}
-
+
filter_items({ search_term='' }={}) {
if (search_term) {
search_term = search_term.toLowerCase();
@@ -235,6 +240,7 @@
const items = this.search_index[search_term];
this.items = items;
this.render_item_list(items);
+ this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
return;
}
}
@@ -247,28 +253,33 @@
}
this.items = items;
this.render_item_list(items);
+ this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
});
}
-
+
+ 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
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 4b8e841..edde7d8 100644
--- a/erpnext/selling/page/point_of_sale/pos_number_pad.js
+++ b/erpnext/selling/page/point_of_sale/pos_number_pad.js
@@ -25,14 +25,13 @@
const fieldname = fieldnames && fieldnames[number] ?
fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number;
- return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
- flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
+ return a2 + `<div class="numpad-btn ${class_to_append}" data-button-value="${fieldname}">${number}</div>`
}, '')
}, '');
}
this.wrapper.html(
- `<div class="grid grid-cols-${cols} gap-4">
+ `<div class="numpad-container">
${get_keys()}
</div>`
)
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 b256247..ec39231 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 @@
prepare_dom() {
this.wrapper.append(
- `<section class="col-span-4 flex flex-col shadow rounded past-order-list bg-white mx-h-70 h-100 d-none">
- <div class="flex flex-col rounded w-full scroll-y">
- <div class="filter-section flex flex-col p-8 pb-2 bg-white sticky z-100">
- <div class="search-field flex items-center text-grey"></div>
- <div class="status-field flex items-center text-grey text-bold"></div>
- </div>
- <div class="flex flex-1 flex-col p-8 pt-2">
- <div class="text-grey mb-6">RECENT ORDERS</div>
- <div class="invoices-container rounded border grid grid-cols-1"></div>
- </div>
+ `<section class="past-order-list">
+ <div class="filter-section">
+ <div class="label">Recent Orders</div>
+ <div class="search-field"></div>
+ <div class="status-field"></div>
</div>
+ <div class="invoices-container"></div>
</section>`
);
@@ -66,7 +62,7 @@
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 @@
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 @@
get_invoice_html(invoice) {
const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma");
return (
- `<div class="invoice-wrapper flex p-4 justify-between border-b-grey pointer no-select" data-invoice-name="${escape(invoice.name)}">
- <div class="flex flex-col justify-end">
- <div class="text-dark-grey text-bold overflow-hidden whitespace-nowrap mb-2">${invoice.name}</div>
- <div class="flex items-center">
- <div class="flex items-center f-shrink-1 text-dark-grey overflow-hidden whitespace-nowrap">
- <svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
- <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
- </svg>
- ${invoice.customer}
- </div>
+ `<div class="invoice-wrapper" data-invoice-name="${escape(invoice.name)}">
+ <div class="invoice-name-date">
+ <div class="invoice-name">${invoice.name}</div>
+ <div class="invoice-date">
+ <svg class="mr-2" width="12" height="12" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
+ </svg>
+ ${invoice.customer}
</div>
</div>
- <div class="flex flex-col text-right">
- <div class="f-shrink-0 text-lg text-dark-grey text-bold ml-4">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
- <div class="f-shrink-0 text-grey ml-4">${posting_datetime}</div>
+ <div class="invoice-total-status">
+ <div class="invoice-total">${format_currency(invoice.grand_total, invoice.currency, 0) || 0}</div>
+ <div class="invoice-date">${posting_datetime}</div>
</div>
- </div>`
+ </div>
+ <div class="seperator"></div>`
);
}
+
+ 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 6fd4c26..eb29976 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 @@
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(
- `<section class="col-span-6 flex flex-col items-center shadow rounded past-order-summary bg-white mx-h-70 h-100 d-none">
- <div class="no-summary-placeholder flex flex-1 items-center justify-center p-16">
- <div class="no-item-wrapper flex items-center h-18 pr-4 pl-4">
- <div class="flex-1 text-center text-grey">Select an invoice to load summary data</div>
- </div>
+ `<section class="past-order-summary">
+ <div class="no-summary-placeholder">
+ Select an invoice to load summary data
</div>
- <div class="summary-wrapper d-none flex-1 w-66 text-dark-grey relative">
- <div class="summary-container absolute flex flex-col pt-16 pb-16 pr-8 pl-8 w-full h-full"></div>
+ <div class="invoice-summary-wrapper">
+ <div class="abs-container">
+ <div class="upper-section"></div>
+ <div class="label">Items</div>
+ <div class="items-container summary-container"></div>
+ <div class="label">Totals</div>
+ <div class="totals-container summary-container"></div>
+ <div class="label">Payments</div>
+ <div class="payments-container summary-container"></div>
+ <div class="summary-btns"></div>
+ </div>
</div>
</section>`
);
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(
- `<div class="flex upper-section justify-between w-full h-24"></div>`
- );
-
+ 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(
- `<div class="flex flex-col flex-1 mt-6 w-full scroll-y">
- <div class="text-grey mb-4 sticky bg-white">ITEMS</div>
- <div class="items-summary-container border rounded flex flex-col w-full"></div>
- </div>`
- );
-
- this.$items_summary_container = this.$summary_container.find('.items-summary-container');
- }
-
- init_totals_summary() {
- this.$summary_container.append(
- `<div class="flex flex-col mt-6 w-full f-shrink-0">
- <div class="text-grey mb-4">TOTALS</div>
- <div class="summary-totals-container border rounded flex flex-col w-full"></div>
- </div>`
- );
-
- this.$totals_summary_container = this.$summary_container.find('.summary-totals-container');
- }
-
- init_payments_summary() {
- this.$summary_container.append(
- `<div class="flex flex-col mt-6 w-full f-shrink-0">
- <div class="text-grey mb-4">PAYMENTS</div>
- <div class="payments-summary-container border rounded flex flex-col w-full mb-4"></div>
- </div>`
- );
-
- this.$payment_summary_container = this.$summary_container.find('.payments-summary-container');
- }
-
- init_summary_buttons() {
- this.$summary_container.append(
- `<div class="summary-btns flex summary-btns justify-between w-full f-shrink-0"></div>`
- );
-
+ 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');
}
@@ -121,132 +75,88 @@
}
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 `<div class="flex flex-col items-start justify-end pr-4">
- <div class="text-lg text-bold pt-2">${doc.customer}</div>
- <div class="text-grey">${this.customer_email}</div>
- <div class="text-grey mt-auto">Sold by: ${doc.owner}</div>
+ return `<div class="left-section">
+ <div class="customer-name">${doc.customer}</div>
+ <div class="customer-email">${this.customer_email}</div>
+ <div class="cashier">Sold by: ${doc.owner}</div>
</div>
- <div class="flex flex-col flex-1 items-end justify-between">
- <div class="text-2-5xl text-bold">${format_currency(doc.paid_amount, doc.currency)}</div>
- <div class="flex justify-between">
- <div class="text-grey mr-4">${doc.name}</div>
- <div class="text-grey text-bold indicator ${indicator_color}">${doc.status}</div>
- </div>
+ <div class="right-section">
+ <div class="paid-amount">${format_currency(doc.paid_amount, doc.currency)}</div>
+ <div class="invoice-name">${doc.name}</div>
+ <span class="indicator-pill whitespace-nowrap ${indicator_color}"><span>${doc.status}</span></span>
</div>`;
}
+ get_item_html(doc, item_data) {
+ return `<div class="item-row-wrapper">
+ <div class="item-name">${item_data.item_name}</div>
+ <div class="item-qty">${item_data.qty || 0}</div>
+ <div class="item-rate-disc">${get_rate_discount_html()}</div>
+ </div>`;
+
+ function get_rate_discount_html() {
+ if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
+ return `<span class="item-disc">(${item_data.discount_percentage}% off)</span>
+ <div class="item-rate">${format_currency(item_data.rate, doc.currency)}</div>`;
+ } else {
+ return `<div class="item-rate">${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}</div>`;
+ }
+ }
+ }
+
get_discount_html(doc) {
if (doc.discount_amount) {
- return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
- <div class="flex f-shrink-1 items-center">
- <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap mr-2">
- Discount
- </div>
- <span class="text-grey">(${doc.additional_discount_percentage} %)</span>
- </div>
- <div class="flex flex-col f-shrink-0 ml-auto text-right">
- <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.discount_amount, doc.currency)}</div>
- </div>
- </div>`;
+ return `<div class="summary-row-wrapper">
+ <div>Discount (${doc.additional_discount_percentage} %)</div>
+ <div>${format_currency(doc.discount_amount, doc.currency)}</div>
+ </div>`;
} else {
return ``;
}
}
get_net_total_html(doc) {
- return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
- <div class="flex f-shrink-1 items-center">
- <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
- Net Total
- </div>
- </div>
- <div class="flex flex-col f-shrink-0 ml-auto text-right">
- <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.net_total, doc.currency)}</div>
- </div>
+ return `<div class="summary-row-wrapper">
+ <div>Net Total</div>
+ <div>${format_currency(doc.net_total, doc.currency)}</div>
</div>`;
}
get_taxes_html(doc) {
- const taxes = doc.taxes.map((t, i) => {
- let margin_left = '';
- if (i !== 0) margin_left = 'ml-2';
- return `<span class="pl-2 pr-2 ${margin_left}">${t.description} @${t.rate}%</span>`;
- }).join('');
+ if (!doc.taxes.length) return '';
return `
- <div class="total-summary-wrapper flex items-center justify-between h-12 pr-4 pl-4 border-b-grey">
- <div class="flex">
- <div class="text-md-0 text-dark-grey text-bold w-fit">Tax Charges</div>
- <div class="flex ml-6 text-dark-grey">${taxes}</div>
- </div>
- <div class="flex flex-col text-right">
- <div class="text-md-0 text-dark-grey text-bold">
- ${format_currency(doc.base_total_taxes_and_charges, doc.currency)}
- </div>
- </div>
+ <div class="taxes-wrapper">
+ ${
+ doc.taxes.map((t, i) => {
+ const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ return `<div class="tax-row">
+ <div class="tax-label">${description}</div>
+ <div class="tax-value">${format_currency(t.tax_amount_after_discount_amount, doc.currency)}</div>
+ </div>`
+ }).join('')
+ }
</div>`;
}
get_grand_total_html(doc) {
- return `<div class="total-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
- <div class="flex f-shrink-1 items-center">
- <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
- Grand Total
- </div>
- </div>
- <div class="flex flex-col f-shrink-0 ml-auto text-right">
- <div class="text-md-0 text-dark-grey text-bold">${format_currency(doc.grand_total, doc.currency)}</div>
- </div>
+ return `<div class="summary-row-wrapper grand-total">
+ <div>Grand Total</div>
+ <div>${format_currency(doc.grand_total, doc.currency)}</div>
</div>`;
}
- get_item_html(doc, item_data) {
- return `<div class="item-summary-wrapper flex items-center h-12 pr-4 pl-4 border-b-grey pointer no-select">
- <div class="flex w-6 h-6 rounded bg-light-grey mr-4 items-center justify-center font-bold f-shrink-0">
- <span>${item_data.qty || 0}</span>
- </div>
- <div class="flex flex-col f-shrink-1">
- <div class="text-md text-dark-grey text-bold overflow-hidden whitespace-nowrap">
- ${item_data.item_name}
- </div>
- </div>
- <div class="flex f-shrink-0 ml-auto text-right">
- ${get_rate_discount_html()}
- </div>
- </div>`;
-
- function get_rate_discount_html() {
- if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
- return `<span class="text-grey mr-2">
- (${item_data.discount_percentage}% off)
- </span>
- <div class="text-md-0 text-dark-grey text-bold">
- ${format_currency(item_data.rate, doc.currency)}
- </div>`;
- } else {
- return `<div class="text-md-0 text-dark-grey text-bold">
- ${format_currency(item_data.price_list_rate || item_data.rate, doc.currency)}
- </div>`;
- }
- }
- }
-
get_payment_html(doc, payment) {
- return `<div class="payment-summary-wrapper flex items-center h-12 pr-4 pl-4 pointer border-b-grey no-select">
- <div class="flex f-shrink-1 items-center">
- <div class="text-md-0 text-dark-grey text-bold overflow-hidden whitespace-nowrap">
- ${payment.mode_of_payment}
- </div>
- </div>
- <div class="flex flex-col f-shrink-0 ml-auto text-right">
- <div class="text-md-0 text-dark-grey text-bold">${format_currency(payment.amount, doc.currency)}</div>
- </div>
+ return `<div class="summary-row-wrapper payments">
+ <div>${payment.mode_of_payment}</div>
+ <div>${format_currency(payment.amount, doc.currency)}</div>
</div>`;
}
@@ -254,22 +164,22 @@
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', '.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', () => {
@@ -312,10 +222,6 @@
});
}
- 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;
@@ -338,8 +244,10 @@
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"]) ]) );
+ 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.'),
@@ -361,9 +269,7 @@
m.visible_btns.forEach(b => {
const class_name = b.split(' ')[0].toLowerCase();
this.$summary_btns.append(
- `<div class="${class_name}-btn border rounded h-14 flex flex-1 items-center mr-4 justify-center text-md text-bold no-select pointer">
- ${b}
- </div>`
+ `<div class="summary-btn btn btn-default ${class_name}-btn">${b}</div>`
);
});
}
@@ -371,29 +277,14 @@
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) {
@@ -408,14 +299,15 @@
}
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);
@@ -428,7 +320,7 @@
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);
@@ -437,19 +329,35 @@
}
attach_items_info(doc) {
- this.$items_summary_container.html('');
- doc.items.forEach(item => {
+ this.$items_container.html('');
+ doc.items.forEach((item, i) => {
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) {
@@ -457,20 +365,24 @@
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 e4d8965..365c27b 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -1,5 +1,3 @@
-{% include "erpnext/selling/page/point_of_sale/pos_number_pad.js" %}
-
erpnext.PointOfSale.Payment = class {
constructor({ events, wrapper }) {
this.wrapper = wrapper;
@@ -18,52 +16,37 @@
prepare_dom() {
this.wrapper.append(
- `<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
- <div class="flex flex-col p-16 pt-8 pb-8 w-full">
- <div class="text-grey mb-6 payment-section no-select pointer">
- PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
+ `<section class="payment-container">
+ <div class="section-label payment-section">Payment Method</div>
+ <div class="payment-modes"></div>
+ <div class="fields-numpad-container">
+ <div class="fields-section">
+ <div class="section-label">Additional Information</div>
+ <div class="invoice-fields"></div>
</div>
- <div class="payment-modes flex flex-wrap"></div>
- <div class="invoice-details-section"></div>
- <div class="flex mt-auto justify-center w-full">
- <div class="flex flex-col justify-center flex-1 ml-4">
- <div class="flex w-full">
- <div class="totals-remarks items-end justify-end flex flex-1">
- <div class="remarks text-md-0 text-grey mr-auto"></div>
- <div class="totals flex justify-end pt-4"></div>
- </div>
- <div class="number-pad w-40 mb-4 ml-8 d-none"></div>
- </div>
- <div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
- Complete Order
- </div>
- <div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
- </div>
- </div>
+ <div class="number-pad"></div>
</div>
+ <div class="totals-section">
+ <div class="totals"></div>
+ </div>
+ <div class="submit-order-btn">Complete Order</div>
</section>`
)
- 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() {
frappe.db.get_doc("POS Settings", undefined).then((doc) => {
const fields = doc.invoice_fields;
if (!fields.length) return;
-
- this.$invoice_details_section.html(
- `<div class="text-grey pb-6 mt-2 pointer no-select">
- ADDITIONAL INFORMATION<span class="octicon octicon-chevron-down collapse-indicator"></span>
- </div>
- <div class="invoice-fields grid grid-cols-2 gap-4 mb-6 d-none"></div>`
- );
- 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 => {
@@ -127,9 +110,9 @@
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 +125,16 @@
// 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 +143,20 @@
// 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`];
const doc = me.events.get_frm().doc;
- me.selected_mode?.$input?.get(0).focus();
- const current_value = me.selected_mode?.get_value()
- !current_value && doc.grand_total > doc.paid_amount ? me.selected_mode?.set_value(doc.grand_total - doc.paid_amount) : '';
+ me.selected_mode = me[`${mode}_control`];
+ me.selected_mode && me.selected_mode.$input.get(0).focus();
+ const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
+ !current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
+ me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
}
})
@@ -198,7 +183,7 @@
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;
@@ -217,9 +202,9 @@
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) => {
@@ -235,29 +220,16 @@
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;
-
- me.$payment_modes.addClass('d-none');
- me.$invoice_fields.toggleClass("d-none");
- me.toggle_numpad(false);
- });
- this.$component.on('click', '.payment-section', () => {
- this.$invoice_fields.addClass("d-none");
- this.$payment_modes.toggleClass('d-none');
- this.toggle_numpad(true);
- })
}
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();
}
});
@@ -287,15 +259,13 @@
}
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');
- }
+ // if (show) {
+ // this.$numpad.css('display', 'flex');
+ // this.$totals_section.addClass('w-60 justify-center').removeClass('justify-end w-full');
+ // } else {
+ // this.$numpad.css('display', 'none');
+ // this.$totals_section.removeClass('w-60 justify-center').addClass('justify-end w-full');
+ // }
}
render_payment_section() {
@@ -327,7 +297,7 @@
fieldtype: 'Data',
onchange: function() {}
},
- parent: this.$totals_remarks.find(`.remarks`),
+ parent: this.$totals_section.find(`.remarks`),
render_input: true,
});
this[`remark_control`].set_value('');
@@ -348,12 +318,11 @@
const amount = p.amount > 0 ? format_currency(p.amount, currency) : '';
return (
- `<div class="w-half ${margin} bg-white">
- <div class="mode-of-payment rounded border border-grey text-grey text-md
- mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="${mode}" data-payment-type="${payment_type}">
+ `<div class="payment-mode-wrapper">
+ <div class="mode-of-payment" data-mode="${mode}" data-payment-type="${payment_type}">
${p.mode_of_payment}
- <div class="${mode}-amount pay-amount inline float-right text-bold">${amount}</div>
- <div class="${mode} mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+ <div class="${mode}-amount pay-amount">${amount}</div>
+ <div class="${mode} mode-of-payment-control"></div>
</div>
</div>`
)
@@ -405,12 +374,10 @@
this.$payment_modes.find('.cash-shortcuts').remove();
this.$payment_modes.find('[data-payment-type="Cash"]').find('.mode-of-payment-control').after(
- `<div class="cash-shortcuts grid grid-cols-3 gap-2 flex-1 text-center text-md-0 mb-2 d-none">
+ `<div class="cash-shortcuts">
${
shortcuts.map(s => {
- return `<div class="shortcut rounded bg-light-grey text-dark-grey pt-2 pb-2 no-select pointer" data-value="${s}">
- ${format_currency(s, currency, 0)}
- </div>`
+ return `<div class="shortcut" data-value="${s}">${format_currency(s, currency, 0)}</div>`
}).join('')
}
</div>`
@@ -457,13 +424,12 @@
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(
- `<div class="w-half ${margin} bg-white">
- <div class="mode-of-payment rounded border border-grey text-grey text-md
- mb-4 p-8 pt-4 pb-4 no-select pointer" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
+ `<div class="payment-mode-wrapper">
+ <div class="mode-of-payment" data-mode="loyalty-amount" data-payment-type="loyalty-amount">
Redeem Loyalty Points
- <div class="loyalty-amount-amount pay-amount inline float-right text-bold">${amount}</div>
- <div class="loyalty-amount-name inline float-right text-bold text-md-0 d-none">${loyalty_program}</div>
- <div class="loyalty-amount mode-of-payment-control mt-4 flex flex-1 items-center d-none"></div>
+ <div class="loyalty-amount-amount pay-amount">${amount}</div>
+ <div class="loyalty-amount-name">${loyalty_program}</div>
+ <div class="loyalty-amount mode-of-payment-control"></div>
</div>
</div>`
)
@@ -520,18 +486,24 @@
const label = change ? __('Change') : __('To Be Paid');
this.$totals.html(
- `<div>
- <div class="pr-8 border-r-grey">Paid Amount</div>
- <div class="pr-8 border-r-grey text-bold text-2xl">${format_currency(paid_amount, currency)}</div>
+ `<div class="col">
+ <div class="total-label">Grand Total</div>
+ <div class="value">${format_currency(doc.grand_total, currency)}</div>
</div>
- <div>
- <div class="pl-8">${label}</div>
- <div class="pl-8 text-green-400 text-bold text-2xl">${format_currency(change || remaining, currency)}</div>
+ <div class="seperator-y"></div>
+ <div class="col">
+ <div class="total-label">Paid Amount</div>
+ <div class="value">${format_currency(paid_amount, currency)}</div>
+ </div>
+ <div class="seperator-y"></div>
+ <div class="col">
+ <div class="total-label">${label}</div>
+ <div class="value">${format_currency(change || remaining, currency)}</div>
</div>`
)
}
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/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index c716aa9..8473276 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -10,8 +10,8 @@
def execute(filters=None):
filters = frappe._dict(filters or {})
if filters.from_date > filters.to_date:
- frappe.throw(_('From Date cannot be greater than To Date'))
-
+ frappe.throw(_("From Date cannot be greater than To Date"))
+
columns = get_columns(filters)
data = get_data(filters)
@@ -148,14 +148,16 @@
company_list.append(filters.get("company"))
customer_details = get_customer_details()
+ item_details = get_item_details()
sales_order_records = get_sales_order_details(company_list, filters)
for record in sales_order_records:
customer_record = customer_details.get(record.customer)
+ item_record = item_details.get(record.item_code)
row = {
"item_code": record.item_code,
- "item_name": record.item_name,
- "item_group": record.item_group,
+ "item_name": item_record.item_name,
+ "item_group": item_record.item_group,
"description": record.description,
"quantity": record.qty,
"uom": record.uom,
@@ -196,8 +198,8 @@
return conditions
def get_customer_details():
- details = frappe.get_all('Customer',
- fields=['name', 'customer_name', "customer_group"])
+ details = frappe.get_all("Customer",
+ fields=["name", "customer_name", "customer_group"])
customer_details = {}
for d in details:
customer_details.setdefault(d.name, frappe._dict({
@@ -206,15 +208,25 @@
}))
return customer_details
+def get_item_details():
+ details = frappe.db.get_all("Item",
+ fields=["item_code", "item_name", "item_group"])
+ item_details = {}
+ for d in details:
+ item_details.setdefault(d.item_code, frappe._dict({
+ "item_name": d.item_name,
+ "item_group": d.item_group
+ }))
+ return item_details
+
def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters)
return frappe.db.sql("""
SELECT
- so_item.item_code, so_item.item_name, so_item.item_group,
- so_item.description, so_item.qty, so_item.uom,
- so_item.base_rate, so_item.base_amount, so.name,
- so.transaction_date, so.customer, so.territory,
+ so_item.item_code, so_item.description, so_item.qty,
+ so_item.uom, so_item.base_rate, so_item.base_amount,
+ so.name, so.transaction_date, so.customer,so.territory,
so.project, so_item.delivered_qty,
so_item.billed_amt, so.company
FROM
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 002cfe4..7f00fca 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -42,16 +42,6 @@
me.frm.set_query('customer_address', erpnext.queries.address_query);
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
- if(this.frm.fields_dict.taxes_and_charges) {
- this.frm.set_query("taxes_and_charges", function() {
- return {
- filters: [
- ['Sales Taxes and Charges Template', 'company', '=', me.frm.doc.company],
- ['Sales Taxes and Charges Template', 'docstatus', '!=', 2]
- ]
- }
- });
- }
if(this.frm.fields_dict.selling_price_list) {
this.frm.set_query("selling_price_list", function() {
@@ -479,7 +469,7 @@
$.each(frm.doc["items"] || [], function(i, row) {
if(r.message) {
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
- frappe.msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message));
+ frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
}
})
}
diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json
new file mode 100644
index 0000000..e20f834
--- /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 0000000..879034a
--- /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 23dec32..0000000
--- a/erpnext/setup/desk_page/home/home.json
+++ /dev/null
@@ -1,101 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Healthcare",
- "links": "[\n {\n \"label\": \"Patient\",\n \"name\": \"Patient\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Diagnosis\",\n \"name\": \"Diagnosis\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Agriculture",
- "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 {\n \"label\": \"Fertilizer\",\n \"name\": \"Fertilizer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Education",
- "links": "[\n {\n \"label\": \"Student\",\n \"name\": \"Student\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Course\",\n \"name\": \"Course\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Instructor\",\n \"name\": \"Instructor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Room\",\n \"name\": \"Room\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Non Profit",
- "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "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,
- "icon": "getting-started",
- "idx": 0,
- "is_standard": 1,
- "label": "Home",
- "modified": "2020-06-30 18:36:05.637904",
- "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"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index cbf67b4..36033d9 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -274,7 +274,8 @@
["default_employee_advance_account", {"root_type": "Asset"}],
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
- ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}]
+ ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
+ ["unrealized_profit_loss_account", {"root_type": "Liability"}]
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 40938ea..d49ae7c 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -46,10 +46,9 @@
"round_off_account",
"round_off_cost_center",
"write_off_account",
- "discount_allowed_account",
- "discount_received_account",
"exchange_gain_loss_account",
"unrealized_exchange_gain_loss_account",
+ "unrealized_profit_loss_account",
"column_break0",
"allow_account_creation_against_child_company",
"default_payable_account",
@@ -261,14 +260,14 @@
{
"fieldname": "create_chart_of_accounts_based_on",
"fieldtype": "Select",
- "label": "Create Chart of Accounts Based on",
+ "label": "Create Chart Of Accounts Based On",
"options": "\nStandard Template\nExisting Company"
},
{
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
"fieldname": "chart_of_accounts",
"fieldtype": "Select",
- "label": "Chart of Accounts Template",
+ "label": "Chart Of Accounts Template",
"no_copy": 1
},
{
@@ -346,18 +345,6 @@
"options": "Account"
},
{
- "fieldname": "discount_allowed_account",
- "fieldtype": "Link",
- "label": "Discount Allowed Account",
- "options": "Account"
- },
- {
- "fieldname": "discount_received_account",
- "fieldtype": "Link",
- "label": "Discount Received Account",
- "options": "Account"
- },
- {
"fieldname": "exchange_gain_loss_account",
"fieldtype": "Link",
"label": "Exchange Gain / Loss Account",
@@ -740,6 +727,12 @@
"fieldtype": "Link",
"label": "Default In Transit Warehouse",
"options": "Warehouse"
+ },
+ {
+ "fieldname": "unrealized_profit_loss_account",
+ "fieldtype": "Link",
+ "label": "Unrealized Profit / Loss Account",
+ "options": "Account"
}
],
"icon": "fa fa-building",
@@ -747,7 +740,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2020-08-06 00:38:08.311216",
+ "modified": "2020-12-03 12:27:27.085094",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
@@ -808,4 +801,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 4a438f7..dc45cc1 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -75,7 +75,7 @@
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"],
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index b30bd78..cbb4c7c 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -48,12 +48,8 @@
recipients = list(filter(lambda r: r in valid_users,
self.recipient_list.split("\n")))
- original_user = frappe.session.user
-
if recipients:
for user_id in recipients:
- frappe.set_user(user_id)
- frappe.set_user_lang(user_id)
msg_for_this_recipient = self.get_msg_html()
if msg_for_this_recipient:
frappe.sendmail(
@@ -64,9 +60,6 @@
reference_name = self.name,
unsubscribe_message = _("Unsubscribe from this Email Digest"))
- frappe.set_user(original_user)
- frappe.set_user_lang(original_user)
-
def get_msg_html(self):
"""Build email digest content"""
frappe.flags.ignore_account_permission = True
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index 8f7593d..b71a92f 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -5,8 +5,7 @@
refresh: function(frm) {
if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
var info = frm.doc.__onload.dashboard_info;
- frm.dashboard.add_indicator(__('Total Contribution Amount: {0}',
- [format_currency(info.allocated_amount, info.currency)]), 'blue');
+ frm.dashboard.add_indicator(__('Total Contribution Amount: {0}', [format_currency(info.allocated_amount, info.currency)]), 'blue');
}
},
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 5808ce7..7166ba3 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 @@
<h3>{%= __("Next Steps") %}</h3>
<ul class="list-unstyled">
<li><a class="text-muted" href="#">{%= __("Go to the Desktop and start using ERPNext") %}</a></li>
- <li><a class="text-muted" href="#modules/Learn">{%= __("View a list of all the help videos") %}</a></li>
<li><a class="text-muted" href="https://erpnext.com/docs/user" target="_blank">{%= __("Read the ERPNext Manual") %}</a></li>
<li><a class="text-muted" href="https://discuss.erpnext.com" target="_blank">{%= __("Community Forum") %}</a></li>
</ul>
diff --git a/erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
similarity index 96%
rename from erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json
rename to erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
index 5c8cd69..014f409 100644
--- a/erpnext/setup/desk_page/erpnext_settings/erpnext_settings.json
+++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
@@ -1,12 +1,11 @@
{
- "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,
@@ -14,7 +13,8 @@
"idx": 0,
"is_standard": 1,
"label": "ERPNext Settings",
- "modified": "2020-07-08 12:53:44.904241",
+ "links": [],
+ "modified": "2020-12-01 13:38:37.759596",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
new file mode 100644
index 0000000..13c1172
--- /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": "2020-12-16 10:24:52.088466",
+ "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 0ccc025..c2549fe 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -345,7 +345,7 @@
selling_price_list = None
# check if default customer price list exists
- if party_name:
+ if party_name and frappe.db.exists("Customer", party_name):
selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
# check default price list in shopping cart
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 9bd03d4..f64d593 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -198,7 +198,7 @@
freeze: true,
callback: function(r) {
frappe.show_alert(__('Stock Entry {0} created',
- ['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
+ ['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
dialog.hide();
callback(r);
},
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
deleted file mode 100644
index 9f8f346..0000000
--- a/erpnext/stock/desk_page/stock/stock.json
+++ /dev/null
@@ -1,125 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\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 \"label\": \"Product Bundle\",\n \"name\": \"Product Bundle\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Price List\",\n \"name\": \"Price List\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Price\",\n \"name\": \"Item Price\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipping Rule\",\n \"name\": \"Shipping Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Pricing Rule\",\n \"name\": \"Pricing Rule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Alternative\",\n \"name\": \"Item Alternative\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Manufacturer\",\n \"name\": \"Item Manufacturer\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Customs Tariff Number\",\n \"name\": \"Customs Tariff Number\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Stock Transactions",
- "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\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 \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Stock Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Ledger\",\n \"name\": \"Stock Ledger\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Balance\",\n \"name\": \"Stock Balance\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Projected Qty\",\n \"name\": \"Stock Projected Qty\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Summary\",\n \"name\": \"stock-balance\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Ageing\",\n \"name\": \"Stock Ageing\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Price Stock\",\n \"name\": \"Item Price Stock\",\n \"type\": \"report\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Settings",
- "links": "[\n {\n \"label\": \"Stock Settings\",\n \"name\": \"Stock Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Warehouse\",\n \"name\": \"Warehouse\",\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\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Brand\",\n \"name\": \"Brand\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Item Attribute\",\n \"name\": \"Item Attribute\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"UOM Conversion Factor\",\n \"name\": \"UOM Conversion Factor\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Serial No and Batch",
- "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Serial No\",\n \"name\": \"Serial No\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Batch\",\n \"name\": \"Batch\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Installation Note\",\n \"name\": \"Installation Note\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Serial No\"\n ],\n \"doctype\": \"Serial No\",\n \"label\": \"Serial No Service Contract Expiry\",\n \"name\": \"Serial No Service Contract Expiry\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Serial No\"\n ],\n \"doctype\": \"Serial No\",\n \"label\": \"Serial No Status\",\n \"name\": \"Serial No Status\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Serial No\"\n ],\n \"doctype\": \"Serial No\",\n \"label\": \"Serial No Warranty Expiry\",\n \"name\": \"Serial No Warranty Expiry\",\n \"type\": \"report\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Tools",
- "links": "[\n {\n \"label\": \"Stock Reconciliation\",\n \"name\": \"Stock Reconciliation\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Landed Cost Voucher\",\n \"name\": \"Landed Cost Voucher\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Packing Slip\",\n \"name\": \"Packing Slip\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Quality Inspection\",\n \"name\": \"Quality Inspection\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Quality Inspection Template\",\n \"name\": \"Quality Inspection Template\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Quick Stock Balance\",\n \"name\": \"Quick Stock Balance\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Key Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Item Price\"\n ],\n \"doctype\": \"Item Price\",\n \"is_query_report\": false,\n \"label\": \"Item-wise Price List Rate\",\n \"name\": \"Item-wise Price List Rate\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Entry\"\n ],\n \"doctype\": \"Stock Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Analytics\",\n \"name\": \"Stock Analytics\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Qty vs Serial No Count\",\n \"name\": \"Stock Qty vs Serial No Count\",\n \"onboard\": 1,\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 \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\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 \"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 \"Bin\"\n ],\n \"doctype\": \"Bin\",\n \"is_query_report\": true,\n \"label\": \"Item Shortage Report\",\n \"name\": \"Item Shortage Report\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Batch\"\n ],\n \"doctype\": \"Batch\",\n \"is_query_report\": true,\n \"label\": \"Batch-Wise Balance History\",\n \"name\": \"Batch-Wise Balance History\",\n \"type\": \"report\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Other Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Material Request\"\n ],\n \"doctype\": \"Material Request\",\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Transferred\",\n \"name\": \"Requested Items To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Batch Item Expiry Status\",\n \"name\": \"Batch Item Expiry Status\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Price List\"\n ],\n \"doctype\": \"Price List\",\n \"is_query_report\": true,\n \"label\": \"Item Prices\",\n \"name\": \"Item Prices\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Itemwise Recommended Reorder Level\",\n \"name\": \"Itemwise Recommended Reorder Level\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Variant Details\",\n \"name\": \"Item Variant Details\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Order\"\n ],\n \"doctype\": \"Purchase Order\",\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Stock Ledger Entry\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock and Account Value Comparison\",\n \"name\": \"Stock and Account Value Comparison\",\n \"type\": \"report\"\n }\n]"
- }
- ],
- "cards_label": "Masters & Reports",
- "category": "Modules",
- "charts": [
- {
- "chart_name": "Warehouse wise Stock Value"
- }
- ],
- "creation": "2020-03-02 15:43:10.096528",
- "developer_mode_only": 0,
- "disable_user_customization": 0,
- "docstatus": 0,
- "doctype": "Desk Page",
- "extends_another_page": 0,
- "hide_custom": 0,
- "icon": "stock",
- "idx": 0,
- "is_standard": 1,
- "label": "Stock",
- "modified": "2020-10-21 12:28:55.503562",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Stock",
- "onboarding": "Stock",
- "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"
- },
- {
- "label": "Stock Entry",
- "link_to": "Stock Entry",
- "type": "DocType"
- },
- {
- "color": "Yellow",
- "format": "{} To Bill",
- "label": "Purchase Receipt",
- "link_to": "Purchase Receipt",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
- "type": "DocType"
- },
- {
- "color": "Yellow",
- "format": "{} To Bill",
- "label": "Delivery Note",
- "link_to": "Delivery Note",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
- "type": "DocType"
- },
- {
- "label": "Stock Ledger",
- "link_to": "Stock Ledger",
- "type": "Report"
- },
- {
- "label": "Stock Balance",
- "link_to": "Stock Balance",
- "type": "Report"
- },
- {
- "label": "Dashboard",
- "link_to": "Stock",
- "type": "Dashboard"
- }
- ],
- "shortcuts_label": "Quick Access"
-}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index 71a3e7a..3b07e4e 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -102,7 +102,7 @@
},
callback: (r) => {
frappe.show_alert(__('Stock Entry {0} created',
- ['<a href="#Form/Stock Entry/'+r.message.name+'">' + r.message.name+ '</a>']));
+ ['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
frm.refresh();
},
});
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 251a26a..03921c5 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -156,6 +156,11 @@
}
if (!doc.is_return && doc.status!="Closed") {
+ if(doc.docstatus == 1) {
+ this.frm.add_custom_button(__('Shipment'), function() {
+ me.make_shipment() }, __('Create'));
+ }
+
if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
this.frm.add_custom_button(__('Installation Note'), function() {
me.make_installation_note() }, __('Create'));
@@ -220,6 +225,13 @@
}
},
+ make_shipment: function() {
+ frappe.model.open_mapped_doc({
+ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_shipment",
+ frm: this.frm
+ })
+ },
+
make_sales_invoice: function() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 3c5129b..c9f8d08 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:09",
@@ -132,6 +133,7 @@
"per_installed",
"installation_status",
"column_break_89",
+ "per_returned",
"excise_page",
"instructions",
"subscription_section",
@@ -1098,7 +1100,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+ "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -1250,13 +1252,22 @@
"fieldtype": "Link",
"label": "Inter Company Reference",
"options": "Purchase Receipt"
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "per_returned",
+ "fieldtype": "Percent",
+ "label": "% Returned",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-11-11 14:57:16.388139",
+ "modified": "2020-11-30 12:54:45.407289",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 1994dfd..8bed16d 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -55,7 +55,7 @@
'no_allowance': 1
}]
if cint(self.is_return):
- self.status_updater.append({
+ self.status_updater.extend([{
'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item',
'join_field': 'so_detail',
@@ -69,7 +69,19 @@
where name=`tabDelivery Note Item`.parent and is_return=1)""",
'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
- })
+ },
+ {
+ 'source_dt': 'Delivery Note Item',
+ 'target_dt': 'Delivery Note Item',
+ 'join_field': 'dn_detail',
+ 'target_field': 'returned_qty',
+ 'target_parent_dt': 'Delivery Note',
+ 'target_parent_field': 'per_returned',
+ 'target_ref_field': 'stock_qty',
+ 'source_field': '-1 * stock_qty',
+ 'percent_join_field_parent': 'return_against'
+ }
+ ])
def before_print(self, settings=None):
def toggle_print_hide(meta, fieldname):
@@ -569,6 +581,62 @@
return doclist
+@frappe.whitelist()
+def make_shipment(source_name, target_doc=None):
+ def postprocess(source, target):
+ user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1)
+ target.pickup_contact_email = user.email
+ pickup_contact_display = '{}'.format(user.full_name)
+ if user:
+ if user.email:
+ pickup_contact_display += '<br>' + user.email
+ if user.phone:
+ pickup_contact_display += '<br>' + user.phone
+ if user.mobile_no and not user.phone:
+ pickup_contact_display += '<br>' + user.mobile_no
+ target.pickup_contact = pickup_contact_display
+
+ contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1)
+ delivery_contact_display = '{}'.format(source.contact_display)
+ if contact:
+ if contact.email_id:
+ delivery_contact_display += '<br>' + contact.email_id
+ if contact.phone:
+ delivery_contact_display += '<br>' + contact.phone
+ if contact.mobile_no and not contact.phone:
+ delivery_contact_display += '<br>' + contact.mobile_no
+ target.delivery_contact = delivery_contact_display
+
+ doclist = get_mapped_doc("Delivery Note", source_name, {
+ "Delivery Note": {
+ "doctype": "Shipment",
+ "field_map": {
+ "grand_total": "value_of_goods",
+ "company": "pickup_company",
+ "company_address": "pickup_address_name",
+ "company_address_display": "pickup_address",
+ "address_display": "delivery_address",
+ "customer": "delivery_customer",
+ "shipping_address_name": "delivery_address_name",
+ "contact_person": "delivery_contact_name",
+ "contact_email": "delivery_contact_email"
+ },
+ "validation": {
+ "docstatus": ["=", 1]
+ }
+ },
+ "Delivery Note Item": {
+ "doctype": "Shipment Delivery Note",
+ "field_map": {
+ "name": "prevdoc_detail_docname",
+ "parent": "prevdoc_docname",
+ "parenttype": "prevdoc_doctype",
+ "base_amount": "grand_total"
+ }
+ }
+ }, target_doc, postprocess)
+
+ return doclist
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index 98ababa..f08125b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -6,9 +6,11 @@
return [__("Return"), "gray", "is_return,=,Yes"];
} else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
+ } else if (flt(doc.per_returned, 2) === 100) {
+ return [__("Return Issued"), "grey", "per_returned,=,100"];
} else if (flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.per_billed, 2) == 100) {
+ } else if (flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
},
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 9566af7..6b4663a 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -206,7 +206,7 @@
for field, value in field_values.items():
self.assertEqual(cstr(serial_no.get(field)), value)
- def test_sales_return_for_non_bundled_items(self):
+ def test_sales_return_for_non_bundled_items_partial(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
@@ -225,7 +225,10 @@
# return entry
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500,
- company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1", do_not_submit=1)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1")
@@ -243,6 +246,70 @@
self.assertEqual(gle_warehouse_amount, stock_value_difference)
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Delivery Note", dn1.name)
+ returned.update_prevdoc_status()
+ dn.load_from_db()
+
+ # Check if Original DN updated
+ self.assertEqual(dn.items[0].returned_qty, 2)
+ self.assertEqual(dn.per_returned, 40)
+
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ return_dn_2 = make_return_doc("Delivery Note", dn.name)
+
+ # Check if unreturned amount is mapped in 2nd return
+ self.assertEqual(return_dn_2.items[0].qty, -3)
+
+ si = make_sales_invoice(dn.name)
+ si.submit()
+
+ self.assertEqual(si.items[0].qty, 3)
+
+ dn.load_from_db()
+ # DN should be completed on billing all unreturned amount
+ self.assertEqual(dn.items[0].billed_amt, 1500)
+ self.assertEqual(dn.per_billed, 100)
+ self.assertEqual(dn.status, 'Completed')
+
+ si.load_from_db()
+ si.cancel()
+
+ dn.load_from_db()
+ self.assertEqual(dn.per_billed, 0)
+
+ dn1.cancel()
+ dn.cancel()
+
+ def test_sales_return_for_non_bundled_items_full(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+
+ make_item("Box", {'is_stock_item': 1})
+
+ make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100)
+
+ dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company,
+ expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+
+ #return entry
+ dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500,
+ company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1", do_not_submit=1)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
+
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Delivery Note", dn1.name)
+ returned.update_prevdoc_status()
+ dn.load_from_db()
+
+ # Check if Original DN updated
+ self.assertEqual(dn.items[0].returned_qty, 5)
+ self.assertEqual(dn.per_returned, 100)
+ self.assertEqual(dn.status, 'Return Issued')
+
def test_return_single_item_from_bundled_items(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 3d57f47..7b47187 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-04-22 13:15:44",
"doctype": "DocType",
@@ -24,7 +25,10 @@
"col_break2",
"uom",
"conversion_factor",
+ "stock_qty_sec_break",
"stock_qty",
+ "stock_qty_col_break",
+ "returned_qty",
"section_break_17",
"price_list_rate",
"base_price_list_rate",
@@ -211,7 +215,7 @@
{
"fieldname": "stock_qty",
"fieldtype": "Float",
- "label": "Qty as per Stock UOM",
+ "label": "Qty in Stock UOM",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
@@ -715,12 +719,29 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "stock_qty_sec_break",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "stock_qty_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "returned_qty",
+ "fieldname": "returned_qty",
+ "fieldtype": "Float",
+ "label": "Returned Qty in Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-07-20 12:25:06.177894",
+ "modified": "2020-07-31 20:12:43.054342",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 8b8b700..2d5ab5a 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -85,7 +85,7 @@
}
if (frm.doc.variant_of) {
frm.set_intro(__('This Item is a Variant of {0} (Template).',
- [`<a href="#Form/Item/${frm.doc.variant_of}">${frm.doc.variant_of}</a>`]), true);
+ [`<a href="/app/item/${frm.doc.variant_of}">${frm.doc.variant_of}</a>`]), true);
}
if (frappe.defaults.get_default("item_naming_by")!="Naming Series" || frm.doc.variant_of) {
@@ -649,7 +649,7 @@
if (r.message) {
var variant = r.message;
frappe.msgprint_dialog = frappe.msgprint(__("Item Variant {0} already exists with same attributes",
- [repl('<a href="#Form/Item/%(item_encoded)s" class="strong variant-click">%(item)s</a>', {
+ [repl('<a href="/app/item/%(item_encoded)s" class="strong variant-click">%(item)s</a>', {
item_encoded: encodeURIComponent(variant),
item: variant
})]
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 3b62c38..86ce5cc 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -860,7 +860,7 @@
rows = ''
for docname, attr_list in not_included.items():
- link = "<a href='#Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
+ link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
rows += table_row(link, body(attr_list))
error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')
@@ -977,15 +977,20 @@
# For "Is Stock Item", following doctypes is important
# because reserved_qty, ordered_qty and requested_qty updated from these doctypes
if field == "is_stock_item":
- linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item"]
+ linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item", "Product Bundle"]
for doctype in linked_doctypes:
+ filters={"item_code": self.name, "docstatus": 1}
+
+ if doctype == "Product Bundle":
+ filters={"new_item_code": self.name}
+
if doctype in ("Purchase Invoice Item", "Sales Invoice Item",):
# If Invoice has Stock impact, only then consider it.
if self.stock_ledger_created():
return True
- elif frappe.db.get_value(doctype, filters={"item_code": self.name, "docstatus": 1}):
+ elif frappe.db.get_value(doctype, filters):
return True
def validate_auto_reorder_enabled_in_stock_settings(self):
diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js
index 2729f4b..017d248 100644
--- a/erpnext/stock/doctype/item_price/item_price.js
+++ b/erpnext/stock/doctype/item_price/item_price.js
@@ -14,6 +14,6 @@
frm.add_fetch("item_code", "stock_uom", "uom");
frm.set_df_property("bulk_import_help", "options",
- '<a href="#data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
+ '<a href="/app/data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
}
});
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 51b47c5..bed5ea9 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -4,14 +4,13 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-
-
-class ItemPriceDuplicateItem(frappe.ValidationError): pass
-
-
from frappe.model.document import Document
+class ItemPriceDuplicateItem(frappe.ValidationError):
+ pass
+
+
class ItemPrice(Document):
def validate(self):
@@ -23,7 +22,7 @@
def validate_item(self):
if not frappe.db.exists("Item", self.item_code):
- frappe.throw(_("Item {0} not found").format(self.item_code))
+ frappe.throw(_("Item {0} not found.").format(self.item_code))
def validate_dates(self):
if self.valid_from and self.valid_upto:
@@ -38,40 +37,45 @@
if not price_list_details:
link = frappe.utils.get_link_to_form('Price List', self.price_list)
- frappe.throw("The price list {0} does not exists or disabled".
- format(link))
+ frappe.throw("The price list {0} does not exist or is disabled".format(link))
self.buying, self.selling, self.currency = price_list_details
def update_item_details(self):
if self.item_code:
- self.item_name, self.item_description = frappe.db.get_value("Item",
- self.item_code,["item_name", "description"])
+ self.item_name, self.item_description = frappe.db.get_value("Item", self.item_code,["item_name", "description"])
def check_duplicates(self):
- conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s"
- condition_data_dict = dict(item_code=self.item_code, price_list=self.price_list, name=self.name)
+ conditions = """where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
- for field in ['uom', 'valid_from',
- 'valid_upto', 'packing_unit', 'customer', 'supplier']:
+ for field in [
+ "uom",
+ "valid_from",
+ "valid_upto",
+ "packing_unit",
+ "customer",
+ "supplier",]:
if self.get(field):
- conditions += " and {0} = %({1})s".format(field, field)
- condition_data_dict[field] = self.get(field)
+ conditions += " and {0} = %({0})s ".format(field)
+ else:
+ conditions += "and (isnull({0}) or {0} = '')".format(field)
price_list_rate = frappe.db.sql("""
- SELECT price_list_rate
- FROM `tabItem Price`
- {conditions} """.format(conditions=conditions), condition_data_dict)
+ select price_list_rate
+ from `tabItem Price`
+ {conditions}
+ """.format(conditions=conditions),
+ self.as_dict(),)
- if price_list_rate :
- frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates."), ItemPriceDuplicateItem)
+ if price_list_rate:
+ frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
def before_save(self):
if self.selling:
self.reference = self.customer
if self.buying:
self.reference = self.supplier
-
+
if self.selling and not self.buying:
# if only selling then remove supplier
self.supplier = None
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 702acc3..f3d406e 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -138,4 +138,23 @@
# Valid price list must already exist
self.assertRaises(frappe.ValidationError, doc.save)
+ def test_empty_duplicate_validation(self):
+ # Check if none/empty values are not compared during insert validation
+ doc = frappe.copy_doc(test_records[2])
+ doc.customer = None
+ doc.price_list_rate = 21
+ doc.insert()
+
+ args = {
+ "price_list": doc.price_list,
+ "uom": "_Test UOM",
+ "transaction_date": '2017-04-18',
+ "qty": 7
+ }
+
+ price = get_price_list_rate_for(args, doc.item_code)
+ frappe.db.rollback()
+
+ self.assertEqual(price, 21)
+
test_records = frappe.get_test_records('Item Price')
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
index f1e1fd3..888bc2d 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
@@ -1,88 +1,57 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:28:01",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:28:01",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "specification",
+ "value",
+ "column_break_3",
+ "acceptance_formula"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "specification",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Parameter",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "specification",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "specification",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Parameter",
+ "oldfieldname": "specification",
+ "oldfieldtype": "Data",
+ "print_width": "200px",
+ "reqd": 1,
"width": "200px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "value",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Acceptance Criteria",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "value",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "value",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Acceptance Criteria",
+ "oldfieldname": "value",
+ "oldfieldtype": "Data"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 < 10</b>",
+ "fieldname": "acceptance_formula",
+ "fieldtype": "Code",
+ "in_list_view": 1,
+ "label": "Acceptance Criteria Formula"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:28:01.074316",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Quality Inspection Parameter",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-16 16:33:42.421842",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Quality Inspection Parameter",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ce54fc8..5bb3095 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@@ -110,6 +111,7 @@
"range",
"column_break4",
"per_billed",
+ "per_returned",
"is_internal_supplier",
"inter_company_reference",
"subscription_detail",
@@ -894,7 +896,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+ "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -1103,13 +1105,22 @@
"fieldtype": "Small Text",
"label": "Billing Address",
"read_only": 1
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "per_returned",
+ "fieldtype": "Percent",
+ "label": "% Returned",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-08-03 23:20:26.381024",
+ "modified": "2020-11-30 12:54:23.278500",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index d964669..97e0fa7 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -55,20 +55,33 @@
'percent_join_field': 'material_request'
}]
if cint(self.is_return):
- self.status_updater.append({
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'purchase_order_item',
- 'target_field': 'returned_qty',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Purchase Invoice Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'po_detail',
- 'extra_cond': """ and exists (select name from `tabPurchase Receipt`
- where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
- 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
- })
+ self.status_updater.extend([
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Order Item',
+ 'join_field': 'purchase_order_item',
+ 'target_field': 'returned_qty',
+ 'source_field': '-1 * qty',
+ 'second_source_dt': 'Purchase Invoice Item',
+ 'second_source_field': '-1 * qty',
+ 'second_join_field': 'po_detail',
+ 'extra_cond': """ and exists (select name from `tabPurchase Receipt`
+ where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
+ 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
+ },
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Receipt Item',
+ 'join_field': 'purchase_receipt_item',
+ 'target_field': 'returned_qty',
+ 'target_parent_dt': 'Purchase Receipt',
+ 'target_parent_field': 'per_returned',
+ 'target_ref_field': 'received_stock_qty',
+ 'source_field': '-1 * received_stock_qty',
+ 'percent_join_field_parent': 'return_against'
+ }
+ ])
def validate(self):
self.validate_posting_time()
@@ -478,7 +491,7 @@
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
def update_status(self, status):
- self.set_status(update=True, status = status)
+ self.set_status(update=True, status=status)
self.notify_update()
clear_doctype_notifications(self)
@@ -490,7 +503,7 @@
for pr in set(updated_pr):
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
- pr_doc.update_billing_percentage(update_modified=update_modified)
+ update_billing_percentage(pr_doc, update_modified=update_modified)
self.load_from_db()
@@ -500,7 +513,7 @@
where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
- # Get all Delivery Note Item rows against the Sales Order Item row
+ # Get all Purchase Receipt Item rows against the Purchase Order Item row
pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name=pr_item.parent and pr_item.purchase_order_item=%s
@@ -530,6 +543,39 @@
return updated_pr
+def update_billing_percentage(pr_doc, update_modified=True):
+ # Reload as billed amount was set in db directly
+ pr_doc.load_from_db()
+
+ # Update Billing % based on pending accepted qty
+ total_amount, total_billed_amount = 0, 0
+ for item in pr_doc.items:
+ return_data = frappe.db.get_list("Purchase Receipt",
+ fields = [
+ "sum(abs(`tabPurchase Receipt Item`.qty)) as qty"
+ ],
+ filters = [
+ ["Purchase Receipt", "docstatus", "=", 1],
+ ["Purchase Receipt", "is_return", "=", 1],
+ ["Purchase Receipt Item", "purchase_receipt_item", "=", item.name]
+ ])
+
+ returned_qty = return_data[0].qty if return_data else 0
+ returned_amount = flt(returned_qty) * flt(item.rate)
+ pending_amount = flt(item.amount) - returned_amount
+ total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
+
+ total_amount += total_billable_amount
+ total_billed_amount += flt(item.billed_amt)
+
+ percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
+ pr_doc.db_set("per_billed", percent_billed)
+ pr_doc.load_from_db()
+
+ if update_modified:
+ pr_doc.set_status(update=True)
+ pr_doc.notify_update()
+
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
@@ -552,6 +598,7 @@
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
@@ -572,7 +619,8 @@
"doctype": "Purchase Invoice",
"field_map": {
"supplier_warehouse":"supplier_warehouse",
- "is_return": "is_return"
+ "is_return": "is_return",
+ "bill_date": "bill_date"
},
"validation": {
"docstatus": ["=", 1],
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index aed2e49..77711de 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -6,9 +6,11 @@
return [__("Return"), "gray", "is_return,=,Yes"];
} else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
+ } else if (flt(doc.per_returned, 2) === 100) {
+ return [__("Return Issued"), "grey", "per_returned,=,100"];
} else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) {
+ } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 253edb0..9b8eeed 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -137,7 +137,10 @@
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
def test_purchase_receipt_gl_entry(self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+ get_multiple_items = True, get_taxes_and_charges = True)
+
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -281,11 +284,15 @@
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
pr.get("items")[0].rejected_warehouse)
- def test_purchase_return(self):
+ def test_purchase_return_partial(self):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
-
- return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2)
+ return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+ is_return=1, return_against=pr.name, qty=-2, do_not_submit=1)
+ return_pr.items[0].purchase_receipt_item = pr.items[0].name
+ return_pr.submit()
# check sle
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
@@ -309,6 +316,60 @@
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+ returned.update_prevdoc_status()
+ pr.load_from_db()
+
+ # Check if Original PR updated
+ self.assertEqual(pr.items[0].returned_qty, 2)
+ self.assertEqual(pr.per_returned, 40)
+
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ return_pr_2 = make_return_doc("Purchase Receipt", pr.name)
+
+ # Check if unreturned amount is mapped in 2nd return
+ self.assertEqual(return_pr_2.items[0].qty, -3)
+
+ # Make PI against unreturned amount
+ pi = make_purchase_invoice(pr.name)
+ pi.submit()
+
+ self.assertEqual(pi.items[0].qty, 3)
+
+ pr.load_from_db()
+ # PR should be completed on billing all unreturned amount
+ self.assertEqual(pr.items[0].billed_amt, 150)
+ self.assertEqual(pr.per_billed, 100)
+ self.assertEqual(pr.status, 'Completed')
+
+ pi.load_from_db()
+ pi.cancel()
+
+ pr.load_from_db()
+ self.assertEqual(pr.per_billed, 0)
+
+ return_pr.cancel()
+ pr.cancel()
+
+ def test_purchase_return_full(self):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+ supplier_warehouse = "Work in Progress - TCP1")
+
+ return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+ supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1)
+ return_pr.items[0].purchase_receipt_item = pr.items[0].name
+ return_pr.submit()
+
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+ returned.update_prevdoc_status()
+ pr.load_from_db()
+
+ # Check if Original PR updated
+ self.assertEqual(pr.items[0].returned_qty, 5)
+ self.assertEqual(pr.per_returned, 100)
+ self.assertEqual(pr.status, 'Return Issued')
def test_purchase_return_for_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
@@ -416,6 +477,7 @@
self.assertEqual(pr1.per_billed, 100)
self.assertEqual(pr1.status, "Completed")
+ pr2.load_from_db()
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
self.assertEqual(pr2.per_billed, 80)
self.assertEqual(pr2.status, "To Bill")
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index c1e1f90..84c64aa 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -28,9 +28,13 @@
"uom",
"stock_uom",
"conversion_factor",
- "stock_qty",
"retain_sample",
"sample_quantity",
+ "tracking_section",
+ "received_stock_qty",
+ "stock_qty",
+ "col_break_tracking_section",
+ "returned_qty",
"rate_and_amount",
"price_list_rate",
"discount_percentage",
@@ -526,7 +530,7 @@
{
"fieldname": "stock_qty",
"fieldtype": "Float",
- "label": "Accepted Qty as per Stock UOM",
+ "label": "Accepted Qty in Stock UOM",
"oldfieldname": "stock_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
@@ -834,12 +838,35 @@
"collapsible": 1,
"fieldname": "image_column",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "tracking_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "col_break_tracking_section",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "returned_qty",
+ "fieldname": "returned_qty",
+ "fieldtype": "Float",
+ "label": "Returned Qty in Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "received_stock_qty",
+ "fieldtype": "Float",
+ "label": "Received Qty in Stock UOM",
+ "print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-28 19:01:21.154963",
+ "modified": "2020-11-02 10:00:38.204294",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
index 22f29e0..376848a 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
@@ -31,17 +31,27 @@
// item code based on GRN/DN
cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
- const doctype = (doc.reference_type == "Stock Entry") ?
- "Stock Entry Detail" : doc.reference_type + " Item";
+ let doctype = doc.reference_type;
+
+ if (doc.reference_type !== "Job Card") {
+ doctype = (doc.reference_type == "Stock Entry") ?
+ "Stock Entry Detail" : doc.reference_type + " Item";
+ }
if (doc.reference_type && doc.reference_name) {
+ let filters = {
+ "from": doctype,
+ "inspection_type": doc.inspection_type
+ };
+
+ if (doc.reference_type == doctype)
+ filters["reference_name"] = doc.reference_name;
+ else
+ filters["parent"] = doc.reference_name;
+
return {
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
- filters: {
- "from": doctype,
- "parent": doc.reference_name,
- "inspection_type": doc.inspection_type
- }
+ filters: filters
};
}
},
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index dd95075..f6d7619 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -73,7 +73,7 @@
"fieldname": "reference_type",
"fieldtype": "Select",
"label": "Reference Type",
- "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry",
+ "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry\nJob Card",
"reqd": 1
},
{
@@ -236,7 +236,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-10-21 13:03:11.938072",
+ "modified": "2020-11-19 17:06:05.409963",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index c3bb514..ae4eb9b 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -4,15 +4,20 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from frappe.model.mapper import get_mapped_doc
+from frappe import _
+from frappe.utils import flt
from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
import get_template_details
-from frappe.model.mapper import get_mapped_doc
class QualityInspection(Document):
def validate(self):
if not self.readings and self.item_code:
self.get_item_specification_details()
+ if self.readings:
+ self.set_status_based_on_acceptance_formula()
+
def get_item_specification_details(self):
if not self.quality_inspection_template:
self.quality_inspection_template = frappe.db.get_value('Item',
@@ -26,6 +31,7 @@
child = self.append('readings', {})
child.specification = d.specification
child.value = d.value
+ child.acceptance_formula = d.acceptance_formula
child.status = "Accepted"
def get_quality_inspection_template(self):
@@ -47,16 +53,51 @@
def update_qc_reference(self):
quality_inspection = self.name if self.docstatus == 1 else ""
- doctype = self.reference_type + ' Item'
- if self.reference_type == 'Stock Entry':
- doctype = 'Stock Entry Detail'
- if self.reference_type and self.reference_name:
- frappe.db.sql("""update `tab{child_doc}` t1, `tab{parent_doc}` t2
- set t1.quality_inspection = %s, t2.modified = %s
- where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name"""
- .format(parent_doc=self.reference_type, child_doc=doctype),
- (quality_inspection, self.modified, self.reference_name, self.item_code))
+ if self.reference_type == 'Job Card':
+ if self.reference_name:
+ frappe.db.sql("""
+ UPDATE `tab{doctype}`
+ SET quality_inspection = %s, modified = %s
+ WHERE name = %s and production_item = %s
+ """.format(doctype=self.reference_type),
+ (quality_inspection, self.modified, self.reference_name, self.item_code))
+
+ else:
+ doctype = self.reference_type + ' Item'
+ if self.reference_type == 'Stock Entry':
+ doctype = 'Stock Entry Detail'
+
+ if self.reference_type and self.reference_name:
+ frappe.db.sql("""
+ UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2
+ SET t1.quality_inspection = %s, t2.modified = %s
+ WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name
+ """.format(parent_doc=self.reference_type, child_doc=doctype),
+ (quality_inspection, self.modified, self.reference_name, self.item_code))
+
+ def set_status_based_on_acceptance_formula(self):
+ for reading in self.readings:
+ if not reading.acceptance_formula: continue
+
+ condition = reading.acceptance_formula
+ data = {}
+ for i in range(1, 11):
+ field = "reading_" + str(i)
+ data[field] = flt(reading.get(field)) or 0
+
+ try:
+ result = frappe.safe_eval(condition, None, data)
+ reading.status = "Accepted" if result else "Rejected"
+ except SyntaxError:
+ frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
+ title=_("Invalid Formula"))
+ except NameError as e:
+ field = frappe.bold(e.args[0].split()[1])
+ frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
+ .format(reading.idx, field),
+ title=_("Invalid Formula"))
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -66,27 +107,44 @@
mcond = get_match_cond(filters["from"])
cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
- if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
- and filters.get("inspection_type") != "In Process":
- cond = """and item_code in (select name from `tabItem` where
- inspection_required_before_purchase = 1)"""
- elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
- and filters.get("inspection_type") != "In Process":
- cond = """and item_code in (select name from `tabItem` where
- inspection_required_before_delivery = 1)"""
- elif filters.get('from') == 'Stock Entry Detail':
- cond = """and s_warehouse is null"""
+ if filters.get("parent"):
+ if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
+ and filters.get("inspection_type") != "In Process":
+ cond = """and item_code in (select name from `tabItem` where
+ inspection_required_before_purchase = 1)"""
+ elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
+ and filters.get("inspection_type") != "In Process":
+ cond = """and item_code in (select name from `tabItem` where
+ inspection_required_before_delivery = 1)"""
+ elif filters.get('from') == 'Stock Entry Detail':
+ cond = """and s_warehouse is null"""
- if filters.get('from') in ['Supplier Quotation Item']:
- qi_condition = ""
+ if filters.get('from') in ['Supplier Quotation Item']:
+ qi_condition = ""
- return frappe.db.sql(""" select item_code from `tab{doc}`
- where parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
- {qi_condition} {cond} {mcond}
- order by item_code limit {start}, {page_len}""".format(doc=filters.get('from'),
- parent=filters.get('parent'), cond = cond, mcond = mcond, start = start,
- page_len = page_len, qi_condition = qi_condition),
- {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+ return frappe.db.sql("""
+ SELECT item_code
+ FROM `tab{doc}`
+ WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
+ {qi_condition} {cond} {mcond}
+ ORDER BY item_code limit {start}, {page_len}
+ """.format(doc=filters.get('from'),
+ cond = cond, mcond = mcond, start = start,
+ page_len = page_len, qi_condition = qi_condition),
+ {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+
+ elif filters.get("reference_name"):
+ return frappe.db.sql("""
+ SELECT production_item
+ FROM `tab{doc}`
+ WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
+ {qi_condition} {cond} {mcond}
+ ORDER BY production_item
+ LIMIT {start}, {page_len}
+ """.format(doc=filters.get("from"),
+ cond = cond, mcond = mcond, start = start,
+ page_len = page_len, qi_condition = qi_condition),
+ {'reference_name': filters.get('reference_name'), 'txt': "%%%s%%" % txt})
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index bb535c1..2c40009 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -7,6 +7,7 @@
from frappe.utils import nowdate
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError
# test_records = frappe.get_test_records('Quality Inspection')
@@ -17,10 +18,12 @@
frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1)
def test_qa_for_delivery(self):
+ make_stock_entry(item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100)
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+
self.assertRaises(QualityInspectionRequiredError, dn.submit)
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected", submit=True)
+ qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected")
dn.reload()
self.assertRaises(QualityInspectionRejectedError, dn.submit)
@@ -28,12 +31,51 @@
dn.reload()
dn.submit()
+ qa.cancel()
+ dn.reload()
+ dn.cancel()
+
def test_qa_not_submit(self):
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
- qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, submit = False)
+ qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, do_not_submit=True)
dn.items[0].quality_inspection = qa.name
self.assertRaises(QualityInspectionNotSubmittedError, dn.submit)
+ qa.delete()
+ dn.delete()
+
+ def test_formula_based_qi_readings(self):
+ dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+ readings = [{
+ "specification": "Iron Content",
+ "acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
+ "reading_1": 0.4
+ },
+ {
+ "specification": "Calcium Content",
+ "acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
+ "reading_1": 0.7
+ },
+ {
+ "specification": "Mg Content",
+ "acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9",
+ "reading_1": 0.5,
+ "reading_2": 0.7,
+ "reading_3": "random text" # check if random string input causes issues
+ }]
+
+ qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
+ readings=readings, do_not_save=True)
+ qa.save()
+
+ # status must be auto set as per formula
+ self.assertEqual(qa.readings[0].status, "Accepted")
+ self.assertEqual(qa.readings[1].status, "Rejected")
+ self.assertEqual(qa.readings[2].status, "Accepted")
+
+ qa.delete()
+ dn.delete()
+
def create_quality_inspection(**args):
args = frappe._dict(args)
qa = frappe.new_doc("Quality Inspection")
@@ -44,12 +86,18 @@
qa.item_code = args.item_code or "_Test Item with QA"
qa.sample_size = 1
qa.inspected_by = frappe.session.user
- qa.append("readings", {
- "specification": "Size",
- "status": args.status
- })
- qa.save()
- if args.submit:
- qa.submit()
+
+ readings = args.readings or {"specification": "Size", "status": args.status}
+
+ if isinstance(readings, list):
+ for entry in readings:
+ qa.append("readings", entry)
+ else:
+ qa.append("readings", readings)
+
+ if not args.do_not_save:
+ qa.save()
+ if not args.do_not_submit:
+ qa.submit()
return qa
diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
index f9f8a71..c1976dd 100644
--- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
+++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
@@ -1,22 +1,29 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-02-22 01:27:43",
"doctype": "DocType",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"specification",
"value",
+ "status",
+ "column_break_4",
+ "acceptance_formula",
+ "section_break_3",
"reading_1",
"reading_2",
"reading_3",
+ "column_break_10",
"reading_4",
"reading_5",
"reading_6",
+ "column_break_14",
"reading_7",
"reading_8",
"reading_9",
- "reading_10",
- "status"
+ "reading_10"
],
"fields": [
{
@@ -124,15 +131,40 @@
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "Accepted\nRejected"
+ },
+ {
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 > 0.2 and reading_1 < 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 < 10</b>",
+ "fieldname": "acceptance_formula",
+ "fieldtype": "Code",
+ "label": "Acceptance Criteria Formula"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-11 18:48:12.667404",
+ "links": [],
+ "modified": "2020-11-16 16:34:29.947856",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection Reading",
"owner": "Administrator",
"permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index 0d9a903..e284846 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -12,5 +12,7 @@
def get_template_details(template):
if not template: return []
- return frappe.get_all('Item Quality Inspection Parameter', fields=["specification", "value"],
- filters={'parenttype': 'Quality Inspection Template', 'parent': template}, order_by="idx")
\ No newline at end of file
+ return frappe.get_all('Item Quality Inspection Parameter',
+ fields=["specification", "value", "acceptance_formula"],
+ filters={'parenttype': 'Quality Inspection Template', 'parent': template},
+ order_by="idx")
\ No newline at end of file
diff --git a/erpnext/config/__init__.py b/erpnext/stock/doctype/shipment/__init__.py
similarity index 100%
copy from erpnext/config/__init__.py
copy to erpnext/stock/doctype/shipment/__init__.py
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
new file mode 100644
index 0000000..7af16af
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -0,0 +1,451 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Shipment', {
+ address_query: function(frm, link_doctype, link_name, is_your_company_address) {
+ return {
+ query: 'frappe.contacts.doctype.address.address.address_query',
+ filters: {
+ link_doctype: link_doctype,
+ link_name: link_name,
+ is_your_company_address: is_your_company_address
+ }
+ };
+ },
+ contact_query: function(frm, link_doctype, link_name) {
+ return {
+ query: 'frappe.contacts.doctype.contact.contact.contact_query',
+ filters: {
+ link_doctype: link_doctype,
+ link_name: link_name
+ }
+ };
+ },
+ onload: function(frm) {
+ frm.set_query("delivery_address_name", () => {
+ let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+ return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0);
+ });
+ frm.set_query("pickup_address_name", () => {
+ let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+ return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0);
+ });
+ frm.set_query("delivery_contact_name", () => {
+ let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+ return frm.events.contact_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to]);
+ });
+ frm.set_query("pickup_contact_name", () => {
+ let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+ return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]);
+ });
+ frm.set_query("delivery_note", "shipment_delivery_note", function() {
+ let customer = '';
+ if (frm.doc.delivery_to_type == "Customer") {
+ customer = frm.doc.delivery_customer;
+ }
+ if (frm.doc.delivery_to_type == "Company") {
+ customer = frm.doc.delivery_company;
+ }
+ if (customer) {
+ return {
+ filters: {
+ customer: customer,
+ docstatus: 1,
+ status: ["not in", ["Cancelled"]]
+ }
+ };
+ }
+ });
+ },
+ refresh: function() {
+ $('div[data-fieldname=pickup_address] > div > .clearfix').hide();
+ $('div[data-fieldname=pickup_contact] > div > .clearfix').hide();
+ $('div[data-fieldname=delivery_address] > div > .clearfix').hide();
+ $('div[data-fieldname=delivery_contact] > div > .clearfix').hide();
+ },
+ before_save: function(frm) {
+ let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
+ frm.set_value("delivery_to", frm.doc[delivery_to]);
+ let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
+ frm.set_value("pickup", frm.doc[pickup_from]);
+ },
+ set_pickup_company_address: function(frm) {
+ frappe.db.get_value('Address', {
+ address_title: frm.doc.pickup_company,
+ is_your_company_address: 1
+ }, 'name', (r) => {
+ frm.set_value("pickup_address_name", r.name);
+ });
+ },
+ set_delivery_company_address: function(frm) {
+ frappe.db.get_value('Address', {
+ address_title: frm.doc.delivery_company,
+ is_your_company_address: 1
+ }, 'name', (r) => {
+ frm.set_value("delivery_address_name", r.name);
+ });
+ },
+ pickup_from_type: function(frm) {
+ if (frm.doc.pickup_from_type == 'Company') {
+ frm.set_value("pickup_company", frappe.defaults.get_default('company'));
+ frm.set_value("pickup_customer", '');
+ frm.set_value("pickup_supplier", '');
+ } else {
+ frm.trigger('clear_pickup_fields');
+ }
+ if (frm.doc.pickup_from_type == 'Customer') {
+ frm.set_value("pickup_company", '');
+ frm.set_value("pickup_supplier", '');
+ }
+ if (frm.doc.pickup_from_type == 'Supplier') {
+ frm.set_value("pickup_customer", '');
+ frm.set_value("pickup_company", '');
+ }
+ },
+ delivery_to_type: function(frm) {
+ if (frm.doc.delivery_to_type == 'Company') {
+ frm.set_value("delivery_company", frappe.defaults.get_default('company'));
+ frm.set_value("delivery_customer", '');
+ frm.set_value("delivery_supplier", '');
+ } else {
+ frm.trigger('clear_delivery_fields');
+ }
+ if (frm.doc.delivery_to_type == 'Customer') {
+ frm.set_value("delivery_company", '');
+ frm.set_value("delivery_supplier", '');
+ }
+ if (frm.doc.delivery_to_type == 'Supplier') {
+ frm.set_value("delivery_customer", '');
+ frm.set_value("delivery_company", '');
+ frm.toggle_display("shipment_delivery_note", false);
+ } else {
+ frm.toggle_display("shipment_delivery_note", true);
+ }
+ },
+ delivery_address_name: function(frm) {
+ if (frm.doc.delivery_to_type == 'Company') {
+ erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true);
+ } else {
+ erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false);
+ }
+ },
+ pickup_address_name: function(frm) {
+ if (frm.doc.pickup_from_type == 'Company') {
+ erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true);
+ } else {
+ erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false);
+ }
+ },
+ get_contact_display: function(frm, contact_name, contact_type) {
+ frappe.call({
+ method: "frappe.contacts.doctype.contact.contact.get_contact_details",
+ args: { contact: contact_name },
+ callback: function(r) {
+ if (r.message) {
+ if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) {
+ if (contact_type == 'Delivery') {
+ frm.set_value('delivery_contact_name', '');
+ frm.set_value('delivery_contact', '');
+ } else {
+ frm.set_value('pickup_contact_name', '');
+ frm.set_value('pickup_contact', '');
+ }
+ frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.")
+ + "</br>" + __("Please set Email/Phone for the contact")
+ + ` <a href='/app/contact/${contact_name}'>${contact_name}</a>`);
+ }
+ let contact_display = r.message.contact_display;
+ if (r.message.contact_email) {
+ contact_display += '<br>' + r.message.contact_email;
+ }
+ if (r.message.contact_phone) {
+ contact_display += '<br>' + r.message.contact_phone;
+ }
+ if (r.message.contact_mobile && !r.message.contact_phone) {
+ contact_display += '<br>' + r.message.contact_mobile;
+ }
+ if (contact_type == 'Delivery') {
+ frm.set_value('delivery_contact', contact_display);
+ if (r.message.contact_email) {
+ frm.set_value('delivery_contact_email', r.message.contact_email);
+ }
+ } else {
+ frm.set_value('pickup_contact', contact_display);
+ if (r.message.contact_email) {
+ frm.set_value('pickup_contact_email', r.message.contact_email);
+ }
+ }
+ }
+ }
+ });
+ },
+ delivery_contact_name: function(frm) {
+ if (frm.doc.delivery_contact_name) {
+ frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, 'Delivery');
+ }
+ },
+ pickup_contact_name: function(frm) {
+ if (frm.doc.pickup_contact_name) {
+ frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup');
+ }
+ },
+ pickup_contact_person: function(frm) {
+ if (frm.doc.pickup_contact_person) {
+ frappe.call({
+ method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
+ args: { user: frm.doc.pickup_contact_person },
+ callback: function({ message }) {
+ const r = message;
+ let contact_display = `${r.first_name} ${r.last_name}`;
+ if (r.email) {
+ contact_display += `<br>${ r.email }`;
+ frm.set_value('pickup_contact_email', r.email);
+ }
+ if (r.phone) {
+ contact_display += `<br>${ r.phone }`;
+ }
+ if (r.mobile_no && !r.phone) {
+ contact_display += `<br>${ r.mobile_no }`;
+ }
+ frm.set_value('pickup_contact', contact_display);
+ }
+ });
+ } else {
+ if (frm.doc.pickup_from_type === 'Company') {
+ frappe.call({
+ method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
+ args: { user: frappe.session.user },
+ callback: function({ message }) {
+ const r = message;
+ let contact_display = `${r.first_name} ${r.last_name}`;
+ if (r.email) {
+ contact_display += `<br>${ r.email }`;
+ frm.set_value('pickup_contact_email', r.email);
+ }
+ if (r.phone) {
+ contact_display += `<br>${ r.phone }`;
+ }
+ if (r.mobile_no && !r.phone) {
+ contact_display += `<br>${ r.mobile_no }`;
+ }
+ frm.set_value('pickup_contact', contact_display);
+ }
+ });
+ }
+ }
+ },
+ set_company_contact: function(frm, delivery_type) {
+ frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => {
+ if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
+ if (delivery_type == 'Delivery') {
+ frm.set_value('delivery_company', '');
+ frm.set_value('delivery_contact', '');
+ } else {
+ frm.set_value('pickup_company', '');
+ frm.set_value('pickup_contact', '');
+ }
+ frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "</br>"
+ + __("Please first set Last Name, Email and Phone for the user")
+ + ` <a href="/app/user/${frappe.session.user}">${frappe.session.user}</a>`);
+ }
+ let contact_display = r.full_name;
+ if (r.email) {
+ contact_display += '<br>' + r.email;
+ }
+ if (r.phone) {
+ contact_display += '<br>' + r.phone;
+ }
+ if (r.mobile_no && !r.phone) {
+ contact_display += '<br>' + r.mobile_no;
+ }
+ if (delivery_type == 'Delivery') {
+ frm.set_value('delivery_contact', contact_display);
+ if (r.email) {
+ frm.set_value('delivery_contact_email', r.email);
+ }
+ } else {
+ frm.set_value('pickup_contact', contact_display);
+ if (r.email) {
+ frm.set_value('pickup_contact_email', r.email);
+ }
+ }
+ });
+ frm.set_value('pickup_contact_person', frappe.session.user);
+ },
+ pickup_company: function(frm) {
+ if (frm.doc.pickup_from_type == 'Company' && frm.doc.pickup_company) {
+ frm.trigger('set_pickup_company_address');
+ frm.events.set_company_contact(frm, 'Pickup');
+ }
+ },
+ delivery_company: function(frm) {
+ if (frm.doc.delivery_to_type == 'Company' && frm.doc.delivery_company) {
+ frm.trigger('set_delivery_company_address');
+ frm.events.set_company_contact(frm, 'Delivery');
+ }
+ },
+ delivery_customer: function(frm) {
+ frm.trigger('clear_delivery_fields');
+ if (frm.doc.delivery_customer) {
+ frm.events.set_address_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
+ frm.events.set_contact_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
+ }
+ },
+ delivery_supplier: function(frm) {
+ frm.trigger('clear_delivery_fields');
+ if (frm.doc.delivery_supplier) {
+ frm.events.set_address_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
+ frm.events.set_contact_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
+ }
+ },
+ pickup_customer: function(frm) {
+ if (frm.doc.pickup_customer) {
+ frm.events.set_address_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
+ frm.events.set_contact_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
+ }
+ },
+ pickup_supplier: function(frm) {
+ if (frm.doc.pickup_supplier) {
+ frm.events.set_address_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
+ frm.events.set_contact_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
+ }
+ },
+ set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) {
+ frappe.call({
+ method: "erpnext.stock.doctype.shipment.shipment.get_address_name",
+ args: {
+ ref_doctype: ref_doctype,
+ docname: ref_docname
+ },
+ callback: function(r) {
+ if (r.message) {
+ if (delivery_type == 'Delivery') {
+ frm.set_value('delivery_address_name', r.message);
+ } else {
+ frm.set_value('pickup_address_name', r.message);
+ }
+ }
+ }
+ });
+ },
+ set_contact_name: function(frm, ref_doctype, ref_docname, delivery_type) {
+ frappe.call({
+ method: "erpnext.stock.doctype.shipment.shipment.get_contact_name",
+ args: {
+ ref_doctype: ref_doctype,
+ docname: ref_docname
+ },
+ callback: function(r) {
+ if (r.message) {
+ if (delivery_type == 'Delivery') {
+ frm.set_value('delivery_contact_name', r.message);
+ } else {
+ frm.set_value('pickup_contact_name', r.message);
+ }
+ }
+ }
+ });
+ },
+ add_template: function(frm) {
+ if (frm.doc.parcel_template) {
+ frappe.model.with_doc("Shipment Parcel Template", frm.doc.parcel_template, () => {
+ let parcel_template = frappe.model.get_doc("Shipment Parcel Template", frm.doc.parcel_template);
+ let row = frappe.model.add_child(frm.doc, "Shipment Parcel", "shipment_parcel");
+ row.length = parcel_template.length;
+ row.width = parcel_template.width;
+ row.height = parcel_template.height;
+ row.weight = parcel_template.weight;
+ frm.refresh_fields("shipment_parcel");
+ });
+ }
+ },
+ pickup_date: function(frm) {
+ if (frm.doc.pickup_date < frappe.datetime.get_today()) {
+ frappe.throw(__("Pickup Date cannot be before this day"));
+ }
+ if (frm.doc.pickup_date == frappe.datetime.get_today()) {
+ var pickup_time = frm.events.get_pickup_time(frm);
+ frm.set_value("pickup_from", pickup_time);
+ frm.trigger('set_pickup_to_time');
+ }
+ },
+ pickup_from: function(frm) {
+ var pickup_time = frm.events.get_pickup_time(frm);
+ if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) {
+ let current_hour = pickup_time.split(':')[0];
+ let current_min = pickup_time.split(':')[1];
+ let pickup_hour = frm.doc.pickup_from.split(':')[0];
+ let pickup_min = frm.doc.pickup_from.split(':')[1];
+ if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) {
+ frm.set_value("pickup_from", pickup_time);
+ frappe.throw(__("Pickup Time cannot be in the past"));
+ }
+ }
+ frm.trigger('set_pickup_to_time');
+ },
+ get_pickup_time: function() {
+ let current_hour = new Date().getHours();
+ let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'});
+ if (current_min < 30) {
+ current_min = '30';
+ } else {
+ current_min = '00';
+ current_hour = Number(current_hour)+1;
+ }
+ let pickup_time = current_hour +':'+ current_min;
+ return pickup_time;
+ },
+ set_pickup_to_time: function(frm) {
+ let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5;
+ let pickup_to_min = frm.doc.pickup_from.split(':')[1];
+ let pickup_to = pickup_to_hour +':'+ pickup_to_min;
+ frm.set_value("pickup_to", pickup_to);
+ },
+ clear_pickup_fields: function(frm) {
+ let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
+ for (let field of fields) {
+ frm.set_value(field, '');
+ }
+ },
+ clear_delivery_fields: function(frm) {
+ let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"];
+ for (let field of fields) {
+ frm.set_value(field, '');
+ }
+ },
+ remove_email_row: function(frm, table, fieldname) {
+ $.each(frm.doc[table] || [], function(i, detail) {
+ if (detail.email === fieldname) {
+ cur_frm.get_field(table).grid.grid_rows[i].remove();
+ }
+ });
+ }
+});
+
+frappe.ui.form.on('Shipment Delivery Note', {
+ delivery_note: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ if (row.delivery_note) {
+ let row_index = row.idx - 1;
+ if (validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) {
+ frappe.throw(__("You have entered a duplicate Delivery Note on Row") + ` ${row.idx}. ` + __("Please rectify and try again."));
+ }
+ }
+ },
+ grand_total: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+ if (row.grand_total) {
+ var value_of_goods = parseFloat(frm.doc.value_of_goods)+parseFloat(row.grand_total);
+ frm.set_value("value_of_goods", Math.round(value_of_goods));
+ frm.refresh_fields("value_of_goods");
+ }
+ },
+});
+
+var validate_duplicate = function(frm, table, fieldname, index) {
+ return (
+ table === 'shipment_delivery_note'
+ ? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i))
+ : frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i))
+ );
+};
diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json
new file mode 100644
index 0000000..37a9cc6
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.json
@@ -0,0 +1,471 @@
+{
+ "actions": [],
+ "autoname": "SHIPMENT-.#####",
+ "creation": "2020-07-09 10:58:52.508703",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "heading_pickup_from",
+ "pickup_from_type",
+ "pickup_company",
+ "pickup_customer",
+ "pickup_supplier",
+ "pickup",
+ "pickup_address_name",
+ "pickup_address",
+ "pickup_contact_person",
+ "pickup_contact_name",
+ "pickup_contact_email",
+ "pickup_contact",
+ "column_break_2",
+ "heading_delivery_to",
+ "delivery_to_type",
+ "delivery_company",
+ "delivery_customer",
+ "delivery_supplier",
+ "delivery_to",
+ "delivery_address_name",
+ "delivery_address",
+ "delivery_contact_name",
+ "delivery_contact_email",
+ "delivery_contact",
+ "parcels_section",
+ "shipment_parcel",
+ "parcel_template",
+ "add_template",
+ "column_break_28",
+ "shipment_delivery_note",
+ "shipment_details_section",
+ "pallets",
+ "value_of_goods",
+ "pickup_date",
+ "pickup_from",
+ "pickup_to",
+ "column_break_36",
+ "shipment_type",
+ "pickup_type",
+ "incoterm",
+ "description_of_content",
+ "section_break_40",
+ "shipment_information_section",
+ "service_provider",
+ "shipment_id",
+ "shipment_amount",
+ "status",
+ "tracking_url",
+ "column_break_55",
+ "carrier",
+ "carrier_service",
+ "awb_number",
+ "tracking_status",
+ "tracking_status_info",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "heading_pickup_from",
+ "fieldtype": "Heading",
+ "label": "Pickup from"
+ },
+ {
+ "default": "Company",
+ "fieldname": "pickup_from_type",
+ "fieldtype": "Select",
+ "label": "Pickup from",
+ "options": "Company\nCustomer\nSupplier"
+ },
+ {
+ "depends_on": "eval:doc.pickup_from_type == 'Company'",
+ "fieldname": "pickup_company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company"
+ },
+ {
+ "depends_on": "eval:doc.pickup_from_type == 'Customer'",
+ "fieldname": "pickup_customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer"
+ },
+ {
+ "depends_on": "eval:doc.pickup_from_type == 'Supplier'",
+ "fieldname": "pickup_supplier",
+ "fieldtype": "Link",
+ "label": "Supplier",
+ "options": "Supplier"
+ },
+ {
+ "fieldname": "pickup",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Pickup From",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"",
+ "fieldname": "pickup_address_name",
+ "fieldtype": "Link",
+ "label": "Address",
+ "options": "Address",
+ "reqd": 1
+ },
+ {
+ "fieldname": "pickup_address",
+ "fieldtype": "Small Text",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type !== \"Company\"",
+ "fieldname": "pickup_contact_name",
+ "fieldtype": "Link",
+ "label": "Contact",
+ "mandatory_depends_on": "eval: doc.pickup_from_type !== 'Company'",
+ "options": "Contact"
+ },
+ {
+ "fieldname": "pickup_contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "read_only": 1
+ },
+ {
+ "fieldname": "pickup_contact",
+ "fieldtype": "Small Text",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "heading_delivery_to",
+ "fieldtype": "Heading",
+ "label": "Delivery to"
+ },
+ {
+ "default": "Customer",
+ "fieldname": "delivery_to_type",
+ "fieldtype": "Select",
+ "label": "Delivery to",
+ "options": "Company\nCustomer\nSupplier"
+ },
+ {
+ "depends_on": "eval:doc.delivery_to_type == 'Company'",
+ "fieldname": "delivery_company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company"
+ },
+ {
+ "depends_on": "eval:doc.delivery_to_type == 'Customer'",
+ "fieldname": "delivery_customer",
+ "fieldtype": "Link",
+ "label": "Customer",
+ "options": "Customer"
+ },
+ {
+ "depends_on": "eval:doc.delivery_to_type == 'Supplier'",
+ "fieldname": "delivery_supplier",
+ "fieldtype": "Link",
+ "label": "Supplier",
+ "options": "Supplier"
+ },
+ {
+ "fieldname": "delivery_to",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Delivery To",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
+ "fieldname": "delivery_address_name",
+ "fieldtype": "Link",
+ "label": "Address",
+ "options": "Address",
+ "reqd": 1
+ },
+ {
+ "fieldname": "delivery_address",
+ "fieldtype": "Small Text",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"",
+ "fieldname": "delivery_contact_name",
+ "fieldtype": "Link",
+ "label": "Contact",
+ "mandatory_depends_on": "eval: doc.delivery_from_type !== 'Company'",
+ "options": "Contact"
+ },
+ {
+ "fieldname": "delivery_contact_email",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Contact Email",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.delivery_contact_name",
+ "fieldname": "delivery_contact",
+ "fieldtype": "Small Text",
+ "read_only": 1
+ },
+ {
+ "fieldname": "parcels_section",
+ "fieldtype": "Section Break",
+ "label": "Parcels"
+ },
+ {
+ "fieldname": "shipment_parcel",
+ "fieldtype": "Table",
+ "label": "Shipment Parcel",
+ "options": "Shipment Parcel"
+ },
+ {
+ "fieldname": "parcel_template",
+ "fieldtype": "Link",
+ "label": "Parcel Template",
+ "options": "Shipment Parcel Template"
+ },
+ {
+ "depends_on": "eval:doc.docstatus !== 1\n",
+ "fieldname": "add_template",
+ "fieldtype": "Button",
+ "label": "Add Template"
+ },
+ {
+ "fieldname": "column_break_28",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "shipment_details_section",
+ "fieldtype": "Section Break",
+ "label": "Shipment details"
+ },
+ {
+ "default": "No",
+ "fieldname": "pallets",
+ "fieldtype": "Select",
+ "label": "Pallets",
+ "options": "No\nYes"
+ },
+ {
+ "fieldname": "value_of_goods",
+ "fieldtype": "Currency",
+ "label": "Value of Goods",
+ "precision": "2",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "fieldname": "pickup_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Pickup Date",
+ "reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "09:00",
+ "fieldname": "pickup_from",
+ "fieldtype": "Time",
+ "label": "Pickup from"
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "17:00",
+ "fieldname": "pickup_to",
+ "fieldtype": "Time",
+ "label": "Pickup to"
+ },
+ {
+ "fieldname": "column_break_36",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Goods",
+ "fieldname": "shipment_type",
+ "fieldtype": "Select",
+ "label": "Shipment Type",
+ "options": "Goods\nDocuments"
+ },
+ {
+ "default": "Pickup",
+ "fieldname": "pickup_type",
+ "fieldtype": "Select",
+ "label": "Pickup Type",
+ "options": "Pickup\nSelf delivery"
+ },
+ {
+ "fieldname": "description_of_content",
+ "fieldtype": "Small Text",
+ "label": "Description of Content",
+ "reqd": 1
+ },
+ {
+ "fieldname": "section_break_40",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "shipment_information_section",
+ "fieldtype": "Section Break",
+ "label": "Shipment Information"
+ },
+ {
+ "fieldname": "service_provider",
+ "fieldtype": "Data",
+ "label": "Service Provider",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "shipment_id",
+ "fieldtype": "Data",
+ "label": "Shipment ID",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "shipment_amount",
+ "fieldtype": "Currency",
+ "label": "Shipment Amount",
+ "no_copy": 1,
+ "precision": "2",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "no_copy": 1,
+ "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "tracking_url",
+ "fieldtype": "Small Text",
+ "hidden": 1,
+ "label": "Tracking URL",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "carrier",
+ "fieldtype": "Data",
+ "label": "Carrier",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "carrier_service",
+ "fieldtype": "Data",
+ "label": "Carrier Service",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "awb_number",
+ "fieldtype": "Data",
+ "label": "AWB Number",
+ "no_copy": 1,
+ "print_hide": 1
+ },
+ {
+ "fieldname": "tracking_status",
+ "fieldtype": "Select",
+ "label": "Tracking Status",
+ "no_copy": 1,
+ "options": "\nIn Progress\nDelivered\nReturned\nLost",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "tracking_status_info",
+ "fieldtype": "Data",
+ "label": "Tracking Status Info",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Shipment",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_55",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "incoterm",
+ "fieldtype": "Select",
+ "label": "Incoterm",
+ "options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)"
+ },
+ {
+ "fieldname": "shipment_delivery_note",
+ "fieldtype": "Table",
+ "label": "Shipment Delivery Note",
+ "options": "Shipment Delivery Note"
+ },
+ {
+ "depends_on": "eval:doc.pickup_from_type === 'Company'",
+ "fieldname": "pickup_contact_person",
+ "fieldtype": "Link",
+ "label": "Pickup Contact Person",
+ "mandatory_depends_on": "eval:doc.pickup_from_type === 'Company'",
+ "options": "User"
+ }
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-12-02 15:43:44.607039",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Stock Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
new file mode 100644
index 0000000..de0c243
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -0,0 +1,63 @@
+# -*- 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 _
+from frappe.utils import flt
+from frappe.model.document import Document
+from erpnext.accounts.party import get_party_shipping_address
+from frappe.contacts.doctype.contact.contact import get_default_contact
+
+class Shipment(Document):
+ def validate(self):
+ self.validate_weight()
+ self.set_value_of_goods()
+ if self.docstatus == 0:
+ self.status = 'Draft'
+
+ def on_submit(self):
+ if not self.shipment_parcel:
+ frappe.throw(_('Please enter Shipment Parcel information'))
+ if self.value_of_goods == 0:
+ frappe.throw(_('Value of goods cannot be 0'))
+ self.status = 'Submitted'
+
+ def on_cancel(self):
+ self.status = 'Cancelled'
+
+ def validate_weight(self):
+ for parcel in self.shipment_parcel:
+ if flt(parcel.weight) <= 0:
+ frappe.throw(_('Parcel weight cannot be 0'))
+
+ def set_value_of_goods(self):
+ value_of_goods = 0
+ for entry in self.get("shipment_delivery_note"):
+ value_of_goods += flt(entry.get("grand_total"))
+ self.value_of_goods = value_of_goods if value_of_goods else self.value_of_goods
+
+@frappe.whitelist()
+def get_address_name(ref_doctype, docname):
+ # Return address name
+ return get_party_shipping_address(ref_doctype, docname)
+
+@frappe.whitelist()
+def get_contact_name(ref_doctype, docname):
+ # Return address name
+ return get_default_contact(ref_doctype, docname)
+
+@frappe.whitelist()
+def get_company_contact(user):
+ contact = frappe.db.get_value('User', user, [
+ 'first_name',
+ 'last_name',
+ 'email',
+ 'phone',
+ 'mobile_no',
+ 'gender',
+ ], as_dict=1)
+ if not contact.phone:
+ contact.phone = contact.mobile_no
+ return contact
diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js
new file mode 100644
index 0000000..52b052c
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/shipment_list.js
@@ -0,0 +1,8 @@
+frappe.listview_settings['Shipment'] = {
+ add_fields: ["status"],
+ get_indicator: function(doc) {
+ if (doc.status=='Booked') {
+ return [__("Booked"), "green"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py
new file mode 100644
index 0000000..e1fa207
--- /dev/null
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+from datetime import date, timedelta
+
+import frappe
+import unittest
+from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment
+
+class TestShipment(unittest.TestCase):
+ def test_shipment_from_delivery_note(self):
+ delivery_note = create_test_delivery_note()
+ delivery_note.submit()
+ shipment = create_test_shipment([ delivery_note ])
+ shipment.submit()
+ second_shipment = make_shipment(delivery_note.name)
+ self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total)
+ self.assertEqual(len(second_shipment.shipment_delivery_note), 1)
+ self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name)
+
+def create_test_delivery_note():
+ company = get_shipment_company()
+ customer = get_shipment_customer()
+ item = get_shipment_item(company.name)
+ posting_date = date.today() + timedelta(days=1)
+
+ create_material_receipt(item, company.name)
+ delivery_note = frappe.new_doc("Delivery Note")
+ delivery_note.company = company.name
+ delivery_note.posting_date = posting_date.strftime("%Y-%m-%d")
+ delivery_note.posting_time = '10:00'
+ delivery_note.customer = customer.name
+ delivery_note.append('items',
+ {
+ "item_code": item.name,
+ "item_name": item.item_name,
+ "description": 'Test delivery note for shipment',
+ "qty": 5,
+ "uom": 'Nos',
+ "warehouse": 'Stores - SC',
+ "rate": item.standard_rate,
+ "cost_center": 'Main - SC'
+ }
+ )
+ delivery_note.insert()
+ frappe.db.commit()
+ return delivery_note
+
+
+def create_test_shipment(delivery_notes = None):
+ company = get_shipment_company()
+ company_address = get_shipment_company_address(company.name)
+ customer = get_shipment_customer()
+ customer_address = get_shipment_customer_address(customer.name)
+ customer_contact = get_shipment_customer_contact(customer.name)
+ posting_date = date.today() + timedelta(days=5)
+
+ shipment = frappe.new_doc("Shipment")
+ shipment.pickup_from_type = 'Company'
+ shipment.pickup_company = company.name
+ shipment.pickup_address_name = company_address.name
+ shipment.delivery_to_type = 'Customer'
+ shipment.delivery_customer = customer.name
+ shipment.delivery_address_name = customer_address.name
+ shipment.delivery_contact_name = customer_contact.name
+ shipment.pallets = 'No'
+ shipment.shipment_type = 'Goods'
+ shipment.value_of_goods = 1000
+ shipment.pickup_type = 'Pickup'
+ shipment.pickup_date = posting_date.strftime("%Y-%m-%d")
+ shipment.pickup_from = '09:00'
+ shipment.pickup_to = '17:00'
+ shipment.description_of_content = 'unit test entry'
+ for delivery_note in delivery_notes:
+ shipment.append('shipment_delivery_note',
+ {
+ "delivery_note": delivery_note.name
+ }
+ )
+ shipment.append('shipment_parcel',
+ {
+ "length": 5,
+ "width": 5,
+ "height": 5,
+ "weight": 5,
+ "count": 5
+ }
+ )
+ shipment.insert()
+ frappe.db.commit()
+ return shipment
+
+
+def get_shipment_customer_contact(customer_name):
+ contact_fname = 'Customer Shipment'
+ contact_lname = 'Testing'
+ customer_name = contact_fname + ' ' + contact_lname
+ contacts = frappe.get_all("Contact", fields=["name"], filters = {"name": customer_name})
+ if len(contacts):
+ return contacts[0]
+ else:
+ return create_customer_contact(contact_fname, contact_lname)
+
+
+def get_shipment_customer_address(customer_name):
+ address_title = customer_name + ' address 123'
+ customer_address = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+ if len(customer_address):
+ return customer_address[0]
+ else:
+ return create_shipment_address(address_title, customer_name, 81929)
+
+def get_shipment_customer():
+ customer_name = 'Shipment Customer'
+ customer = frappe.get_all("Customer", fields=["name"], filters = {"name": customer_name})
+ if len(customer):
+ return customer[0]
+ else:
+ return create_shipment_customer(customer_name)
+
+def get_shipment_company_address(company_name):
+ address_title = company_name + ' address 123'
+ addresses = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+ if len(addresses):
+ return addresses[0]
+ else:
+ return create_shipment_address(address_title, company_name, 80331)
+
+def get_shipment_company():
+ company_name = 'Shipment Company'
+ abbr = 'SC'
+ companies = frappe.get_all("Company", fields=["name"], filters = {"company_name": company_name})
+ if len(companies):
+ return companies[0]
+ else:
+ return create_shipment_company(company_name, abbr)
+
+def get_shipment_item(company_name):
+ item_name = 'Testing Shipment item'
+ items = frappe.get_all("Item",
+ fields=["name", "item_name", "item_code", "standard_rate"],
+ filters = {"item_name": item_name}
+ )
+ if len(items):
+ return items[0]
+ else:
+ return create_shipment_item(item_name, company_name)
+
+def create_shipment_address(address_title, company_name, postal_code):
+ address = frappe.new_doc("Address")
+ address.address_title = address_title
+ address.address_type = 'Shipping'
+ address.address_line1 = company_name + ' address line 1'
+ address.city = 'Random City'
+ address.postal_code = postal_code
+ address.country = 'Germany'
+ address.insert()
+ return address
+
+
+def create_customer_contact(fname, lname):
+ customer = frappe.new_doc("Contact")
+ customer.customer_name = fname + ' ' + lname
+ customer.first_name = fname
+ customer.last_name = lname
+ customer.is_primary_contact = 1
+ customer.is_billing_contact = 1
+ customer.append('email_ids',
+ {
+ 'email_id': 'randomme@email.com',
+ 'is_primary': 1
+ }
+ )
+ customer.append('phone_nos',
+ {
+ 'phone': '123123123',
+ 'is_primary_phone': 1,
+ 'is_primary_mobile_no': 1
+ }
+ )
+ customer.status = 'Passive'
+ customer.insert()
+ return customer
+
+
+def create_shipment_company(company_name, abbr):
+ company = frappe.new_doc("Company")
+ company.company_name = company_name
+ company.abbr = abbr
+ company.default_currency = 'EUR'
+ company.country = 'Germany'
+ company.insert()
+ return company
+
+def create_shipment_customer(customer_name):
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = customer_name
+ customer.customer_type = 'Company'
+ customer.customer_group = 'All Customer Groups'
+ customer.territory = 'All Territories'
+ customer.gst_category = 'Unregistered'
+ customer.insert()
+ return customer
+
+def create_material_receipt(item, company):
+ posting_date = date.today()
+ stock = frappe.new_doc("Stock Entry")
+ stock.company = company
+ stock.stock_entry_type = 'Material Receipt'
+ stock.posting_date = posting_date.strftime("%Y-%m-%d")
+ stock.append('items',
+ {
+ "t_warehouse": 'Stores - SC',
+ "item_code": item.name,
+ "qty": 5,
+ "uom": 'Nos',
+ "basic_rate": item.standard_rate,
+ "cost_center": 'Main - SC'
+ }
+ )
+ stock.insert()
+ stock.submit()
+
+
+def create_shipment_item(item_name, company_name):
+ item = frappe.new_doc("Item")
+ item.item_name = item_name
+ item.item_code = item_name
+ item.item_group = 'All Item Groups'
+ item.stock_uom = 'Nos'
+ item.standard_rate = 50
+ item.append('item_defaults',
+ {
+ "company": company_name,
+ "default_warehouse": 'Stores - SC'
+ }
+ )
+ item.insert()
+ return item
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_delivery_note/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_delivery_note/__init__.py
diff --git a/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json
new file mode 100644
index 0000000..8625913
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "creation": "2020-07-09 11:52:57.939021",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "delivery_note",
+ "grand_total"
+ ],
+ "fields": [
+ {
+ "fieldname": "delivery_note",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Delivery Note",
+ "options": "Delivery Note",
+ "reqd": 1
+ },
+ {
+ "fieldname": "grand_total",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Value",
+ "read_only": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-12-02 15:44:34.028703",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Delivery Note",
+ "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/stock/doctype/shipment_delivery_note/shipment_delivery_note.py b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py
new file mode 100644
index 0000000..4342151
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentDeliveryNote(Document):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_parcel/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_parcel/__init__.py
diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
new file mode 100644
index 0000000..6943edc
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
@@ -0,0 +1,65 @@
+{
+ "actions": [],
+ "creation": "2020-07-09 11:28:48.887737",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "length",
+ "width",
+ "height",
+ "weight",
+ "count"
+ ],
+ "fields": [
+ {
+ "fieldname": "length",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Length (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "width",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Width (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "height",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Height (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "weight",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Weight (kg)",
+ "precision": "1",
+ "reqd": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "count",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Count",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-07-09 12:54:14.847170",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Parcel",
+ "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/stock/doctype/shipment_parcel/shipment_parcel.py b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
new file mode 100644
index 0000000..53e6ed5
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentParcel(Document):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/stock/doctype/shipment_parcel_template/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/stock/doctype/shipment_parcel_template/__init__.py
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js
new file mode 100644
index 0000000..785a3b3
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Shipment Parcel Template', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json
new file mode 100644
index 0000000..4735d9f
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json
@@ -0,0 +1,78 @@
+{
+ "actions": [],
+ "autoname": "field:parcel_template_name",
+ "creation": "2020-07-09 11:43:43.470339",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "parcel_template_name",
+ "length",
+ "width",
+ "height",
+ "weight"
+ ],
+ "fields": [
+ {
+ "fieldname": "length",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Length (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "width",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Width (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "height",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Height (cm)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "weight",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Weight (kg)",
+ "precision": "1",
+ "reqd": 1
+ },
+ {
+ "fieldname": "parcel_template_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Parcel Template Name",
+ "reqd": 1,
+ "unique": 1
+ }
+ ],
+ "links": [],
+ "modified": "2020-09-28 12:51:00.320421",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Shipment Parcel Template",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py
new file mode 100644
index 0000000..2a8d58d
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class ShipmentParcelTemplate(Document):
+ pass
diff --git a/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py
new file mode 100644
index 0000000..6e2caa7
--- /dev/null
+++ b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.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 TestShipmentParcelTemplate(unittest.TestCase):
+ pass
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 9121758..27fcbb7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -841,6 +841,10 @@
}
},
+ fg_completed_qty: function() {
+ this.get_items();
+ },
+
get_items: function() {
var me = this;
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
@@ -850,6 +854,7 @@
// if work order / bom is mentioned, get items
return this.frm.call({
doc: me.frm.doc,
+ freeze: true,
method: "get_items",
callback: function(r) {
if(!r.exc) refresh_field("items");
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index e3159b9..32d7e6e 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -120,6 +120,7 @@
self.update_transferred_qty()
self.update_quality_inspection()
self.delete_auto_created_batches()
+ self.delete_linked_stock_entry()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('Not Started')
@@ -152,6 +153,12 @@
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
.format(self.job_card))
+ def delete_linked_stock_entry(self):
+ if self.purpose == "Send to Warehouse":
+ for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
+ "outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
+ frappe.delete_doc("Stock Entry", d.name)
+
def set_transfer_qty(self):
for item in self.get("items"):
if not flt(item.qty):
@@ -1033,26 +1040,22 @@
wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item',
filters={'parent': self.work_order},
- fields=["item_code", "required_qty", "consumed_qty"]
+ fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"]
)
+ work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
for item in wo_items:
- qty = item.required_qty
-
item_account_details = get_item_defaults(item.item_code, self.company)
# Take into account consumption if there are any.
- if self.purpose == 'Manufacture':
- req_qty_each = flt(item.required_qty / wo.qty)
- if (flt(item.consumed_qty) != 0):
- remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each)
- exhaust_qty = req_qty_each * wo.produced_qty
- if remaining_qty > exhaust_qty :
- if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
- qty =0
- else:
- qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
- else:
- qty = req_qty_each * flt(self.fg_completed_qty)
+
+ wo_item_qty = item.transferred_qty or item.required_qty
+
+ req_qty_each = (
+ (flt(wo_item_qty) - flt(item.consumed_qty)) /
+ (flt(work_order_qty) - flt(wo.produced_qty))
+ )
+
+ qty = req_qty_each * flt(self.fg_completed_qty)
if qty > 0:
self.add_to_stock_entry_detail({
@@ -1134,13 +1137,15 @@
else:
qty = req_qty_each * flt(self.fg_completed_qty)
-
elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse):
if (qty > req_qty):
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
+ if consumed_qty:
+ qty -= consumed_qty
+
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
qty = frappe.utils.ceil(qty)
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 067659f..a166657 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -217,7 +217,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2020-10-13 10:33:29.147682",
+ "modified": "2020-11-23 15:26:54.225608",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
@@ -235,5 +235,6 @@
],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 4c7828b..3b9608b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -55,7 +55,7 @@
""")
if sle:
- frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method"))
+ frappe.throw(_("Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"))
def validate_clean_description_html(self):
if int(self.clean_description_html or 0) \
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 1339d9b..ccd0100 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -164,7 +164,7 @@
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
- sle.item_code as name, sle.voucher_no
+ sle.item_code as name, sle.voucher_no, sle.stock_value
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
@@ -197,7 +197,7 @@
else:
qty_diff = flt(d.actual_qty)
- value_diff = flt(d.stock_value_difference)
+ value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
if d.posting_date < from_date:
qty_dict.opening_qty += qty_diff
diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json
new file mode 100644
index 0000000..3221dc4
--- /dev/null
+++ b/erpnext/stock/workspace/stock/stock.json
@@ -0,0 +1,721 @@
+{
+ "cards_label": "Masters & Reports",
+ "category": "Modules",
+ "charts": [
+ {
+ "chart_name": "Warehouse wise Stock Value"
+ }
+ ],
+ "creation": "2020-03-02 15:43:10.096528",
+ "developer_mode_only": 0,
+ "disable_user_customization": 0,
+ "docstatus": 0,
+ "doctype": "Workspace",
+ "extends_another_page": 0,
+ "hide_custom": 0,
+ "icon": "stock",
+ "idx": 0,
+ "is_standard": 1,
+ "label": "Stock",
+ "links": [
+ {
+ "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": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item Group",
+ "link_to": "Item Group",
+ "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": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Price List",
+ "link_to": "Price List",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item Price",
+ "link_to": "Item Price",
+ "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": "Pricing Rule",
+ "link_to": "Pricing Rule",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item Alternative",
+ "link_to": "Item Alternative",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item Manufacturer",
+ "link_to": "Item Manufacturer",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Customs Tariff Number",
+ "link_to": "Customs Tariff Number",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Transactions",
+ "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",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Entry",
+ "link_to": "Stock Entry",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item, Customer",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Delivery Note",
+ "link_to": "Delivery Note",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item, Supplier",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Purchase Receipt",
+ "link_to": "Purchase Receipt",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Pick List",
+ "link_to": "Pick List",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Delivery Trip",
+ "link_to": "Delivery Trip",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Reports",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Ledger",
+ "link_to": "Stock Ledger",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Balance",
+ "link_to": "Stock Balance",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Projected Qty",
+ "link_to": "Stock Projected Qty",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock Summary",
+ "link_to": "stock-balance",
+ "link_type": "Page",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Ageing",
+ "link_to": "Stock Ageing",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Item Price Stock",
+ "link_to": "Item Price Stock",
+ "link_type": "Report",
+ "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": "Stock Settings",
+ "link_to": "Stock Settings",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "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": "Unit of Measure (UOM)",
+ "link_to": "UOM",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item Variant Settings",
+ "link_to": "Item Variant Settings",
+ "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": "Item Attribute",
+ "link_to": "Item Attribute",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "UOM Conversion Factor",
+ "link_to": "UOM Conversion Factor",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Serial No and Batch",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Serial No",
+ "link_to": "Serial No",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Batch",
+ "link_to": "Batch",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Installation Note",
+ "link_to": "Installation Note",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Serial No",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Serial No Service Contract Expiry",
+ "link_to": "Serial No Service Contract Expiry",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Serial No",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Serial No Status",
+ "link_to": "Serial No Status",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Serial No",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Serial No Warranty Expiry",
+ "link_to": "Serial No Warranty Expiry",
+ "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": "Stock Reconciliation",
+ "link_to": "Stock Reconciliation",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Landed Cost Voucher",
+ "link_to": "Landed Cost Voucher",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Packing Slip",
+ "link_to": "Packing Slip",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Quality Inspection",
+ "link_to": "Quality Inspection",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Quality Inspection Template",
+ "link_to": "Quality Inspection Template",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Quick Stock Balance",
+ "link_to": "Quick Stock Balance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Key Reports",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Item Price",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Item-wise Price List Rate",
+ "link_to": "Item-wise Price List Rate",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Stock Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Analytics",
+ "link_to": "Stock Analytics",
+ "link_type": "Report",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock Qty vs Serial No Count",
+ "link_to": "Stock Qty vs Serial No Count",
+ "link_type": "Report",
+ "onboard": 1,
+ "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": "Purchase Receipt",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Purchase Receipt Trends",
+ "link_to": "Purchase Receipt Trends",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Sales Order",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Sales Order Analysis",
+ "link_to": "Sales Order Analysis",
+ "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": "Bin",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Item Shortage Report",
+ "link_to": "Item Shortage Report",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Batch",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Batch-Wise Balance History",
+ "link_to": "Batch-Wise Balance History",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Other Reports",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Material Request",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Requested Items To Be Transferred",
+ "link_to": "Requested Items To Be Transferred",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Stock Ledger Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Batch Item Expiry Status",
+ "link_to": "Batch Item Expiry Status",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Price List",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Item Prices",
+ "link_to": "Item Prices",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Itemwise Recommended Reorder Level",
+ "link_to": "Itemwise Recommended Reorder Level",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Item",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Item Variant Details",
+ "link_to": "Item Variant Details",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Purchase Order",
+ "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": "Purchase Order",
+ "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": "Stock Ledger Entry",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Stock and Account Value Comparison",
+ "link_to": "Stock and Account Value Comparison",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ }
+ ],
+ "modified": "2020-12-01 13:38:36.282890",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock",
+ "onboarding": "Stock",
+ "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"
+ },
+ {
+ "label": "Stock Entry",
+ "link_to": "Stock Entry",
+ "type": "DocType"
+ },
+ {
+ "color": "Yellow",
+ "format": "{} To Bill",
+ "label": "Purchase Receipt",
+ "link_to": "Purchase Receipt",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "Yellow",
+ "format": "{} To Bill",
+ "label": "Delivery Note",
+ "link_to": "Delivery Note",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Bill\"\n}",
+ "type": "DocType"
+ },
+ {
+ "label": "Stock Ledger",
+ "link_to": "Stock Ledger",
+ "type": "Report"
+ },
+ {
+ "label": "Stock Balance",
+ "link_to": "Stock Balance",
+ "type": "Report"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Stock",
+ "type": "Dashboard"
+ }
+ ],
+ "shortcuts_label": "Quick Access"
+}
\ No newline at end of file
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
deleted file mode 100644
index f676ce5..0000000
--- a/erpnext/support/desk_page/support/support.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Issues",
- "links": "[\n {\n \"description\": \"Support queries from customers.\",\n \"label\": \"Issue\",\n \"name\": \"Issue\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Type.\",\n \"label\": \"Issue Type\",\n \"name\": \"Issue Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Issue Priority.\",\n \"label\": \"Issue Priority\",\n \"name\": \"Issue Priority\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Maintenance",
- "links": "[\n {\n \"label\": \"Maintenance Schedule\",\n \"name\": \"Maintenance Schedule\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Maintenance Visit\",\n \"name\": \"Maintenance Visit\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Service Level Agreement",
- "links": "[\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Warranty",
- "links": "[\n {\n \"description\": \"Warranty Claim against Serial No.\",\n \"label\": \"Warranty Claim\",\n \"name\": \"Warranty Claim\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Single unit of an Item.\",\n \"label\": \"Serial No\",\n \"name\": \"Serial No\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Settings",
- "links": "[\n {\n \"label\": \"Support Settings\",\n \"name\": \"Support Settings\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Issue\"\n ],\n \"doctype\": \"Issue\",\n \"is_query_report\": true,\n \"label\": \"First Response Time for Issues\",\n \"name\": \"First Response Time for Issues\",\n \"type\": \"report\"\n }\n]"
- }
- ],
- "category": "Modules",
- "charts": [],
- "creation": "2020-03-02 15:48:23.224699",
- "developer_mode_only": 0,
- "disable_user_customization": 0,
- "docstatus": 0,
- "doctype": "Desk Page",
- "extends_another_page": 0,
- "hide_custom": 0,
- "icon": "support",
- "idx": 0,
- "is_standard": 1,
- "label": "Support",
- "modified": "2020-08-11 15:49:34.307341",
- "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/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 940b940..158416b 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -1,6 +1,13 @@
frappe.ui.form.on("Issue", {
onload: function(frm) {
frm.email_field = "raised_by";
+ frm.set_query("customer", function () {
+ return {
+ filters: {
+ "disabled": 0
+ }
+ };
+ });
frappe.db.get_value("Support Settings", {name: "Support Settings"},
["allow_resetting_service_level_agreement", "track_service_level_agreement"], (r) => {
@@ -21,14 +28,14 @@
},
callback: function (r) {
if (r && r.message) {
- frm.set_query('priority', function() {
+ frm.set_query("priority", function() {
return {
filters: {
"name": ["in", r.message.priority],
}
};
});
- frm.set_query('service_level_agreement', function() {
+ frm.set_query("service_level_agreement", function() {
return {
filters: {
"name": ["in", r.message.service_level_agreements],
@@ -45,9 +52,9 @@
if (frm.doc.status !== "Closed" && frm.doc.agreement_status === "Ongoing") {
if (frm.doc.service_level_agreement) {
frappe.call({
- 'method': 'frappe.client.get',
+ "method": "frappe.client.get",
args: {
- doctype: 'Service Level Agreement',
+ doctype: "Service Level Agreement",
name: frm.doc.service_level_agreement
},
callback: function(data) {
@@ -127,8 +134,8 @@
reset_sla.clear();
frappe.show_alert({
- indicator: 'green',
- message: __('Resetting Service Level Agreement.')
+ indicator: "green",
+ message: __("Resetting Service Level Agreement.")
});
frm.call("reset_service_level_agreement", {
@@ -145,24 +152,25 @@
reset_sla.show();
},
+
timeline_refresh: function(frm) {
// create button for "Help Article"
- if(frappe.model.can_create('Help Article')) {
+ if (frappe.model.can_create("Help Article")) {
// Removing Help Article button if exists to avoid multiple occurance
frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove();
$('<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs pull-right">'+
__('Help Article') + '</button>')
.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');
+ .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);
+ frappe.set_route("Form", "Help Article", doc.name);
});
}
- if (!frm.timeline.wrapper.find('.btn-split-issue').length) {
+ if (!frm.timeline.wrapper.find(".btn-split-issue").length) {
let split_issue = __("Split Issue")
$(`<button class="btn btn-xs btn-link btn-add-to-kb text-muted hidden-xs btn-split-issue pull-right" style="display:inline-block; margin-right: 15px">
${split_issue}
@@ -173,7 +181,7 @@
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() {
@@ -181,10 +189,7 @@
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: <a href="${result}/desk#Form/Issue/${r.message}">${r.message}</a>`)
+ frappe.msgprint(`New issue created: <a href="/app/issue/${r.message}">${r.message}</a>`)
frm.reload_doc();
dialog.hide();
});
@@ -226,7 +231,7 @@
function get_time_left(timestamp, agreement_status) {
const diff = moment(timestamp).diff(moment());
const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : "Failed";
- let indicator = (diff_display == 'Failed' && agreement_status != "Fulfilled") ? "red" : "green";
+ let indicator = (diff_display == "Failed" && agreement_status != "Fulfilled") ? "red" : "green";
return {"diff_display": diff_display, "indicator": indicator};
}
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 62b39cc..e4e7b25 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -207,7 +207,7 @@
"comment_type": "Info",
"reference_doctype": "Issue",
"reference_name": replicated_issue.name,
- "content": " - Split the Issue from <a href='#Form/Issue/{0}'>{1}</a>".format(self.name, frappe.bold(self.name)),
+ "content": " - Split the Issue from <a href='/app/Form/Issue/{0}'>{1}</a>".format(self.name, frappe.bold(self.name)),
}).insert(ignore_permissions=True)
return replicated_issue.name
diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json
new file mode 100644
index 0000000..01a8676
--- /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/config/__init__.py b/erpnext/telephony/__init__.py
similarity index 100%
rename from erpnext/config/__init__.py
rename to erpnext/telephony/__init__.py
diff --git a/erpnext/config/__init__.py b/erpnext/telephony/doctype/__init__.py
similarity index 100%
copy from erpnext/config/__init__.py
copy to erpnext/telephony/doctype/__init__.py
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/call_log/__init__.py
similarity index 100%
rename from erpnext/communication/doctype/call_log/__init__.py
rename to erpnext/telephony/doctype/call_log/__init__.py
diff --git a/erpnext/telephony/doctype/call_log/call_log.js b/erpnext/telephony/doctype/call_log/call_log.js
new file mode 100644
index 0000000..977f86d
--- /dev/null
+++ b/erpnext/telephony/doctype/call_log/call_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Call Log', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/communication/doctype/call_log/call_log.json b/erpnext/telephony/doctype/call_log/call_log.json
similarity index 96%
rename from erpnext/communication/doctype/call_log/call_log.json
rename to erpnext/telephony/doctype/call_log/call_log.json
index 31e79f1..55ad2ba 100644
--- a/erpnext/communication/doctype/call_log/call_log.json
+++ b/erpnext/telephony/doctype/call_log/call_log.json
@@ -137,12 +137,11 @@
"read_only": 1
}
],
- "in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-08-25 17:08:34.085731",
+ "modified": "2020-11-25 14:32:44.407815",
"modified_by": "Administrator",
- "module": "Communication",
+ "module": "Telephony",
"name": "Call Log",
"owner": "Administrator",
"permissions": [
diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
similarity index 100%
rename from erpnext/communication/doctype/call_log/call_log.py
rename to erpnext/telephony/doctype/call_log/call_log.py
diff --git a/erpnext/telephony/doctype/call_log/test_call_log.py b/erpnext/telephony/doctype/call_log/test_call_log.py
new file mode 100644
index 0000000..faa6304
--- /dev/null
+++ b/erpnext/telephony/doctype/call_log/test_call_log.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 TestCallLog(unittest.TestCase):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/incoming_call_handling_schedule/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/incoming_call_handling_schedule/__init__.py
diff --git a/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json
new file mode 100644
index 0000000..6d46b4e
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json
@@ -0,0 +1,60 @@
+{
+ "actions": [],
+ "creation": "2020-11-19 11:15:54.967710",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "day_of_week",
+ "from_time",
+ "to_time",
+ "agent_group"
+ ],
+ "fields": [
+ {
+ "fieldname": "day_of_week",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Day Of Week",
+ "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
+ "reqd": 1
+ },
+ {
+ "default": "9:00:00",
+ "fieldname": "from_time",
+ "fieldtype": "Time",
+ "in_list_view": 1,
+ "label": "From Time",
+ "reqd": 1
+ },
+ {
+ "default": "17:00:00",
+ "fieldname": "to_time",
+ "fieldtype": "Time",
+ "in_list_view": 1,
+ "label": "To Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "agent_group",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Agent Group",
+ "options": "Employee Group",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-19 11:15:54.967710",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Incoming Call Handling Schedule",
+ "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/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py
new file mode 100644
index 0000000..fcf2974
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class IncomingCallHandlingSchedule(Document):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/incoming_call_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/incoming_call_settings/__init__.py
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
new file mode 100644
index 0000000..1bcc846
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+function time_to_seconds(time_str) {
+ // Convert time string of format HH:MM:SS into seconds.
+ let seq = time_str.split(':');
+ seq = seq.map((n) => parseInt(n));
+ return (seq[0]*60*60) + (seq[1]*60) + seq[2];
+}
+
+function number_sort(array, ascending=true) {
+ let array_copy = [...array];
+ if (ascending) {
+ array_copy.sort((a, b) => a-b); // ascending order
+ } else {
+ array_copy.sort((a, b) => b-a); // descending order
+ }
+ return array_copy;
+}
+
+function groupby(items, key) {
+ // Group the list of items using the given key.
+ const obj = {};
+ items.forEach((item) => {
+ if (item[key] in obj) {
+ obj[item[key]].push(item);
+ } else {
+ obj[item[key]] = [item];
+ }
+ });
+ return obj;
+}
+
+function check_timeslot_overlap(ts1, ts2) {
+ /// Timeslot is a an array of length 2 ex: [from_time, to_time]
+ /// time in timeslot is an integer represents number of seconds.
+ if ((ts1[0] < ts2[0] && ts1[1] <= ts2[0]) || (ts1[0] >= ts2[1] && ts1[1] > ts2[1])) {
+ return false;
+ }
+ return true;
+}
+
+function validate_call_schedule(schedule) {
+ validate_call_schedule_timeslot(schedule);
+ validate_call_schedule_overlaps(schedule);
+}
+
+function validate_call_schedule_timeslot(schedule) {
+ // Make sure that to time slot is ahead of from time slot.
+ let errors = [];
+
+ for (let row in schedule) {
+ let record = schedule[row];
+ let from_time_in_secs = time_to_seconds(record.from_time);
+ let to_time_in_secs = time_to_seconds(record.to_time);
+ if (from_time_in_secs >= to_time_in_secs) {
+ errors.push(__('Call Schedule Row {0}: To time slot should always be ahead of From time slot.', [row]));
+ }
+ }
+
+ if (errors.length > 0) {
+ frappe.throw(errors.join("<br/>"));
+ }
+}
+
+function is_call_schedule_overlapped(day_schedule) {
+ // Check if any time slots are overlapped in a day schedule.
+ let timeslots = [];
+ day_schedule.forEach((record)=> {
+ timeslots.push([time_to_seconds(record.from_time), time_to_seconds(record.to_time)]);
+ });
+
+ if (timeslots.length < 2) {
+ return false;
+ }
+
+ timeslots = number_sort(timeslots);
+
+ // Sorted timeslots will be in ascending order if not overlapped.
+ for (let i=1; i < timeslots.length; i++) {
+ if (check_timeslot_overlap(timeslots[i-1], timeslots[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function validate_call_schedule_overlaps(schedule) {
+ let group_by_day = groupby(schedule, 'day_of_week');
+ for (const [day, day_schedule] of Object.entries(group_by_day)) {
+ if (is_call_schedule_overlapped(day_schedule)) {
+ frappe.throw(__('Please fix overlapping time slots for {0}', [day]));
+ }
+ }
+}
+
+frappe.ui.form.on('Incoming Call Settings', {
+ validate(frm) {
+ validate_call_schedule(frm.doc.call_handling_schedule);
+ }
+});
+
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json
new file mode 100644
index 0000000..3ffb3e4
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json
@@ -0,0 +1,82 @@
+{
+ "actions": [],
+ "autoname": "Prompt",
+ "creation": "2020-11-19 10:37:20.734245",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "call_routing",
+ "column_break_2",
+ "greeting_message",
+ "agent_busy_message",
+ "agent_unavailable_message",
+ "section_break_6",
+ "call_handling_schedule"
+ ],
+ "fields": [
+ {
+ "default": "Sequential",
+ "fieldname": "call_routing",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Call Routing",
+ "options": "Sequential\nSimultaneous"
+ },
+ {
+ "fieldname": "greeting_message",
+ "fieldtype": "Data",
+ "label": "Greeting Message"
+ },
+ {
+ "fieldname": "agent_busy_message",
+ "fieldtype": "Data",
+ "label": "Agent Busy Message"
+ },
+ {
+ "fieldname": "agent_unavailable_message",
+ "fieldtype": "Data",
+ "label": "Agent Unavailable Message"
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "call_handling_schedule",
+ "fieldtype": "Table",
+ "label": "Call Handling Schedule",
+ "options": "Incoming Call Handling Schedule",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-11-19 11:17:14.527862",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Incoming Call Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
new file mode 100644
index 0000000..2b2008a
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
@@ -0,0 +1,63 @@
+# -*- 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 datetime import datetime
+from typing import Tuple
+from frappe import _
+
+class IncomingCallSettings(Document):
+ def validate(self):
+ """List of validations
+ * Make sure that to time slot is ahead of from time slot in call schedule
+ * Make sure that no overlapping timeslots for a given day
+ """
+ self.validate_call_schedule_timeslot(self.call_handling_schedule)
+ self.validate_call_schedule_overlaps(self.call_handling_schedule)
+
+ def validate_call_schedule_timeslot(self, schedule: list):
+ """ Make sure that to time slot is ahead of from time slot.
+ """
+ errors = []
+ for record in schedule:
+ from_time = self.time_to_seconds(record.from_time)
+ to_time = self.time_to_seconds(record.to_time)
+ if from_time >= to_time:
+ errors.append(
+ _('Call Schedule Row {0}: To time slot should always be ahead of From time slot.').format(record.idx)
+ )
+
+ if errors:
+ frappe.throw('<br/>'.join(errors))
+
+ def validate_call_schedule_overlaps(self, schedule: list):
+ """Check if any time slots are overlapped in a day schedule.
+ """
+ week_days = set([each.day_of_week for each in schedule])
+
+ for day in week_days:
+ timeslots = [(record.from_time, record.to_time) for record in schedule if record.day_of_week==day]
+
+ # convert time in timeslot into an integer represents number of seconds
+ timeslots = sorted(map(lambda seq: tuple(map(self.time_to_seconds, seq)), timeslots))
+ if len(timeslots) < 2: continue
+
+ for i in range(1, len(timeslots)):
+ if self.check_timeslots_overlap(timeslots[i-1], timeslots[i]):
+ frappe.throw(_('Please fix overlapping time slots for {0}.').format(day))
+
+ @staticmethod
+ def check_timeslots_overlap(ts1: Tuple[int, int], ts2: Tuple[int, int]) -> bool:
+ if (ts1[0] < ts2[0] and ts1[1] <= ts2[0]) or (ts1[0] >= ts2[1] and ts1[1] > ts2[1]):
+ return False
+ return True
+
+ @staticmethod
+ def time_to_seconds(time: str) -> int:
+ """Convert time string of format HH:MM:SS into seconds
+ """
+ date_time = datetime.strptime(time, "%H:%M:%S")
+ return date_time - datetime(1900, 1, 1)
diff --git a/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py b/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py
new file mode 100644
index 0000000..c058c11
--- /dev/null
+++ b/erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.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 TestIncomingCallSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/communication/doctype/call_log/__init__.py b/erpnext/telephony/doctype/voice_call_settings/__init__.py
similarity index 100%
copy from erpnext/communication/doctype/call_log/__init__.py
copy to erpnext/telephony/doctype/voice_call_settings/__init__.py
diff --git a/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
new file mode 100644
index 0000000..85d6add
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.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 TestVoiceCallSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
new file mode 100644
index 0000000..4a61b61
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Voice Call Settings', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
new file mode 100644
index 0000000..25e55a2
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
@@ -0,0 +1,124 @@
+{
+ "actions": [],
+ "autoname": "field:user",
+ "creation": "2020-12-08 16:52:40.590146",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "user",
+ "call_receiving_device",
+ "column_break_3",
+ "greeting_message",
+ "agent_busy_message",
+ "agent_unavailable_message"
+ ],
+ "fields": [
+ {
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "User",
+ "options": "User",
+ "permlevel": 1,
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "greeting_message",
+ "fieldtype": "Data",
+ "label": "Greeting Message"
+ },
+ {
+ "fieldname": "agent_busy_message",
+ "fieldtype": "Data",
+ "label": "Agent Busy Message"
+ },
+ {
+ "fieldname": "agent_unavailable_message",
+ "fieldtype": "Data",
+ "label": "Agent Unavailable Message"
+ },
+ {
+ "default": "Computer",
+ "fieldname": "call_receiving_device",
+ "fieldtype": "Select",
+ "label": "Call Receiving Device",
+ "options": "Computer\nPhone"
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-14 18:49:34.600194",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Voice Call Settings",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "All",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "All",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py
new file mode 100644
index 0000000..ad3bbf1
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class VoiceCallSettings(Document):
+ pass
diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html
index 6171b61..c7f0d06 100644
--- a/erpnext/templates/includes/footer/footer_extension.html
+++ b/erpnext/templates/includes/footer/footer_extension.html
@@ -1,12 +1,12 @@
{% if not hide_footer_signup %}
<div class="input-group">
- <input type="text" class="form-control border-secondary"
+ <input type="text" class="form-control"
id="footer-subscribe-email"
placeholder="{{ _('Your email address...') }}"
aria-label="{{ _('Your email address...') }}"
aria-describedby="footer-subscribe-button">
<div class="input-group-append">
- <button class="btn btn-sm btn-outline-secondary"
+ <button class="btn btn-sm btn-default"
type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
</div>
</div>
diff --git a/erpnext/templates/print_formats/includes/taxes.html b/erpnext/templates/print_formats/includes/taxes.html
index 334ac78..1935542 100644
--- a/erpnext/templates/print_formats/includes/taxes.html
+++ b/erpnext/templates/print_formats/includes/taxes.html
@@ -20,10 +20,10 @@
{%- if (charge.tax_amount or print_settings.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<div class="row">
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
- <label>{{ charge.get_formatted("description") }}</label></div>
+ <label>{{ charge.get_formatted("description") }}</label>
+ </div>
<div class="col-xs-7 text-right">
- {{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
- table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
+ {{ charge.get_formatted('tax_amount', doc) }}
</div>
</div>
{%- endif -%}
diff --git a/erpnext/utilities/bot.py b/erpnext/utilities/bot.py
index 0e5e95d..b2e74da 100644
--- a/erpnext/utilities/bot.py
+++ b/erpnext/utilities/bot.py
@@ -26,12 +26,12 @@
for warehouse in warehouses:
qty = frappe.db.get_value("Bin", {'item_code': item[0], 'warehouse': warehouse.name}, 'actual_qty')
if qty:
- out.append(_('{0} units of [{1}](#Form/Item/{1}) found in [{2}](#Form/Warehouse/{2})').format(qty,
+ out.append(_('{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})').format(qty,
item[0], warehouse.name))
found = True
if not found:
- out.append(_('[{0}](#Form/Item/{0}) is out of stock').format(item[0]))
+ out.append(_('[{0}](/app/Form/Item/{0}) is out of stock').format(item[0]))
return "\n\n".join(out)
diff --git a/erpnext/utilities/desk_page/utilities/utilities.json b/erpnext/utilities/desk_page/utilities/utilities.json
deleted file mode 100644
index 591eab5..0000000
--- a/erpnext/utilities/desk_page/utilities/utilities.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "cards": [
- {
- "hidden": 0,
- "label": "Video",
- "links": "[\n {\n \"description\": \"Video\",\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Video settings\",\n \"label\": \"Video Settings\",\n \"name\": \"Video Settings\",\n \"type\": \"doctype\"\n }\n]"
- }
- ],
- "category": "Modules",
- "charts": [],
- "creation": "2020-09-10 12:21:22.335307",
- "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": "Utilities",
- "modified": "2020-09-10 12:33:30.089853",
- "modified_by": "user@erpnext.com",
- "module": "Utilities",
- "name": "Utilities",
- "owner": "user@erpnext.com",
- "pin_to_bottom": 1,
- "pin_to_top": 0,
- "shortcuts": []
-}
\ No newline at end of file
diff --git a/erpnext/utilities/workspace/utilities/utilities.json b/erpnext/utilities/workspace/utilities/utilities.json
new file mode 100644
index 0000000..2f9250e
--- /dev/null
+++ b/erpnext/utilities/workspace/utilities/utilities.json
@@ -0,0 +1,51 @@
+{
+ "category": "Modules",
+ "charts": [],
+ "creation": "2020-09-10 12:21:22.335307",
+ "developer_mode_only": 0,
+ "disable_user_customization": 0,
+ "docstatus": 0,
+ "doctype": "Workspace",
+ "extends_another_page": 0,
+ "hide_custom": 0,
+ "idx": 0,
+ "is_standard": 1,
+ "label": "Utilities",
+ "links": [
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Video",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "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": "Video Settings",
+ "link_to": "Video Settings",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ }
+ ],
+ "modified": "2020-12-01 13:38:36.711884",
+ "modified_by": "Administrator",
+ "module": "Utilities",
+ "name": "Utilities",
+ "owner": "user@erpnext.com",
+ "pin_to_bottom": 1,
+ "pin_to_top": 0,
+ "shortcuts": []
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 1b2dc9e..d12661b 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"snyk": "^1.290.1"
},
"dependencies": {
+ "onscan.js": "^1.5.2"
},
"scripts": {
"snyk-protect": "snyk protect",
diff --git a/yarn.lock b/yarn.lock
index 97a0635..e5a2da1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1217,6 +1217,11 @@
dependencies:
mimic-fn "^1.0.0"
+onscan.js@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341"
+ integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw==
+
opn@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"