Merge pull request #22334 from kennethsequeira/item-sale-hist
fix: Customer Group label in Item-wise Sales History report
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 0000000..8f67858
--- /dev/null
+++ b/.github/workflows/docker-release.yml
@@ -0,0 +1,14 @@
+name: Trigger Docker build on release
+on:
+ release:
+ types: [created]
+jobs:
+ curl:
+ runs-on: ubuntu-latest
+ container:
+ image: alpine:latest
+ steps:
+ - name: curl
+ run: |
+ apk add curl bash
+ curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
diff --git a/CODEOWNERS b/CODEOWNERS
index 5e1113d..7cf65a7 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,17 +3,16 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
-* @nabinhait
-manufacturing/ @rohitwaghchaure
+manufacturing/ @rohitwaghchaure @marination
accounts/ @deepeshgarg007 @nextchamp-saqib
-loan_management/ @deepeshgarg007
-pos* @nextchamp-saqib
-assets/ @nextchamp-saqib
+loan_management/ @deepeshgarg007 @rohitwaghchaure
+pos* @nextchamp-saqib @rohitwaghchaure
+assets/ @nextchamp-saqib @deepeshgarg007
stock/ @marination @rohitwaghchaure
-buying/ @marination @rohitwaghchaure
-hr/ @Anurag810
-projects/ @hrwX
-support/ @hrwX
-healthcare/ @ruchamahabal
-erpnext_integrations/ @Mangesh-Khairnar
+buying/ @marination @deepeshgarg007
+hr/ @Anurag810 @rohitwaghchaure
+projects/ @hrwX @nextchamp-saqib
+support/ @hrwX @marination
+healthcare/ @ruchamahabal @marination
+erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
requirements.txt @gavindsouza
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/accounts/accounts
similarity index 100%
copy from erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py
copy to erpnext/accounts/accounts
diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py
index cdd1661..b2abffc 100644
--- a/erpnext/accounts/dashboard_fixtures.py
+++ b/erpnext/accounts/dashboard_fixtures.py
@@ -1,127 +1,284 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from erpnext import get_default_company
import frappe
import json
+from frappe.utils import nowdate, add_months, get_date_str
+from frappe import _
+from erpnext.accounts.utils import get_fiscal_year, get_account_name, FiscalYearError
+def _get_fiscal_year(date=None):
+ try:
+ fiscal_year = get_fiscal_year(date=nowdate(), as_dict=True)
+ return fiscal_year
+
+ except FiscalYearError:
+ #if no fiscal year for current date then get default fiscal year
+ try:
+ fiscal_year = get_fiscal_year(as_dict=True)
+ return fiscal_year
+
+ except FiscalYearError:
+ #if still no fiscal year found then no accounting data created, return
+ return None
+
+def get_company_for_dashboards():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company_list = frappe.get_list("Company")
+ if company_list:
+ return company_list[0].name
+ return None
def get_data():
- data = frappe._dict({
- "dashboards": [],
- "charts": []
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(fiscal_year),
+ "number_cards": get_number_cards(fiscal_year)
})
- company = get_company_for_dashboards()
- if company:
- company_doc = frappe.get_doc("Company", company)
- data.dashboards = get_dashboards()
- data.charts = get_charts(company_doc)
- return data
def get_dashboards():
return [{
"name": "Accounts",
"dashboard_name": "Accounts",
+ "doctype": "Dashboard",
"charts": [
- { "chart": "Outgoing Bills (Sales Invoice)" },
- { "chart": "Incoming Bills (Purchase Invoice)" },
- { "chart": "Bank Balance" },
- { "chart": "Income" },
- { "chart": "Expenses" }
+ { "chart": "Profit and Loss" , "width": "Full"},
+ { "chart": "Incoming Bills (Purchase Invoice)", "width": "Half"},
+ { "chart": "Outgoing Bills (Sales Invoice)", "width": "Half"},
+ { "chart": "Accounts Receivable Ageing", "width": "Half"},
+ { "chart": "Accounts Payable Ageing", "width": "Half"},
+ { "chart": "Budget Variance", "width": "Full"},
+ { "chart": "Bank Balance", "width": "Full"}
+ ],
+ "cards": [
+ {"card": "Total Outgoing Bills"},
+ {"card": "Total Incoming Bills"},
+ {"card": "Total Incoming Payment"},
+ {"card": "Total Outgoing Payment"}
]
}]
-def get_charts(company):
- income_account = company.default_income_account or get_account("Income Account", company.name)
- expense_account = company.default_expense_account or get_account("Expense Account", company.name)
- bank_account = company.default_bank_account or get_account("Bank", company.name)
+def get_charts(fiscal_year):
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ bank_account = company.default_bank_account or get_account_name("Bank", company=company.name)
+ default_cost_center = company.cost_center
return [
{
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Income",
- "chart_name": "Income",
- "timespan": "Last Year",
- "color": None,
- "filters_json": json.dumps({"company": company.name, "account": income_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
+ "doctype": "Dashboard Charts",
+ "name": "Profit and Loss",
"owner": "Administrator",
- "type": "Line"
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Expenses",
- "chart_name": "Expenses",
- "timespan": "Last Year",
- "color": None,
- "filters_json": json.dumps({"company": company.name, "account": expense_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line"
- },
- {
- "doctype": "Dashboard Chart",
- "time_interval": "Quarterly",
- "name": "Bank Balance",
- "chart_name": "Bank Balance",
- "timespan": "Last Year",
- "color": "#ffb868",
- "filters_json": json.dumps({"company": company.name, "account": bank_account}),
- "source": "Account Balance Timeline",
- "chart_type": "Custom",
- "timeseries": 1,
- "owner": "Administrator",
- "type": "Line"
+ "report_name": "Profit and Loss Statement",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "filter_based_on": "Fiscal Year",
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
+ "periodicity": "Monthly",
+ "include_default_book_entries": 1
+ }),
+ "type": "Bar",
+ 'timeseries': 0,
+ "chart_type": "Report",
+ "chart_name": _("Profit and Loss"),
+ "is_custom": 1,
+ "is_public": 1
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"name": "Incoming Bills (Purchase Invoice)",
- "chart_name": "Incoming Bills (Purchase Invoice)",
+ "chart_name": _("Incoming Bills (Purchase Invoice)"),
"timespan": "Last Year",
"color": "#a83333",
- "value_based_on": "base_grand_total",
- "filters_json": json.dumps({}),
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Purchase Invoice", "docstatus", "=", 1]]),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Purchase Invoice",
- "type": "Bar"
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
},
{
"doctype": "Dashboard Chart",
- "time_interval": "Monthly",
"name": "Outgoing Bills (Sales Invoice)",
- "chart_name": "Outgoing Bills (Sales Invoice)",
+ "time_interval": "Monthly",
+ "chart_name": _("Outgoing Bills (Sales Invoice)"),
"timespan": "Last Year",
"color": "#7b933d",
- "value_based_on": "base_grand_total",
- "filters_json": json.dumps({}),
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Sales Invoice", "docstatus", "=", 1]]),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Sales Invoice",
- "type": "Bar"
- }
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Charts",
+ "name": "Accounts Receivable Ageing",
+ "owner": "Administrator",
+ "report_name": "Accounts Receivable",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "report_date": nowdate(),
+ "ageing_based_on": "Due Date",
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120
+ }),
+ "type": "Donut",
+ 'timeseries': 0,
+ "chart_type": "Report",
+ "chart_name": _("Accounts Receivable Ageing"),
+ "is_custom": 1,
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Charts",
+ "name": "Accounts Payable Ageing",
+ "owner": "Administrator",
+ "report_name": "Accounts Payable",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "report_date": nowdate(),
+ "ageing_based_on": "Due Date",
+ "range1": 30,
+ "range2": 60,
+ "range3": 90,
+ "range4": 120
+ }),
+ "type": "Donut",
+ 'timeseries': 0,
+ "chart_type": "Report",
+ "chart_name": _("Accounts Payable Ageing"),
+ "is_custom": 1,
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Charts",
+ "name": "Budget Variance",
+ "owner": "Administrator",
+ "report_name": "Budget Variance Report",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
+ "period": "Monthly",
+ "budget_against": "Cost Center"
+ }),
+ "type": "Bar",
+ "timeseries": 0,
+ "chart_type": "Report",
+ "chart_name": _("Budget Variance"),
+ "is_custom": 1,
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Charts",
+ "name": "Bank Balance",
+ "time_interval": "Quarterly",
+ "chart_name": "Bank Balance",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "account": bank_account
+ }),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half",
+ "is_public": 1
+ },
]
-def get_account(account_type, company):
- accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
- if accounts:
- return accounts[0].name
+def get_number_cards(fiscal_year):
-def get_company_for_dashboards():
- company = get_default_company()
- if not company:
- company_list = frappe.get_list("Company")
- if company_list:
- company = company_list[0].name
- return company
+ year_start_date = get_date_str(fiscal_year.get("year_start_date"))
+ year_end_date = get_date_str(fiscal_year.get("year_end_date"))
+ return [
+ {
+ "doctype": "Number Card",
+ "document_type": "Payment Entry",
+ "name": "Total Incoming Payment",
+ "filters_json": json.dumps([
+ ['Payment Entry', 'docstatus', '=', 1],
+ ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]],
+ ['Payment Entry', 'payment_type', '=', 'Receive']
+ ]),
+ "label": _("Total Incoming Payment"),
+ "function": "Sum",
+ "aggregate_function_based_on": "base_received_amount",
+ "is_public": 1,
+ "is_custom": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Payment Entry",
+ "name": "Total Outgoing Payment",
+ "filters_json": json.dumps([
+ ['Payment Entry', 'docstatus', '=', 1],
+ ['Payment Entry', 'posting_date', 'between', [year_start_date, year_end_date]],
+ ['Payment Entry', 'payment_type', '=', 'Pay']
+ ]),
+ "label": _("Total Outgoing Payment"),
+ "function": "Sum",
+ "aggregate_function_based_on": "base_paid_amount",
+ "is_public": 1,
+ "is_custom": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Sales Invoice",
+ "name": "Total Outgoing Bills",
+ "filters_json": json.dumps([
+ ['Sales Invoice', 'docstatus', '=', 1],
+ ['Sales Invoice', 'posting_date', 'between', [year_start_date, year_end_date]]
+ ]),
+ "label": _("Total Outgoing Bills"),
+ "function": "Sum",
+ "aggregate_function_based_on": "base_net_total",
+ "is_public": 1,
+ "is_custom": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Purchase Invoice",
+ "name": "Total Incoming Bills",
+ "filters_json": json.dumps([
+ ['Purchase Invoice', 'docstatus', '=', 1],
+ ['Purchase Invoice', 'posting_date', 'between', [year_start_date, year_end_date]]
+ ]),
+ "label": _("Total Incoming Bills"),
+ "function": "Sum",
+ "aggregate_function_based_on": "base_net_total",
+ "is_public": 1,
+ "is_custom": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ }
+ ]
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index b57e678..4480110 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -199,10 +199,13 @@
if item.get(enable_check):
_book_deferred_revenue_or_expense(item)
-def process_deferred_accounting(posting_date=today()):
+def process_deferred_accounting(posting_date=None):
''' Converts deferred income/expense into income/expense
Executed via background jobs on every month end '''
+ if not posting_date:
+ posting_date = today()
+
if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
return
diff --git a/erpnext/accounts/desk_page/accounting/accounting.json b/erpnext/accounts/desk_page/accounting/accounting.json
index 0d6aca6..42fb9f4 100644
--- a/erpnext/accounts/desk_page/accounting/accounting.json
+++ b/erpnext/accounts/desk_page/accounting/accounting.json
@@ -18,7 +18,7 @@
{
"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 Invoice\"\n ],\n \"doctype\": \"Purchase Invoice\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Items To Be Billed\",\n \"name\": \"Purchase Order Items To Be Billed\",\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]"
+ "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,
@@ -47,11 +47,6 @@
},
{
"hidden": 0,
- "links": "[\n {\n \"description\": \"Match non-linked Invoices and Payments.\",\n \"label\": \"Match Payments with Invoices\",\n \"name\": \"Payment Reconciliation\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update bank payment dates with journals.\",\n \"label\": \"Update Bank Clearance Dates\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Invoice Discounting\",\n \"name\": \"Invoice Discounting\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Reconciliation Statement\",\n \"name\": \"Bank Reconciliation Statement\",\n \"type\": \"report\"\n },\n {\n \"icon\": \"fa fa-bar-chart\",\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Journal Entry\"\n ],\n \"doctype\": \"Journal Entry\",\n \"is_query_report\": true,\n \"label\": \"Bank Clearance Summary\",\n \"name\": \"Bank Clearance Summary\",\n \"type\": \"report\"\n },\n {\n \"label\": \"Bank Guarantee\",\n \"name\": \"Bank Guarantee\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup cheque dimensions for printing\",\n \"label\": \"Cheque Print Template\",\n \"name\": \"Cheque Print Template\",\n \"type\": \"doctype\"\n }\n]",
- "title": "Banking and Payments"
- },
- {
- "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]"
},
@@ -89,8 +84,8 @@
"category": "Modules",
"charts": [
{
- "chart_name": "Bank Balance",
- "label": "Bank Balance"
+ "chart_name": "Profit and Loss",
+ "label": "Profit and Loss"
}
],
"creation": "2020-03-02 15:41:59.515192",
@@ -99,24 +94,40 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Accounting",
- "modified": "2020-04-29 12:17:34.844397",
+ "modified": "2020-05-27 20:34:50.949772",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
+ "onboarding": "Accounts",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
- "label": "Account",
+ "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": "Dashboard",
+ "link_to": "Accounts",
+ "type": "Dashboard"
+ },
+ {
"label": "Journal Entry",
"link_to": "Journal Entry",
"type": "DocType"
@@ -137,11 +148,6 @@
"type": "Report"
},
{
- "label": "Profit and Loss Statement",
- "link_to": "Profit and Loss Statement",
- "type": "Report"
- },
- {
"label": "Trial Balance",
"link_to": "Trial Balance",
"type": "Report"
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index f62d076..28b090b 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -14,6 +14,9 @@
on_change: function() {
var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value();
+ if (!company) {
+ frappe.throw(__("Please set a Company"));
+ }
frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company",
args: {
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 894ec5b..8834385 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -72,7 +72,11 @@
if doctype == "Budget":
add_dimension_to_budget_doctype(df, doc)
else:
- create_custom_field(doctype, df)
+ meta = frappe.get_meta(doctype, cached=False)
+ fieldnames = [d.fieldname for d in meta.get("fields")]
+
+ if df['fieldname'] not in fieldnames:
+ create_custom_field(doctype, df)
count += 1
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index 9e2f6ee..f341f78 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -14,7 +14,18 @@
is_group: 1
}
}
- })
+ });
+
+ frm.set_query("cost_center", "distributed_cost_center", function() {
+ return {
+ filters: {
+ company: frm.doc.company,
+ is_group: 0,
+ enable_distributed_cost_center: 0,
+ name: ['!=', frm.doc.name]
+ }
+ };
+ });
},
refresh: function(frm) {
if (!frm.is_new()) {
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index 5013c92..e7fa954 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -16,6 +16,9 @@
"cb0",
"is_group",
"disabled",
+ "section_break_9",
+ "enable_distributed_cost_center",
+ "distributed_cost_center",
"lft",
"rgt",
"old_parent"
@@ -119,13 +122,31 @@
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
+ },
+ {
+ "default": "0",
+ "fieldname": "enable_distributed_cost_center",
+ "fieldtype": "Check",
+ "label": "Enable Distributed Cost Center"
+ },
+ {
+ "depends_on": "eval:doc.is_group==0",
+ "fieldname": "section_break_9",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "enable_distributed_cost_center",
+ "fieldname": "distributed_cost_center",
+ "fieldtype": "Table",
+ "label": "Distributed Cost Center",
+ "options": "Distributed Cost Center"
}
],
"icon": "fa fa-money",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2020-04-29 16:09:30.025214",
+ "modified": "2020-06-17 16:09:30.025214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cost Center",
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 0294e78..12094d4 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -19,6 +19,24 @@
def validate(self):
self.validate_mandatory()
self.validate_parent_cost_center()
+ self.validate_distributed_cost_center()
+
+ def validate_distributed_cost_center(self):
+ if cint(self.enable_distributed_cost_center):
+ if not self.distributed_cost_center:
+ frappe.throw(_("Please enter distributed cost center"))
+ if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
+ frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
+ if not self.get('__islocal'):
+ if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
+ and self.check_if_part_of_distributed_cost_center():
+ frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
+ if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
+ frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
+ if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
+ frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
+ else:
+ self.distributed_cost_center = []
def validate_mandatory(self):
if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -43,12 +61,15 @@
return 1
def convert_ledger_to_group(self):
+ if cint(self.enable_distributed_cost_center):
+ frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
+ if self.check_if_part_of_distributed_cost_center():
+ frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
if self.check_gle_exists():
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
- else:
- self.is_group = 1
- self.save()
- return 1
+ self.is_group = 1
+ self.save()
+ return 1
def check_gle_exists(self):
return frappe.db.get_value("GL Entry", {"cost_center": self.name})
@@ -57,6 +78,9 @@
return frappe.db.sql("select name from `tabCost Center` where \
parent_cost_center = %s and docstatus != 2", self.name)
+ def check_if_part_of_distributed_cost_center(self):
+ return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
+
def before_rename(self, olddn, newdn, merge=False):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
@@ -100,3 +124,7 @@
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
return new_account
+
+def check_if_distributed_cost_center_enabled(cost_center_list):
+ value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
+ return next((True for x in value_list if x[0]), False)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index 8f23d90..b5fc7e3 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -22,6 +22,33 @@
self.assertRaises(frappe.ValidationError, cost_center.save)
+ def test_validate_distributed_cost_center(self):
+
+ if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
+ frappe.get_doc(test_records[0]).insert()
+
+ if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
+ frappe.get_doc(test_records[1]).insert()
+
+ invalid_distributed_cost_center = frappe.get_doc({
+ "company": "_Test Company",
+ "cost_center_name": "_Test Distributed Cost Center",
+ "doctype": "Cost Center",
+ "is_group": 0,
+ "parent_cost_center": "_Test Company - _TC",
+ "enable_distributed_cost_center": 1,
+ "distributed_cost_center": [{
+ "cost_center": "_Test Cost Center - _TC",
+ "percentage_allocation": 40
+ }, {
+ "cost_center": "_Test Cost Center 2 - _TC",
+ "percentage_allocation": 50
+ }
+ ]
+ })
+
+ self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
+
def create_cost_center(**args):
args = frappe._dict(args)
if args.cost_center_name:
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/accounts/doctype/distributed_cost_center/__init__.py
similarity index 100%
rename from erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py
rename to erpnext/accounts/doctype/distributed_cost_center/__init__.py
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
new file mode 100644
index 0000000..45b0e2d
--- /dev/null
+++ b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "creation": "2020-03-19 12:34:01.500390",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "cost_center",
+ "percentage_allocation"
+ ],
+ "fields": [
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Cost Center",
+ "options": "Cost Center",
+ "reqd": 1
+ },
+ {
+ "fieldname": "percentage_allocation",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Percentage Allocation",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-03-19 12:54:43.674655",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Distributed Cost Center",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
new file mode 100644
index 0000000..48c589f
--- /dev/null
+++ b/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.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 DistributedCostCenter(Document):
+ pass
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 9d50639..af2aa65 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -191,6 +191,7 @@
{
"fieldname": "total_debit",
"fieldtype": "Currency",
+ "in_list_view": 1,
"label": "Total Debit",
"no_copy": 1,
"oldfieldname": "total_debit",
@@ -252,7 +253,6 @@
"fieldname": "total_amount",
"fieldtype": "Currency",
"hidden": 1,
- "in_list_view": 1,
"label": "Total Amount",
"no_copy": 1,
"options": "total_amount_currency",
@@ -503,7 +503,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 10:55:28.240916",
+ "modified": "2020-06-02 18:15:46.955697",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index 4d8da37..699eb08 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -11,21 +11,9 @@
};
});
- frm.set_query('cost_center', 'invoices', function(doc, cdt, cdn) {
- return {
- filters: {
- 'company': doc.company
- }
- };
- });
-
- frm.set_query('cost_center', function(doc) {
- return {
- filters: {
- 'company': doc.company
- }
- };
- });
+ if (frm.doc.company) {
+ frm.trigger('setup_company_filters');
+ }
},
refresh: function(frm) {
@@ -51,19 +39,50 @@
});
},
- company: function(frm) {
- frappe.call({
- method: 'erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool.get_temporary_opening_account',
- args: {
- company: frm.doc.company
- },
- callback: (r) => {
- if (r.message) {
- frm.doc.__onload.temporary_opening_account = r.message;
- frm.trigger('update_invoice_table');
+ setup_company_filters: function(frm) {
+ frm.set_query('cost_center', 'invoices', function(doc, cdt, cdn) {
+ return {
+ filters: {
+ 'company': doc.company
+ }
+ };
+ });
+
+ frm.set_query('cost_center', function(doc) {
+ return {
+ filters: {
+ 'company': doc.company
+ }
+ };
+ });
+
+ frm.set_query('temporary_opening_account', 'invoices', function(doc, cdt, cdn) {
+ return {
+ filters: {
+ 'company': doc.company
}
}
- })
+ });
+ },
+
+ company: function(frm) {
+ if (frm.doc.company) {
+
+ frm.trigger('setup_company_filters');
+
+ frappe.call({
+ method: 'erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool.get_temporary_opening_account',
+ args: {
+ company: frm.doc.company
+ },
+ callback: (r) => {
+ if (r.message) {
+ frm.doc.__onload.temporary_opening_account = r.message;
+ frm.trigger('update_invoice_table');
+ }
+ }
+ })
+ }
},
invoice_type: function(frm) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index d2245d6..15e51bb 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -319,7 +319,7 @@
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += reference.allocated_amount
- if not invoice_paid_amount_map.get(reference.reference_name):
+ if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
fields=['paid_amount', 'payment_amount', 'payment_term'])
for term in payment_schedule:
@@ -332,12 +332,14 @@
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
else:
- outstanding = invoice_paid_amount_map.get(key)['outstanding']
+ outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
+
if amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
- frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
- WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+ if amount and outstanding:
+ frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
+ WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
def set_status(self):
if self.docstatus == 2:
@@ -1091,17 +1093,20 @@
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
- references.append({
- 'reference_doctype': dt,
- 'reference_name': dn,
- 'bill_no': doc.get('bill_no'),
- 'due_date': doc.get('due_date'),
- 'total_amount': grand_total,
- 'outstanding_amount': outstanding_amount,
- 'payment_term': payment_term.payment_term,
- 'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
+ payment_term_outstanding = flt(payment_term.payment_amount - payment_term.paid_amount,
payment_term.precision('payment_amount'))
- })
+
+ if payment_term_outstanding:
+ references.append({
+ 'reference_doctype': dt,
+ 'reference_name': dn,
+ 'bill_no': doc.get('bill_no'),
+ 'due_date': doc.get('due_date'),
+ 'total_amount': grand_total,
+ 'outstanding_amount': outstanding_amount,
+ 'payment_term': payment_term.payment_term,
+ 'allocated_amount': payment_term_outstanding
+ })
return references
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 7508683..eef6be1 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -349,9 +349,10 @@
"read_only": 1
}
],
+ "in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-08 10:23:02.815237",
+ "modified": "2020-05-29 17:38:49.392713",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 68aeb6d..287e00f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -69,7 +69,7 @@
elif self.payment_request_type == 'Inward':
self.db_set('status', 'Requested')
- send_mail = self.payment_gateway_validation()
+ send_mail = self.payment_gateway_validation() if self.payment_gateway else None
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 2da71df..2bf0b72 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -385,6 +385,50 @@
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
+
+ def test_cumulative_pricing_rule(self):
+ frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
+ test_record = {
+ "doctype": "Pricing Rule",
+ "title": "_Test Cumulative Pricing Rule",
+ "apply_on": "Item Code",
+ "currency": "USD",
+ "items": [{
+ "item_code": "_Test Item",
+ }],
+ "is_cumulative": 1,
+ "selling": 1,
+ "applicable_for": "Customer",
+ "customer": "_Test Customer",
+ "rate_or_discount": "Discount Percentage",
+ "rate": 0,
+ "min_amt": 0,
+ "max_amt": 10000,
+ "discount_percentage": 17.5,
+ "price_or_product_discount": "Price",
+ "company": "_Test Company",
+ "valid_from": frappe.utils.nowdate(),
+ "valid_upto": frappe.utils.nowdate()
+ }
+ frappe.get_doc(test_record.copy()).insert()
+
+ args = frappe._dict({
+ "item_code": "_Test Item",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "name": None,
+ "transaction_date": frappe.utils.nowdate()
+ })
+ details = get_item_details(args)
+
+ self.assertTrue(details)
def make_pricing_rule(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index cb05481..53115f9 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -366,8 +366,7 @@
sum_qty, sum_amt = [0, 0]
doctype = doc.get('parenttype') or doc.doctype
- date_field = ('transaction_date'
- if doc.get('transaction_date') else 'posting_date')
+ date_field = 'transaction_date' if frappe.get_meta(doctype).has_field('transaction_date') else 'posting_date'
child_doctype = '{0} Item'.format(doctype)
apply_on = frappe.scrub(pr_doc.get('apply_on'))
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 98ba5c7..829c34d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -42,6 +42,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -168,7 +170,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -180,7 +184,9 @@
"options": "ACC-PINV-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier",
@@ -192,7 +198,9 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -204,7 +212,9 @@
"label": "Supplier Name",
"oldfieldname": "supplier_name",
"oldfieldtype": "Data",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fetch_from": "supplier.tax_id",
@@ -212,21 +222,27 @@
"fieldtype": "Read Only",
"label": "Tax Id",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date",
"oldfieldname": "due_date",
- "oldfieldtype": "Date"
+ "oldfieldtype": "Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "is_paid",
"fieldtype": "Check",
"label": "Is Paid",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -234,19 +250,25 @@
"fieldtype": "Check",
"label": "Is Return (Debit Note)",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -256,13 +278,17 @@
"label": "Company",
"options": "Company",
"print_hide": 1,
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
- "options": "Cost Center"
+ "options": "Cost Center",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Today",
@@ -274,7 +300,9 @@
"oldfieldtype": "Date",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "posting_time",
@@ -283,6 +311,8 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -291,7 +321,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -303,44 +335,58 @@
"oldfieldtype": "Link",
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.on_hold",
"fieldname": "sb_14",
"fieldtype": "Section Break",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "on_hold",
"fieldtype": "Check",
- "label": "Hold Invoice"
+ "label": "Hold Invoice",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
- "label": "Release Date"
+ "label": "Release Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_17",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.on_hold",
"fieldname": "hold_comment",
"fieldtype": "Small Text",
- "label": "Reason For Putting On Hold"
+ "label": "Reason For Putting On Hold",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "bill_no",
"fieldname": "supplier_invoice_details",
"fieldtype": "Section Break",
- "label": "Supplier Invoice Details"
+ "label": "Supplier Invoice Details",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -348,11 +394,15 @@
"label": "Supplier Invoice No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -360,13 +410,17 @@
"label": "Supplier Invoice Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
"fieldname": "returns",
"fieldtype": "Section Break",
- "label": "Returns"
+ "label": "Returns",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "return_against",
@@ -376,26 +430,34 @@
"no_copy": 1,
"options": "Purchase Invoice",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
@@ -403,51 +465,67 @@
"in_global_search": 1,
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
"fieldtype": "Small Text",
"label": "Contact Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -456,7 +534,9 @@
"oldfieldname": "currency",
"oldfieldtype": "Select",
"options": "Currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -465,18 +545,24 @@
"oldfieldname": "conversion_rate",
"oldfieldtype": "Currency",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -484,14 +570,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -500,11 +590,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -512,7 +606,9 @@
"fieldtype": "Link",
"label": "Set Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "update_stock",
@@ -522,11 +618,15 @@
"label": "Rejected Warehouse",
"no_copy": 1,
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -534,7 +634,9 @@
"fieldtype": "Select",
"label": "Raw Materials Supplied",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -545,25 +647,33 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -573,42 +683,56 @@
"oldfieldname": "entries",
"oldfieldtype": "Table",
"options": "Purchase Invoice Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_materials_supplied",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
"fieldtype": "Table",
"label": "Supplied Items",
"options": "Purchase Receipt Item Supplied",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_26",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -616,7 +740,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -626,18 +752,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_28",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -647,42 +779,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_49",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_51",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -691,7 +837,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -699,13 +847,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -714,13 +866,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -730,7 +886,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -740,7 +898,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -750,11 +910,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_40",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -764,7 +928,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -774,7 +940,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -782,14 +950,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_44",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -797,7 +969,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -805,28 +979,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_46",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_49",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -836,7 +1020,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -845,7 +1031,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -855,7 +1043,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -864,13 +1054,17 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break8",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -881,7 +1075,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -890,7 +1086,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -900,7 +1098,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -909,7 +1109,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_advance",
@@ -920,7 +1122,9 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "outstanding_amount",
@@ -931,14 +1135,18 @@
"oldfieldtype": "Currency",
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"depends_on": "grand_total",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -946,30 +1154,40 @@
"depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
"fieldname": "payments_section",
"fieldtype": "Section Break",
- "label": "Payments"
+ "label": "Payments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"label": "Mode of Payment",
"options": "Mode of Payment",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cash_bank_account",
"fieldtype": "Link",
"label": "Cash/Bank Account",
- "options": "Account"
+ "options": "Account",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "clearance_date",
"fieldtype": "Date",
"hidden": 1,
- "label": "Clearance Date"
+ "label": "Clearance Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_br_payments",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_paid",
@@ -978,7 +1196,9 @@
"label": "Paid Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_paid_amount",
@@ -987,7 +1207,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -995,7 +1217,9 @@
"depends_on": "grand_total",
"fieldname": "write_off",
"fieldtype": "Section Break",
- "label": "Write Off"
+ "label": "Write Off",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "write_off_amount",
@@ -1003,7 +1227,9 @@
"label": "Write Off Amount",
"no_copy": 1,
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_write_off_amount",
@@ -1012,11 +1238,15 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_61",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1024,7 +1254,9 @@
"fieldtype": "Link",
"label": "Write Off Account",
"options": "Account",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1032,7 +1264,9 @@
"fieldtype": "Link",
"label": "Write Off Cost Center",
"options": "Cost Center",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1042,13 +1276,17 @@
"label": "Advance Payments",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "allocate_advances_automatically",
"fieldtype": "Check",
- "label": "Set Advances and Allocate (FIFO)"
+ "label": "Set Advances and Allocate (FIFO)",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1056,7 +1294,9 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advances",
@@ -1066,20 +1306,26 @@
"oldfieldname": "advance_allocation_details",
"oldfieldtype": "Table",
"options": "Purchase Invoice Advance",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval:(!doc.is_return)",
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -1087,7 +1333,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1095,25 +1343,33 @@
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"label": "Terms and Conditions",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
"fieldtype": "Link",
"label": "Terms",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
- "label": "Terms and Conditions1"
+ "label": "Terms and Conditions1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1121,7 +1377,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1129,11 +1387,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_112",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1145,14 +1407,18 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1161,7 +1427,9 @@
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "credit_to",
@@ -1172,7 +1440,9 @@
"options": "Account",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -1182,7 +1452,9 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -1192,7 +1464,9 @@
"oldfieldname": "is_opening",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "against_expense_account",
@@ -1202,11 +1476,15 @@
"no_copy": 1,
"oldfieldname": "against_expense_account",
"oldfieldtype": "Small Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_63",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -1215,14 +1493,18 @@
"in_standard_filter": 1,
"label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_invoice_reference",
"fieldtype": "Link",
"label": "Inter Company Invoice Reference",
"options": "Sales Invoice",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
@@ -1231,14 +1513,18 @@
"no_copy": 1,
"oldfieldname": "remarks",
"oldfieldtype": "Text",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1247,7 +1533,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1256,11 +1544,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_114",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1269,24 +1561,32 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
- "label": "Accounting Dimensions "
+ "label": "Accounting Dimensions ",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -1294,7 +1594,9 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_withholding_category",
@@ -1302,14 +1604,32 @@
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-18 13:05:25.199832",
+ "modified": "2020-06-13 22:26:30.800199",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index db20589..63c34ed 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -398,7 +398,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order",
"no_copy": 1,
"print_hide": 1
@@ -1579,7 +1579,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-29 13:37:09.355300",
+ "modified": "2020-05-19 17:00:57.208696",
"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 05b85da..5e8279b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -582,14 +582,14 @@
def validate_item_code(self):
for d in self.get('items'):
- if not d.item_code:
+ if not d.item_code and self.is_opening == "No":
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
def validate_warehouse(self):
super(SalesInvoice, self).validate_warehouse()
for d in self.get_item_list():
- if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
+ if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
def validate_delivery_note(self):
@@ -1450,11 +1450,17 @@
parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Customer", doc.customer, "represents_company")
+ if not parties:
+ frappe.throw(_('No Supplier found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+
party = get_internal_party(parties, "Supplier", doc)
else:
parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company")
+ if not parties:
+ frappe.throw(_('No Customer found for Inter Company Transactions which represents company {0}').format(frappe.bold(doc.company)))
+
party = get_internal_party(parties, "Customer", doc)
return {
@@ -1519,14 +1525,22 @@
def update_details(source_doc, target_doc, source_parent):
target_doc.inter_company_invoice_reference = source_doc.name
if target_doc.doctype in ["Purchase Invoice", "Purchase Order"]:
+ currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.buying_price_list = source_doc.selling_price_list
+
+ if currency:
+ target_doc.currency = currency
else:
+ currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
target_doc.selling_price_list = source_doc.buying_price_list
+ if currency:
+ target_doc.currency = currency
+
doclist = get_mapped_doc(doctype, source_name, {
doctype: {
"doctype": target_doctype,
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c82a249..6cdf9b5 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1745,53 +1745,6 @@
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
- def test_deferred_error_email(self):
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
-
- item = create_item("_Test Item for Deferred Accounting")
- item.enable_deferred_revenue = 1
- item.deferred_revenue_account = deferred_account
- item.no_of_months = 12
- item.save()
-
- si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_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"
- si.items[0].deferred_revenue_account = deferred_account
- si.save()
- si.submit()
-
- from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
-
- acc_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
- acc_settings.acc_frozen_upto = '2019-01-31'
- acc_settings.save()
-
- pda = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date=nowdate(),
- start_date="2019-01-01",
- end_date="2019-03-31",
- type="Income",
- company="_Test Company"
- ))
-
- pda.insert()
- pda.submit()
-
- email = frappe.db.sql(""" select name from `tabEmail Queue`
- where message like %(txt)s """, {
- 'txt': "%%%s%%" % "Error while processing deferred accounting for {0}".format(pda.name)
- })
-
- self.assertTrue(email)
-
- acc_settings.load_from_db()
- acc_settings.acc_frozen_upto = None
- acc_settings.save()
-
def test_inter_company_transaction(self):
if not frappe.db.exists("Customer", "_Test Internal Customer"):
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index b2638c7..d32a348 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -45,7 +45,9 @@
shipping_amount = 0.0
by_value = False
- self.validate_countries(doc)
+ if doc.get_shipping_address():
+ # validate country only if there is address
+ self.validate_countries(doc)
if self.calculate_based_on == 'Net Total':
value = doc.base_net_total
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 4d43919..83d7967 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -180,7 +180,7 @@
if company:
condition += "and company =%s" % (company)
if from_date and to_date:
- condition += "and posting_date between %s and %s" % (company, from_date, to_date)
+ condition += "and posting_date between %s and %s" % (from_date, to_date)
## Appending the same supplier again if length of suppliers list is 1
## since tuple of single element list contains None, For example ('Test Supplier 1', )
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
new file mode 100644
index 0000000..12da440
--- /dev/null
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -0,0 +1,51 @@
+{
+ "allow_roles": [
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ }
+ ],
+ "creation": "2020-05-13 19:03:32.564049",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-14 22:11:06.475938",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Chart Of Accounts"
+ },
+ {
+ "step": "Setup Taxes"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Create Your First Purchase Invoice"
+ },
+ {
+ "step": "Create a Customer"
+ },
+ {
+ "step": "Create Your First Sales Invoice"
+ },
+ {
+ "step": "Configure Account Settings"
+ }
+ ],
+ "subtitle": "Accounts, invoices and taxation.",
+ "success_message": "The Accounts module is now set up!",
+ "title": "Let's Setup Your Accounts and Taxes.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
new file mode 100644
index 0000000..cbd022b
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-13 19:58:20.928127",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:40:28.410447",
+ "modified_by": "Administrator",
+ "name": "Chart Of Accounts",
+ "owner": "Administrator",
+ "path": "Tree/Account",
+ "reference_document": "Account",
+ "show_full_form": 0,
+ "title": "Review Chart Of Accounts",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json
new file mode 100644
index 0000000..c8be357
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/configure_account_settings/configure_account_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:53:00.876946",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-14 18:06:25.212923",
+ "modified_by": "Administrator",
+ "name": "Configure Account Settings",
+ "owner": "Administrator",
+ "reference_document": "Accounts Settings",
+ "show_full_form": 1,
+ "title": "Configure Account Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
new file mode 100644
index 0000000..bb396d2
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_customer/create_a_customer.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:46:41.831517",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:46:41.831517",
+ "modified_by": "Administrator",
+ "name": "Create a Customer",
+ "owner": "Administrator",
+ "reference_document": "Customer",
+ "show_full_form": 0,
+ "title": "Create a Customer",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..450bee1
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:45:28.554605",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:45:28.554605",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
new file mode 100644
index 0000000..3a2b8d3
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:10:07.049704",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:10:07.049704",
+ "modified_by": "Administrator",
+ "name": "Create Your First Purchase Invoice",
+ "owner": "Administrator",
+ "reference_document": "Purchase Invoice",
+ "show_full_form": 1,
+ "title": "Create Your First Purchase Invoice ",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json
new file mode 100644
index 0000000..473de50
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/create_your_first_sales_invoice/create_your_first_sales_invoice.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:48:21.019019",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:48:21.019019",
+ "modified_by": "Administrator",
+ "name": "Create Your First Sales Invoice",
+ "owner": "Administrator",
+ "reference_document": "Sales Invoice",
+ "show_full_form": 1,
+ "title": "Create Your First Sales Invoice ",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
new file mode 100644
index 0000000..8e00067
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-13 19:29:43.844463",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:40:16.014413",
+ "modified_by": "Administrator",
+ "name": "Setup Taxes",
+ "owner": "Administrator",
+ "reference_document": "Sales Taxes and Charges Template",
+ "show_full_form": 1,
+ "title": "Lets create a Tax Template for Sales ",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 528fb4e..db91b66 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -602,10 +602,14 @@
else:
return ''
-def get_partywise_advanced_payment_amount(party_type, posting_date = None, company=None):
+def get_partywise_advanced_payment_amount(party_type, posting_date = None, future_payment=0, company=None):
cond = "1=1"
if posting_date:
- cond = "posting_date <= '{0}'".format(posting_date)
+ if future_payment:
+ cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date)
+ else:
+ cond = "posting_date <= '{0}'".format(posting_date)
+
if company:
cond += "and company = '{0}'".format(company)
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 4e09f99..2aa9618 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -135,12 +135,5 @@
}
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Accounts Payable"].filters.splice(9, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('Accounts Payable', 9);
diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
index d5f18b0..9c6b063 100644
--- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
+++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js
@@ -104,12 +104,5 @@
}
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Accounts Payable Summary"].filters.splice(9, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 6208eb6..8dc558a 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -199,12 +199,5 @@
}
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Accounts Receivable"].filters.splice(9, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('Accounts Receivable', 9);
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index e9c286f..66aa180 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -169,9 +169,11 @@
def append_subtotal_row(self, party):
sub_total_row = self.total_row_map.get(party)
- self.data.append(sub_total_row)
- self.data.append({})
- self.update_sub_total_row(sub_total_row, 'Total')
+
+ if sub_total_row:
+ self.data.append(sub_total_row)
+ self.data.append({})
+ self.update_sub_total_row(sub_total_row, 'Total')
def get_voucher_balance(self, gle):
if self.filters.get("sales_person"):
@@ -232,7 +234,8 @@
if self.filters.get('group_by_party'):
self.append_subtotal_row(self.previous_party)
- self.data.append(self.total_row_map.get('Total'))
+ if self.data:
+ self.data.append(self.total_row_map.get('Total'))
def append_row(self, row):
self.allocate_future_payments(row)
@@ -534,7 +537,7 @@
def get_ageing_data(self, entry_date, row):
# [0-30, 30-60, 60-90, 90-120, 120-above]
- row.range1 = row.range2 = row.range3 = row.range4 = range5 = 0.0
+ row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0
if not (self.age_as_on and entry_date):
return
@@ -546,7 +549,7 @@
self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4 = 30, 60, 90, 120
for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]):
- if row.age <= days:
+ if cint(row.age) <= cint(days):
index = i
break
@@ -559,6 +562,14 @@
conditions, values = self.prepare_conditions()
order_by = self.get_order_by_condition()
+ if self.filters.show_future_payments:
+ values.insert(2, self.filters.report_date)
+
+ date_condition = """AND (posting_date <= %s
+ OR (against_voucher IS NULL AND DATE(creation) <= %s))"""
+ else:
+ date_condition = "AND posting_date <=%s"
+
if self.filters.get(scrub(self.party_type)):
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
else:
@@ -574,9 +585,8 @@
docstatus < 2
and party_type=%s
and (party is not null and party != '')
- and posting_date <= %s
- {1} {2}"""
- .format(select_fields, conditions, order_by), values, as_dict=True)
+ {1} {2} {3}"""
+ .format(select_fields, date_condition, conditions, order_by), values, as_dict=True)
def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"):
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
index b316f10..305cddb 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
@@ -111,7 +111,12 @@
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
- }
+ },
+ {
+ "fieldname":"show_future_payments",
+ "label": __("Show Future Payments"),
+ "fieldtype": "Check",
+ },
],
onload: function(report) {
@@ -122,11 +127,4 @@
}
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Accounts Receivable Summary"].filters.splice(9, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index aa6b42e..657b3e8 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -33,7 +33,7 @@
self.get_party_total(args)
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
- self.filters.report_date, self.filters.company) or {}
+ self.filters.report_date, self.filters.show_future_payments, self.filters.company) or {}
for party, party_dict in iteritems(self.party_total):
if party_dict.outstanding == 0:
diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
index d7efbad..5001ad9 100644
--- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
+++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
@@ -93,7 +93,7 @@
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
from (SELECT a.asset_category,
- ifnull(sum(case when ds.schedule_date < %(from_date)s then
+ ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
ds.depreciation_amount
else
0
@@ -111,13 +111,11 @@
0
end), 0) as depreciation_amount_during_the_period
from `tabAsset` a, `tabDepreciation Schedule` ds
- where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent
+ where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category
union
SELECT a.asset_category,
- ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
- and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s)
- then
+ ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0
else
a.opening_accumulated_depreciation
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js
index c4c24c0..4a4ad4d 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.js
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js
@@ -4,6 +4,8 @@
frappe.require("assets/erpnext/js/financial_statements.js", function() {
frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements);
+ erpnext.utils.add_dimensions('Balance Sheet', 10);
+
frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "accumulated_values",
"label": __("Accumulated Values"),
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 49c1d0f..9c9ada8 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -29,41 +29,76 @@
for dimension in dimensions:
dimension_items = cam_map.get(dimension)
if dimension_items:
- for account, monthwise_data in iteritems(dimension_items):
- row = [dimension, account]
- totals = [0, 0, 0]
- for year in get_fiscal_years(filters):
- last_total = 0
- for relevant_months in period_month_ranges:
- period_data = [0, 0, 0]
- for month in relevant_months:
- if monthwise_data.get(year[0]):
- month_data = monthwise_data.get(year[0]).get(month, {})
- for i, fieldname in enumerate(["target", "actual", "variance"]):
- value = flt(month_data.get(fieldname))
- period_data[i] += value
- totals[i] += value
+ data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
+ else:
+ DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(dimension)s
+ AND parent NOT IN %(dimension)s
+ GROUP BY parent''',{'dimension':[dimension]})
+ if DCC_allocation:
+ filters['budget_against_filter'] = [DCC_allocation[0][0]]
+ cam_map = get_dimension_account_month_map(filters)
+ dimension_items = cam_map.get(DCC_allocation[0][0])
+ if dimension_items:
+ data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
- period_data[0] += last_total
+ chart = get_chart_data(filters, columns, data)
- if filters.get("show_cumulative"):
- last_total = period_data[0] - period_data[1]
+ return columns, data, None, chart
- period_data[2] = period_data[0] - period_data[1]
- row += period_data
- totals[2] = totals[0] - totals[1]
- if filters["period"] != "Yearly":
- row += totals
- data.append(row)
+def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
- return columns, data
+ for account, monthwise_data in iteritems(dimension_items):
+ row = [dimension, account]
+ totals = [0, 0, 0]
+ for year in get_fiscal_years(filters):
+ last_total = 0
+ for relevant_months in period_month_ranges:
+ period_data = [0, 0, 0]
+ for month in relevant_months:
+ if monthwise_data.get(year[0]):
+ month_data = monthwise_data.get(year[0]).get(month, {})
+ for i, fieldname in enumerate(["target", "actual", "variance"]):
+ value = flt(month_data.get(fieldname))
+ period_data[i] += value
+ totals[i] += value
+
+ period_data[0] += last_total
+
+ if DCC_allocation:
+ period_data[0] = period_data[0]*(DCC_allocation/100)
+ period_data[1] = period_data[1]*(DCC_allocation/100)
+
+ if(filters.get("show_cumulative")):
+ last_total = period_data[0] - period_data[1]
+
+ period_data[2] = period_data[0] - period_data[1]
+ row += period_data
+ totals[2] = totals[0] - totals[1]
+ if filters["period"] != "Yearly" :
+ row += totals
+ data.append(row)
+
+ return data
def get_columns(filters):
columns = [
- _(filters.get("budget_against"))
- + ":Link/%s:150" % (filters.get("budget_against")),
- _("Account") + ":Link/Account:150"
+ {
+ 'label': _(filters.get("budget_against")),
+ 'fieldtype': 'Link',
+ 'fieldname': 'budget_against',
+ 'options': filters.get('budget_against'),
+ 'width': 150
+ },
+ {
+ 'label': _('Account'),
+ 'fieldname': 'Account',
+ 'fieldtype': 'Link',
+ 'options': 'Account',
+ 'width': 150
+ }
]
group_months = False if filters["period"] == "Monthly" else True
@@ -79,7 +114,12 @@
_("Variance ") + " " + str(year[0])
]
for label in labels:
- columns.append(label + ":Float:150")
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
else:
for label in [
_("Budget") + " (%s)" + " " + str(year[0]),
@@ -95,14 +135,23 @@
else:
label = label % formatdate(from_date, format_string="MMM")
- columns.append(label + ":Float:150")
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
if filters["period"] != "Yearly":
- return columns + [
- _("Total Budget") + ":Float:150",
- _("Total Actual") + ":Float:150",
- _("Total Variance") + ":Float:150"
- ]
+ for label in [_("Total Budget"), _("Total Actual"), _("Total Variance")]:
+ columns.append({
+ 'label': label,
+ 'fieldtype': 'Float',
+ 'fieldname': frappe.scrub(label),
+ 'width': 150
+ })
+
+ return columns
else:
return columns
@@ -173,7 +222,7 @@
filters.budget_against,
filters.company,
]
- + filters.get("budget_against_filter")
+ + (filters.get("budget_against_filter") or [])
), as_dict=True)
@@ -305,3 +354,49 @@
})
return fiscal_year
+
+def get_chart_data(filters, columns, data):
+
+ if not data:
+ return None
+
+ labels = []
+
+ fiscal_year = get_fiscal_years(filters)
+ group_months = False if filters["period"] == "Monthly" else True
+
+ for year in fiscal_year:
+ for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
+ if filters['period'] == 'Yearly':
+ labels.append(year[0])
+ else:
+ if group_months:
+ label = formatdate(from_date, format_string="MMM") + "-" \
+ + formatdate(to_date, format_string="MMM")
+ labels.append(label)
+ else:
+ label = formatdate(from_date, format_string="MMM")
+ labels.append(label)
+
+ no_of_columns = len(labels)
+
+ budget_values, actual_values = [0] * no_of_columns, [0] * no_of_columns
+ for d in data:
+ values = d[2:]
+ index = 0
+
+ for i in range(no_of_columns):
+ budget_values[i] += values[index]
+ actual_values[i] += values[index+1]
+ index += 3
+
+ return {
+ 'data': {
+ 'labels': labels,
+ 'datasets': [
+ {'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
+ {'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
+ ]
+ }
+ }
+
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js
index e5d0c89..a984bf4 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.js
+++ b/erpnext/accounts/report/cash_flow/cash_flow.js
@@ -5,6 +5,8 @@
frappe.query_reports["Cash Flow"] = $.extend({},
erpnext.financial_statements);
+ erpnext.utils.add_dimensions('Cash Flow', 10);
+
// The last item in the array is the definition for Presentation Currency
// filter. It won't be used in cash flow for now so we pop it. Please take
// of this if you are working here.
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
index 38fd5fa..0947922 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js
@@ -33,7 +33,6 @@
"fieldname":"period_start_date",
"label": __("Start Date"),
"fieldtype": "Date",
- "default": frappe.datetime.nowdate(),
"hidden": 1,
"reqd": 1
},
@@ -41,7 +40,6 @@
"fieldname":"period_end_date",
"label": __("End Date"),
"fieldtype": "Date",
- "default": frappe.datetime.add_months(frappe.datetime.nowdate(), 12),
"hidden": 1,
"reqd": 1
},
@@ -106,5 +104,16 @@
value = $value.wrap("<p></p>").parent().html();
}
return value;
+ },
+ onload: function() {
+ let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ period_start_date: fy.year_start_date,
+ period_end_date: fy.year_end_date
+ });
+ });
}
}
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 7fb598b..533685d 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -19,7 +19,7 @@
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children
def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_end_date, filter_based_on, periodicity, accumulated_values=False,
- company=None, reset_period_on_fy_change=True):
+ company=None, reset_period_on_fy_change=True, ignore_fiscal_year=False):
"""Get a list of dict {"from_date": from_date, "to_date": to_date, "key": key, "label": label}
Periodicity can be (Yearly, Quarterly, Monthly)"""
@@ -56,9 +56,8 @@
to_date = add_months(start_date, months_to_add)
start_date = to_date
- if to_date == get_first_day(to_date):
- # if to_date is the first day, get the last day of previous month
- to_date = add_days(to_date, -1)
+ # Subtract one day from to_date, as it may be first day in next fiscal year or month
+ to_date = add_days(to_date, -1)
if to_date <= year_end_date:
# the normal case
@@ -67,8 +66,9 @@
# if a fiscal year ends before a 12 month period
period.to_date = year_end_date
- period.to_date_fiscal_year = get_fiscal_year(period.to_date, company=company)[0]
- period.from_date_fiscal_year_start_date = get_fiscal_year(period.from_date, company=company)[1]
+ if not ignore_fiscal_year:
+ period.to_date_fiscal_year = get_fiscal_year(period.to_date, company=company)[0]
+ period.from_date_fiscal_year_start_date = get_fiscal_year(period.from_date, company=company)[1]
period_list.append(period)
@@ -386,11 +386,43 @@
key: value
})
+ distributed_cost_center_query = ""
+ if filters and filters.get('cost_center'):
+ distributed_cost_center_query = """
+ UNION ALL
+ SELECT posting_date,
+ account,
+ debit*(DCC_allocation.percentage_allocation/100) as debit,
+ credit*(DCC_allocation.percentage_allocation/100) as credit,
+ is_opening,
+ fiscal_year,
+ debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
+ credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
+ account_currency
+ FROM `tabGL Entry`,
+ (
+ SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ AND is_cancelled = 0
+ GROUP BY parent
+ ) as DCC_allocation
+ WHERE company=%(company)s
+ {additional_conditions}
+ AND posting_date <= %(to_date)s
+ AND cost_center = DCC_allocation.parent
+ """.format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
+
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
- order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec
+ and is_cancelled = 0
+ {distributed_cost_center_query}
+ order by account, posting_date""".format(
+ additional_conditions=additional_conditions,
+ distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
if filters and filters.get('presentation_currency'):
convert_to_presentation_currency(gl_entries, get_currency(filters))
@@ -488,4 +520,4 @@
"width": 150
})
- return columns
+ return columns
\ No newline at end of file
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 2aecd6b..1fc0f79 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -164,12 +164,5 @@
]
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["General Ledger"].filters.splice(15, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('General Ledger', 15)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 6afe208..fcd36e4 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -128,18 +128,53 @@
filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book')
+ distributed_cost_center_query = ""
+ if filters and filters.get('cost_center'):
+ select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
+ credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
+
+ distributed_cost_center_query = """
+ UNION ALL
+ SELECT name as gl_entry,
+ posting_date,
+ account,
+ party_type,
+ party,
+ voucher_type,
+ voucher_no,
+ cost_center, project,
+ against_voucher_type,
+ against_voucher,
+ account_currency,
+ remarks, against,
+ is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
+ FROM `tabGL Entry`,
+ (
+ SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ GROUP BY parent
+ ) as DCC_allocation
+ WHERE company=%(company)s
+ {conditions}
+ AND posting_date <= %(to_date)s
+ AND cost_center = DCC_allocation.parent
+ """.format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
+
gl_entries = frappe.db.sql(
"""
select
name as gl_entry, posting_date, account, party_type, party,
voucher_type, voucher_no, cost_center, project,
against_voucher_type, against_voucher, account_currency,
- remarks, against, is_opening {select_fields}
+ remarks, against, is_opening, creation {select_fields}
from `tabGL Entry`
where company=%(company)s {conditions}
+ {distributed_cost_center_query}
{order_by_statement}
""".format(
- select_fields=select_fields, conditions=get_conditions(filters),
+ select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
order_by_statement=order_by_statement
),
filters, as_dict=1)
@@ -296,7 +331,7 @@
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
- if data[key].against_voucher:
+ if data[key].against_voucher and gle.against_voucher:
data[key].against_voucher += ', ' + gle.against_voucher
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js
index f88906a..b709ab9 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js
@@ -4,11 +4,18 @@
frappe.query_reports["Item-wise Purchase Register"] = {
"filters": [
{
- "fieldname":"date_range",
- "label": __("Date Range"),
- "fieldtype": "DateRange",
- "default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()],
- "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.get_today(),
+ "reqd": 1,
},
{
"fieldname": "item_code",
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 1f78c7a..3445df7 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
@@ -14,7 +14,6 @@
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {}
- filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]})
columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company)
@@ -266,13 +265,6 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
- },
- {
- 'fieldname': 'currency',
- 'label': _('Currency'),
- 'fieldtype': 'Currency',
- 'width': 80,
- 'hidden': 1
}
]
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
index 8a9c76f..39fb3ca 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
@@ -4,11 +4,18 @@
frappe.query_reports["Item-wise Sales Register"] = {
"filters": [
{
- "fieldname": "date_range",
- "label": __("Date Range"),
- "fieldtype": "DateRange",
- "default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()],
- "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.get_today(),
+ "reqd": 1,
},
{
"fieldname": "customer",
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 92a22e6..a05dcd7 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
@@ -14,7 +14,6 @@
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {}
- filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
@@ -224,7 +223,7 @@
}
]
- if filters.get('group_by') != 'Terriotory':
+ if filters.get('group_by') != 'Territory':
columns.extend([
{
'label': _("Territory"),
@@ -305,13 +304,6 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
- },
- {
- 'fieldname': 'currency',
- 'label': _('Currency'),
- 'fieldtype': 'Currency',
- 'width': 80,
- 'hidden': 1
}
]
@@ -537,6 +529,13 @@
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
+ },
+ {
+ 'fieldname': 'currency',
+ 'label': _('Currency'),
+ 'fieldtype': 'Currency',
+ 'width': 80,
+ 'hidden': 1
}
]
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index 2b946c0..1c461ef 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -6,6 +6,8 @@
frappe.query_reports["Profit and Loss Statement"] = $.extend({},
erpnext.financial_statements);
+ erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
+
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
"fieldname": "project",
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 6e9b31f..60e675f 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -105,6 +105,7 @@
def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
data = []
+ new_accounts = accounts
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
for d in accounts:
@@ -118,6 +119,19 @@
"currency": company_currency,
"based_on": based_on
}
+ if based_on == 'cost_center':
+ cost_center_doc = frappe.get_doc("Cost Center",d.name)
+ if not cost_center_doc.enable_distributed_cost_center:
+ DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
+ FROM `tabDistributed Cost Center`
+ WHERE cost_center IN %(cost_center)s
+ AND parent NOT IN %(cost_center)s
+ GROUP BY parent""",{'cost_center': [d.name]})
+ if DCC_allocation:
+ for account in new_accounts:
+ if account['name'] == DCC_allocation[0][0]:
+ for value in value_fields:
+ d[value] += account[value]*(DCC_allocation[0][1]/100)
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json
deleted file mode 100644
index 3645ec0..0000000
--- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "add_total_row": 1,
- "apply_user_permissions": 1,
- "creation": "2013-05-28 15:54:16",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2017-02-24 20:00:24.302988",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Purchase Order Items To Be Billed",
- "owner": "Administrator",
- "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.base_amount as \"Amount:Currency:100\",\n\t(`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1)) as \"Billed Amount:Currency:100\", \n\t(`tabPurchase Order Item`.base_amount - (`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1))) as \"Amount to Bill:Currency:100\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n\t`tabPurchase Order`.company as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status != \"Closed\"\n and `tabPurchase Order Item`.amount > 0\n\tand (`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1)) < `tabPurchase Order Item`.base_amount\norder by `tabPurchase Order`.transaction_date asc",
- "ref_doctype": "Purchase Invoice",
- "report_name": "Purchase Order Items To Be Billed",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Accounts User"
- },
- {
- "role": "Purchase User"
- },
- {
- "role": "Auditor"
- },
- {
- "role": "Accounts Manager"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py
deleted file mode 100644
index 99d0a36..0000000
--- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from erpnext.accounts.report.non_billed_report import get_ordered_to_be_billed_data
-
-def execute(filters=None):
- columns = get_column()
- args = get_args()
- data = get_ordered_to_be_billed_data(args)
- return columns, data
-
-def get_column():
- return [
- _("Purchase Order") + ":Link/Purchase Order:120", _("Status") + "::120", _("Date") + ":Date:100",
- _("Suplier") + ":Link/Supplier:120", _("Suplier 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",
- ]
-
-def get_args():
- return {'doctype': 'Purchase Order', 'party': 'supplier',
- 'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'}
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js
index b2b95b2..f34ea57 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.js
+++ b/erpnext/accounts/report/purchase_register/purchase_register.js
@@ -56,11 +56,4 @@
]
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Purchase Register"].filters.splice(7, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
\ No newline at end of file
+erpnext.utils.add_dimensions('Purchase Register', 7);
\ No newline at end of file
diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js
index 9dee656..85bbcea 100644
--- a/erpnext/accounts/report/sales_register/sales_register.js
+++ b/erpnext/accounts/report/sales_register/sales_register.js
@@ -68,12 +68,5 @@
]
}
-erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Sales Register"].filters.splice(7, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
-});
+erpnext.utils.add_dimensions('Sales Register', 7);
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 4ac0f65..a9fb237 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -111,7 +111,7 @@
# {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {}
gle = frappe.db.get_all('GL Entry',\
- {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]]},
+ {"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
for d in gle:
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 07752e1..9c0854c 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -102,14 +102,7 @@
"initial_depth": 3
}
- erpnext.dimension_filters.forEach((dimension) => {
- frappe.query_reports["Trial Balance"].filters.splice(6, 0 ,{
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
- });
+ erpnext.utils.add_dimensions('Trial Balance', 6);
});
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 8bd4399..5a699b6 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -71,7 +71,8 @@
opening_balances = get_opening_balances(filters)
#add filter inside list so that the query in financial_statements.py doesn't break
- filters.project = [filters.project]
+ if filters.project:
+ filters.project = [filters.project]
set_gl_entries_by_account(filters.company, filters.from_date,
filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 5165495..f6cd606 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -57,6 +57,9 @@
frappe.cache().hset("fiscal_years", company, fiscal_years)
+ if not transaction_date and not fiscal_year:
+ return fiscal_years
+
if transaction_date:
transaction_date = getdate(transaction_date)
@@ -79,6 +82,23 @@
if verbose==1: frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
+@frappe.whitelist()
+def get_fiscal_year_filter_field(company=None):
+ field = {
+ "fieldtype": "Select",
+ "options": [],
+ "operator": "Between",
+ "query_value": True
+ }
+ fiscal_years = get_fiscal_years(company=company)
+ for fiscal_year in fiscal_years:
+ field["options"].append({
+ "label": fiscal_year.name,
+ "value": fiscal_year.name,
+ "query_value": [fiscal_year.year_start_date.strftime("%Y-%m-%d"), fiscal_year.year_end_date.strftime("%Y-%m-%d")]
+ })
+ return field
+
def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
years = [f[0] for f in get_fiscal_years(date, label=_(label), company=company)]
if fiscal_year not in years:
@@ -942,4 +962,4 @@
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
- return gl_entries
\ No newline at end of file
+ return gl_entries
diff --git a/erpnext/assets/dashboard_fixtures.py b/erpnext/assets/dashboard_fixtures.py
new file mode 100644
index 0000000..7f3c1de
--- /dev/null
+++ b/erpnext/assets/dashboard_fixtures.py
@@ -0,0 +1,179 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import json
+from frappe.utils import nowdate, add_months, get_date_str
+from frappe import _
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
+from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
+
+def get_data():
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ year_start_date = get_date_str(fiscal_year.get('year_start_date'))
+ year_end_date = get_date_str(fiscal_year.get('year_end_date'))
+
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(fiscal_year, year_start_date, year_end_date),
+ "number_cards": get_number_cards(fiscal_year, year_start_date, year_end_date),
+ })
+
+def get_dashboards():
+ return [{
+ "name": "Asset",
+ "dashboard_name": "Asset",
+ "charts": [
+ { "chart": "Asset Value Analytics", "width": "Full" },
+ { "chart": "Category-wise Asset Value", "width": "Half" },
+ { "chart": "Location-wise Asset Value", "width": "Half" },
+ ],
+ "cards": [
+ {"card": "Total Assets"},
+ {"card": "New Assets (This Year)"},
+ {"card": "Asset Value"}
+ ]
+ }]
+
+def get_charts(fiscal_year, year_start_date, year_end_date):
+ company = get_company_for_dashboards()
+ return [
+ {
+ "name": "Asset Value Analytics",
+ "chart_name": _("Asset Value Analytics"),
+ "chart_type": "Report",
+ "report_name": "Fixed Asset Register",
+ "is_custom": 1,
+ "group_by_type": "Count",
+ "number_of_groups": 0,
+ "is_public": 0,
+ "timespan": "Last Year",
+ "time_interval": "Yearly",
+ "timeseries": 0,
+ "filters_json": json.dumps({
+ "company": company,
+ "status": "In Location",
+ "filter_based_on": "Fiscal Year",
+ "from_fiscal_year": fiscal_year.get('name'),
+ "to_fiscal_year": fiscal_year.get('name'),
+ "period_start_date": year_start_date,
+ "period_end_date": year_end_date,
+ "date_based_on": "Purchase Date",
+ "group_by": "--Select a group--"
+ }),
+ "type": "Bar",
+ "custom_options": json.dumps({
+ "type": "bar",
+ "barOptions": { "stacked": 1 },
+ "axisOptions": { "shortenYAxisNumbers": 1 },
+ "tooltipOptions": {}
+ }),
+ "doctype": "Dashboard Chart",
+ "y_axis": []
+ },
+ {
+ "name": "Category-wise Asset Value",
+ "chart_name": _("Category-wise Asset Value"),
+ "chart_type": "Report",
+ "report_name": "Fixed Asset Register",
+ "x_field": "asset_category",
+ "timeseries": 0,
+ "filters_json": json.dumps({
+ "company": company,
+ "status":"In Location",
+ "group_by":"Asset Category",
+ "is_existing_asset":0
+ }),
+ "type": "Donut",
+ "doctype": "Dashboard Chart",
+ "y_axis": [
+ {
+ "parent": "Category-wise Asset Value",
+ "parentfield": "y_axis",
+ "parenttype": "Dashboard Chart",
+ "y_field": "asset_value",
+ "doctype": "Dashboard Chart Field"
+ }
+ ],
+ "custom_options": json.dumps({
+ "type": "donut",
+ "height": 300,
+ "axisOptions": {"shortenYAxisNumbers": 1}
+ })
+ },
+ {
+ "name": "Location-wise Asset Value",
+ "chart_name": "Location-wise Asset Value",
+ "chart_type": "Report",
+ "report_name": "Fixed Asset Register",
+ "x_field": "location",
+ "timeseries": 0,
+ "filters_json": json.dumps({
+ "company": company,
+ "status":"In Location",
+ "group_by":"Location",
+ "is_existing_asset":0
+ }),
+ "type": "Donut",
+ "doctype": "Dashboard Chart",
+ "y_axis": [
+ {
+ "parent": "Location-wise Asset Value",
+ "parentfield": "y_axis",
+ "parenttype": "Dashboard Chart",
+ "y_field": "asset_value",
+ "doctype": "Dashboard Chart Field"
+ }
+ ],
+ "custom_options": json.dumps({
+ "type": "donut",
+ "height": 300,
+ "axisOptions": {"shortenYAxisNumbers": 1}
+ })
+ }
+ ]
+
+def get_number_cards(fiscal_year, year_start_date, year_end_date):
+ return [
+ {
+ "name": "Total Assets",
+ "label": _("Total Assets"),
+ "function": "Count",
+ "document_type": "Asset",
+ "is_public": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "filters_json": "[]",
+ "doctype": "Number Card",
+ },
+ {
+ "name": "New Assets (This Year)",
+ "label": _("New Assets (This Year)"),
+ "function": "Count",
+ "document_type": "Asset",
+ "is_public": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "filters_json": json.dumps([
+ ['Asset', 'creation', 'between', [year_start_date, year_end_date]]
+ ]),
+ "doctype": "Number Card",
+ },
+ {
+ "name": "Asset Value",
+ "label": _("Asset Value"),
+ "function": "Sum",
+ "aggregate_function_based_on": "value_after_depreciation",
+ "document_type": "Asset",
+ "is_public": 1,
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly",
+ "filters_json": "[]",
+ "doctype": "Number Card"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/assets/desk_page/assets/assets.json b/erpnext/assets/desk_page/assets/assets.json
index 0309416..94939fd 100644
--- a/erpnext/assets/desk_page/assets/assets.json
+++ b/erpnext/assets/desk_page/assets/assets.json
@@ -17,21 +17,27 @@
}
],
"category": "Modules",
- "charts": [],
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Assets",
- "modified": "2020-04-01 11:28:51.072198",
+ "modified": "2020-05-20 18:05:23.994795",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
+ "onboarding": "Assets",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
@@ -42,14 +48,19 @@
"type": "DocType"
},
{
- "label": "Asset Movement",
- "link_to": "Asset Movement",
+ "label": "Asset Category",
+ "link_to": "Asset Category",
"type": "DocType"
},
{
"label": "Fixed Asset Register",
"link_to": "Fixed Asset Register",
"type": "Report"
+ },
+ {
+ "label": "Assets 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 a53ff88..fba20c0 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -387,7 +387,8 @@
}
frm.set_value('gross_purchase_amount', item.base_net_rate + item.item_tax_amount);
frm.set_value('purchase_receipt_amount', item.base_net_rate + item.item_tax_amount);
- frm.set_value('location', item.asset_location);
+ item.asset_location && frm.set_value('location', item.asset_location);
+ frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
},
set_depreciation_rate: function(frm, row) {
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 505ba4c..2ecabe6 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -127,6 +127,8 @@
frappe.throw(_("Available-for-use Date should be after purchase date"))
def validate_gross_and_purchase_amount(self):
+ if self.is_existing_asset: return
+
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
Please do not book expense of multiple assets against one single Asset.")
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
index efb2de3..c0c2566 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
@@ -1,559 +1,140 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:asset_name",
- "beta": 0,
- "creation": "2017-10-19 16:50:22.879545",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "field:asset_name",
+ "creation": "2017-10-19 16:50:22.879545",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "asset_name",
+ "asset_category",
+ "company",
+ "column_break_3",
+ "item_code",
+ "item_name",
+ "section_break_6",
+ "maintenance_team",
+ "column_break_9",
+ "maintenance_manager",
+ "maintenance_manager_name",
+ "section_break_8",
+ "asset_maintenance_tasks"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_name",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Asset Name",
- "length": 0,
- "no_copy": 0,
- "options": "Asset",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "asset_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Asset Name",
+ "options": "Asset",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.asset_category",
- "fieldname": "asset_category",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Category",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "asset_category",
+ "fieldtype": "Read Only",
+ "label": "Asset Category"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.item_code",
- "fieldname": "item_code",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Read Only",
+ "label": "Item Code"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "asset_name.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "company",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Company",
- "length": 0,
- "no_copy": 0,
- "options": "Company",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "select_serial_no",
- "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": "Select Serial No",
- "length": 0,
- "no_copy": 0,
- "options": "Serial No",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "serial_no",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Serial No",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_team",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Maintenance Team",
+ "options": "Asset Maintenance Team",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_team",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Maintenance Team",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Team",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "maintenance_team.maintenance_manager",
- "fieldname": "maintenance_manager",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Manager",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_manager",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Maintenance Manager",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fetch_from": "maintenance_team.maintenance_manager_name",
- "fieldname": "maintenance_manager_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Manager Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_manager_name",
+ "fieldtype": "Read Only",
+ "label": "Maintenance Manager Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Tasks",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "label": "Tasks"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_maintenance_tasks",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Tasks",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Task",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "asset_maintenance_tasks",
+ "fieldtype": "Table",
+ "label": "Maintenance Tasks",
+ "options": "Asset Maintenance Task",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-05-22 17:20:54.711885",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Maintenance",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2020-05-28 20:28:32.993823",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance",
+ "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": "Quality Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Quality Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 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
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 3f04611..1869a29 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -38,10 +38,10 @@
@frappe.whitelist()
def assign_tasks(asset_maintenance_name, assign_to_member, maintenance_task, next_due_date):
- team_member = frappe.get_doc('User', assign_to_member).email
+ team_member = frappe.db.get_value('User', assign_to_member, "email")
args = {
'doctype' : 'Asset Maintenance',
- 'assign_to' : team_member,
+ 'assign_to' : [team_member],
'name' : asset_maintenance_name,
'description' : maintenance_task,
'date' : next_due_date
@@ -77,7 +77,7 @@
def update_maintenance_log(asset_maintenance, item_code, item_name, task):
asset_maintenance_log = frappe.get_value("Asset Maintenance Log", {"asset_maintenance": asset_maintenance,
- "task": task.maintenance_task, "maintenance_status": ('in',['Planned','Overdue'])})
+ "task": task.name, "maintenance_status": ('in',['Planned','Overdue'])})
if not asset_maintenance_log:
asset_maintenance_log = frappe.get_doc({
@@ -86,7 +86,7 @@
"asset_name": asset_maintenance,
"item_code": item_code,
"item_name": item_name,
- "task": task.maintenance_task,
+ "task": task.name,
"has_certificate": task.certificate_required,
"description": task.description,
"assign_to_name": task.assign_to_name,
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
index 42aecdf..7395bec 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
@@ -1,819 +1,210 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "naming_series:",
- "beta": 0,
- "creation": "2017-10-23 16:58:44.424309",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2017-10-23 16:58:44.424309",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "asset_maintenance",
+ "naming_series",
+ "asset_name",
+ "column_break_2",
+ "item_code",
+ "item_name",
+ "section_break_5",
+ "task",
+ "task_name",
+ "maintenance_type",
+ "periodicity",
+ "assign_to_name",
+ "column_break_6",
+ "due_date",
+ "completion_date",
+ "maintenance_status",
+ "section_break_12",
+ "has_certificate",
+ "certificate_attachement",
+ "section_break_6",
+ "description",
+ "column_break_9",
+ "actions_performed",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "asset_maintenance",
- "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": "Asset Maintenance",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "asset_maintenance",
+ "fieldtype": "Link",
+ "label": "Asset Maintenance",
+ "options": "Asset Maintenance"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "fieldname": "naming_series",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Series",
- "length": 0,
- "no_copy": 0,
- "options": "ACC-AML-.YYYY.-",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Series",
+ "options": "ACC-AML-.YYYY.-",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.asset_name",
- "fieldname": "asset_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Asset Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.asset_name",
+ "fieldname": "asset_name",
+ "fieldtype": "Read Only",
+ "label": "Asset Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.item_code",
- "fieldname": "item_code",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.item_code",
+ "fieldname": "item_code",
+ "fieldtype": "Read Only",
+ "label": "Item Code"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "asset_maintenance.item_name",
- "fieldname": "item_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "asset_maintenance.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Read Only",
+ "label": "Item Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "task",
- "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": "Task",
- "length": 0,
- "no_copy": 0,
- "options": "Asset Maintenance Task",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "task",
+ "fieldtype": "Link",
+ "label": "Task",
+ "options": "Asset Maintenance Task"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.maintenance_type",
- "fieldname": "maintenance_type",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Maintenance Type",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.maintenance_type",
+ "fieldname": "maintenance_type",
+ "fieldtype": "Read Only",
+ "label": "Maintenance Type"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.periodicity",
- "fieldname": "periodicity",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Periodicity",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.periodicity",
+ "fieldname": "periodicity",
+ "fieldtype": "Data",
+ "label": "Periodicity",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.assign_to_name",
- "fieldname": "assign_to_name",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Assign To",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.assign_to_name",
+ "fieldname": "assign_to_name",
+ "fieldtype": "Read Only",
+ "label": "Assign To"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.next_due_date",
- "fieldname": "due_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Due Date",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.next_due_date",
+ "fieldname": "due_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Due Date",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "completion_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Completion Date",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "completion_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Completion Date"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maintenance_status",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 1,
- "label": "Maintenance Status",
- "length": 0,
- "no_copy": 0,
- "options": "Planned\nCompleted\nCancelled\nOverdue",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "maintenance_status",
+ "fieldtype": "Select",
+ "in_standard_filter": 1,
+ "label": "Maintenance Status",
+ "options": "Planned\nCompleted\nCancelled\nOverdue",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_12",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_12",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.certificate_required",
- "fieldname": "has_certificate",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Has Certificate ",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fetch_from": "task.certificate_required",
+ "fieldname": "has_certificate",
+ "fieldtype": "Check",
+ "label": "Has Certificate "
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.has_certificate",
- "fieldname": "certificate_attachement",
- "fieldtype": "Attach",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Certificate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:doc.has_certificate",
+ "fieldname": "certificate_attachement",
+ "fieldtype": "Attach",
+ "label": "Certificate"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_6",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "section_break_6",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "task.description",
- "fieldname": "description",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fetch_from": "task.description",
+ "fieldname": "description",
+ "fieldtype": "Read Only",
+ "label": "Description",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_9",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_9",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 1,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "actions_performed",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Actions performed",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "allow_on_submit": 1,
+ "fieldname": "actions_performed",
+ "fieldtype": "Text Editor",
+ "label": "Actions performed"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "amended_from",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Amended From",
- "length": 0,
- "no_copy": 1,
- "options": "Asset Maintenance Log",
- "permlevel": 0,
- "precision": "",
- "print_hide": 1,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Asset Maintenance Log",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fetch_from": "task.maintenance_task",
+ "fieldname": "task_name",
+ "fieldtype": "Data",
+ "in_preview": 1,
+ "label": "Task Name",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-21 14:44:51.457835",
- "modified_by": "Administrator",
- "module": "Assets",
- "name": "Asset Maintenance Log",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2020-05-28 20:51:48.238397",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance Log",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 1,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Manufacturing User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
+ "amend": 1,
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "submit": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "",
- "track_changes": 1,
- "track_seen": 1,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 1
}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
index d98cc31..2a5666d 100644
--- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
+++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.py
@@ -7,5 +7,4 @@
from frappe.model.document import Document
class AssetMaintenanceTask(Document):
- def autoname(self):
- self.name = self.maintenance_task
+ pass
diff --git a/erpnext/assets/module_onboarding/assets/assets.json b/erpnext/assets/module_onboarding/assets/assets.json
new file mode 100644
index 0000000..66dd60a
--- /dev/null
+++ b/erpnext/assets/module_onboarding/assets/assets.json
@@ -0,0 +1,42 @@
+{
+ "allow_roles": [
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Maintenance User"
+ }
+ ],
+ "creation": "2020-05-08 15:10:45.571457",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-08 16:17:31.685943",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Assets",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Assets"
+ },
+ {
+ "step": "Create a Fixed Asset Item"
+ },
+ {
+ "step": "Create an Asset Category"
+ },
+ {
+ "step": "Purchase an Asset Item"
+ },
+ {
+ "step": "Create an Asset"
+ }
+ ],
+ "subtitle": "Assets, Depreciations, Repairs and more",
+ "success_message": "The Asset Module is all set up!",
+ "title": "Let's Setup Asset Management",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json b/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json
new file mode 100644
index 0000000..f5818c0
--- /dev/null
+++ b/erpnext/assets/onboarding_step/create_a_fixed_asset_item/create_a_fixed_asset_item.json
@@ -0,0 +1,16 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-08 13:20:00.259985",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 13:20:00.259985",
+ "modified_by": "Administrator",
+ "name": "Create a Fixed Asset Item",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "title": "Create a Fixed Asset Item"
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json b/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json
new file mode 100644
index 0000000..5488b1d
--- /dev/null
+++ b/erpnext/assets/onboarding_step/create_an_asset/create_an_asset.json
@@ -0,0 +1,16 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-08 13:21:53.332538",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 13:21:53.332538",
+ "modified_by": "Administrator",
+ "name": "Create an Asset",
+ "owner": "Administrator",
+ "reference_document": "Asset",
+ "title": "Create an Asset"
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json b/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json
new file mode 100644
index 0000000..3bf54af
--- /dev/null
+++ b/erpnext/assets/onboarding_step/create_an_asset_category/create_an_asset_category.json
@@ -0,0 +1,16 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-08 13:21:53.332538",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 13:21:53.332538",
+ "modified_by": "Administrator",
+ "name": "Create an Asset Category",
+ "owner": "Administrator",
+ "reference_document": "Asset Category",
+ "title": "Create an Asset Category"
+ }
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json b/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json
new file mode 100644
index 0000000..d48dd1c
--- /dev/null
+++ b/erpnext/assets/onboarding_step/introduction_to_assets/introduction_to_assets.json
@@ -0,0 +1,16 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-08 13:18:25.424715",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 16:06:16.625646",
+ "modified_by": "Administrator",
+ "name": "Introduction to Assets",
+ "owner": "Administrator",
+ "title": "Introduction to Assets",
+ "video_url": "https://www.youtube.com/watch?v=I-K8pLRmvSo"
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json b/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json
new file mode 100644
index 0000000..732ff7f
--- /dev/null
+++ b/erpnext/assets/onboarding_step/purchase_an_asset_item/purchase_an_asset_item.json
@@ -0,0 +1,16 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-08 13:21:28.208059",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-08 13:21:28.208059",
+ "modified_by": "Administrator",
+ "name": "Purchase an Asset Item",
+ "owner": "Administrator",
+ "reference_document": "Purchase Receipt",
+ "title": "Purchase an Asset Item"
+}
\ No newline at end of file
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index 91ce9ce..1a6ef54 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -21,20 +21,54 @@
reqd: 1
},
{
- fieldname:"purchase_date",
- label: __("Purchase Date"),
- fieldtype: "Date"
+ "fieldname":"filter_based_on",
+ "label": __("Period Based On"),
+ "fieldtype": "Select",
+ "options": ["Fiscal Year", "Date Range"],
+ "default": ["Fiscal Year"],
+ "reqd": 1
},
{
- fieldname:"available_for_use_date",
- label: __("Available For Use Date"),
- fieldtype: "Date"
+ "fieldname":"from_date",
+ "label": __("Start Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "reqd": 1
},
{
- fieldname:"finance_book",
- label: __("Finance Book"),
- fieldtype: "Link",
- options: "Finance Book"
+ "fieldname":"to_date",
+ "label": __("End Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.nowdate(),
+ "depends_on": "eval: doc.filter_based_on == 'Date Range'",
+ "reqd": 1
+ },
+ {
+ "fieldname":"from_fiscal_year",
+ "label": __("Start Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
+ "reqd": 1
+ },
+ {
+ "fieldname":"to_fiscal_year",
+ "label": __("End Year"),
+ "fieldtype": "Link",
+ "options": "Fiscal Year",
+ "default": frappe.defaults.get_user_default("fiscal_year"),
+ "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
+ "reqd": 1
+ },
+ {
+ "fieldname":"date_based_on",
+ "label": __("Date Based On"),
+ "fieldtype": "Select",
+ "options": ["Purchase Date", "Available For Use Date"],
+ "default": "Purchase Date",
+ "reqd": 1
},
{
fieldname:"asset_category",
@@ -42,6 +76,26 @@
fieldtype: "Link",
options: "Asset Category"
},
+ {
+ fieldname:"finance_book",
+ label: __("Finance Book"),
+ fieldtype: "Link",
+ options: "Finance Book"
+ },
+ {
+ fieldname:"cost_center",
+ label: __("Cost Center"),
+ fieldtype: "Link",
+ options: "Cost Center"
+ },
+ {
+ fieldname:"group_by",
+ label: __("Group By"),
+ fieldtype: "Select",
+ options: ["--Select a group--", "Asset Category", "Location"],
+ default: "--Select a group--",
+ reqd: 1
+ },
{
fieldname:"is_existing_asset",
label: __("Is Existing Asset"),
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 fa2fe7b..af08a2a 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -4,122 +4,39 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cstr, today, flt
+from frappe.utils import cstr, today, flt, add_years, formatdate, getdate
+from erpnext.accounts.report.financial_statements import get_period_list, get_fiscal_year_data, validate_fiscal_year
def execute(filters=None):
filters = frappe._dict(filters or {})
columns = get_columns(filters)
data = get_data(filters)
- return columns, data
+ chart = prepare_chart_data(data, filters) if filters.get("group_by") not in ("Asset Category", "Location") else {}
-def get_columns(filters):
- return [
- {
- "label": _("Asset Id"),
- "fieldtype": "Link",
- "fieldname": "asset_id",
- "options": "Asset",
- "width": 100
- },
- {
- "label": _("Asset Name"),
- "fieldtype": "Data",
- "fieldname": "asset_name",
- "width": 140
- },
- {
- "label": _("Asset Category"),
- "fieldtype": "Link",
- "fieldname": "asset_category",
- "options": "Asset Category",
- "width": 100
- },
- {
- "label": _("Status"),
- "fieldtype": "Data",
- "fieldname": "status",
- "width": 90
- },
- {
- "label": _("Purchase Date"),
- "fieldtype": "Date",
- "fieldname": "purchase_date",
- "width": 90
- },
- {
- "label": _("Available For Use Date"),
- "fieldtype": "Date",
- "fieldname": "available_for_use_date",
- "width": 90
- },
- {
- "label": _("Gross Purchase Amount"),
- "fieldname": "gross_purchase_amount",
- "options": "Currency",
- "width": 90
- },
- {
- "label": _("Asset Value"),
- "fieldname": "asset_value",
- "options": "Currency",
- "width": 90
- },
- {
- "label": _("Opening Accumulated Depreciation"),
- "fieldname": "opening_accumulated_depreciation",
- "options": "Currency",
- "width": 90
- },
- {
- "label": _("Depreciated Amount"),
- "fieldname": "depreciated_amount",
- "options": "Currency",
- "width": 90
- },
- {
- "label": _("Cost Center"),
- "fieldtype": "Link",
- "fieldname": "cost_center",
- "options": "Cost Center",
- "width": 100
- },
- {
- "label": _("Department"),
- "fieldtype": "Link",
- "fieldname": "department",
- "options": "Department",
- "width": 100
- },
- {
- "label": _("Vendor Name"),
- "fieldtype": "Data",
- "fieldname": "vendor_name",
- "width": 100
- },
- {
- "label": _("Location"),
- "fieldtype": "Link",
- "fieldname": "location",
- "options": "Location",
- "width": 100
- },
- ]
+ return columns, data, None, chart
def get_conditions(filters):
conditions = { 'docstatus': 1 }
status = filters.status
- date = filters.date
+ date_field = frappe.scrub(filters.date_based_on or "Purchase Date")
if filters.get('company'):
conditions["company"] = filters.company
- if filters.get('purchase_date'):
- conditions["purchase_date"] = ('<=', filters.get('purchase_date'))
- if filters.get('available_for_use_date'):
- conditions["available_for_use_date"] = ('<=', filters.get('available_for_use_date'))
+ if filters.filter_based_on == "Date Range":
+ conditions[date_field] = ["between", [filters.from_date, filters.to_date]]
+ if filters.filter_based_on == "Fiscal Year":
+ fiscal_year = get_fiscal_year_data(filters.from_fiscal_year, filters.to_fiscal_year)
+ validate_fiscal_year(fiscal_year, filters.from_fiscal_year, filters.to_fiscal_year)
+ filters.year_start_date = getdate(fiscal_year.year_start_date)
+ filters.year_end_date = getdate(fiscal_year.year_end_date)
+
+ conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
if filters.get('is_existing_asset'):
conditions["is_existing_asset"] = filters.get('is_existing_asset')
if filters.get('asset_category'):
conditions["asset_category"] = filters.get('asset_category')
+ if filters.get('cost_center'):
+ conditions["cost_center"] = filters.get('cost_center')
# In Store assets are those that are not sold or scrapped
operand = 'not in'
@@ -139,18 +56,28 @@
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
- assets_record = frappe.db.get_all("Asset",
- filters=conditions,
- fields=["name", "asset_name", "department", "cost_center", "purchase_receipt",
+ group_by = frappe.scrub(filters.get("group_by"))
+
+ if group_by == "asset_category":
+ fields = ["asset_category", "gross_purchase_amount", "opening_accumulated_depreciation"]
+ assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by)
+
+ elif group_by == "location":
+ fields = ["location", "gross_purchase_amount", "opening_accumulated_depreciation"]
+ assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by)
+
+ else:
+ fields = ["name as asset_id", "asset_name", "status", "department", "cost_center", "purchase_receipt",
"asset_category", "purchase_date", "gross_purchase_amount", "location",
- "available_for_use_date", "status", "purchase_invoice", "opening_accumulated_depreciation"])
+ "available_for_use_date", "purchase_invoice", "opening_accumulated_depreciation"]
+ assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
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.name,
+ "asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
@@ -158,7 +85,7 @@
"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.name) or 0.0,
+ "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,
@@ -169,8 +96,39 @@
return data
+def prepare_chart_data(data, filters):
+ labels_values_map = {}
+ date_field = frappe.scrub(filters.date_based_on)
+
+ period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
+ filters.from_date, filters.to_date, filters.filter_based_on, "Monthly", company=filters.company)
+
+ for d in period_list:
+ labels_values_map.setdefault(d.get('label'), frappe._dict({'asset_value': 0, 'depreciated_amount': 0}))
+
+ for d in data:
+ date = d.get(date_field)
+ belongs_to_month = formatdate(date, "MMM YYYY")
+
+ labels_values_map[belongs_to_month].asset_value += d.get("asset_value")
+ labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount")
+
+ return {
+ "data" : {
+ "labels": labels_values_map.keys(),
+ "datasets": [
+ { 'name': _('Asset Value'), 'values': [d.get("asset_value") for d in labels_values_map.values()] },
+ { 'name': _('Depreciatied Amount'), 'values': [d.get("depreciated_amount") for d in labels_values_map.values()] }
+ ]
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ },
+ }
+
def get_finance_book_value_map(filters):
- date = filters.get('purchase_date') or filters.get('available_for_use_date') or today()
+ date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
return frappe._dict(frappe.db.sql(''' Select
parent, SUM(depreciation_amount)
@@ -201,3 +159,139 @@
AND pii.is_fixed_asset=1
AND pi.docstatus=1
AND pi.is_return=0'''))
+
+def get_columns(filters):
+ if filters.get("group_by") in ["Asset Category", "Location"]:
+ return [
+ {
+ "label": _("{}").format(filters.get("group_by")),
+ "fieldtype": "Link",
+ "fieldname": frappe.scrub(filters.get("group_by")),
+ "options": filters.get("group_by"),
+ "width": 120
+ },
+ {
+ "label": _("Gross Purchase Amount"),
+ "fieldname": "gross_purchase_amount",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ },
+ {
+ "label": _("Opening Accumulated Depreciation"),
+ "fieldname": "opening_accumulated_depreciation",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 90
+ },
+ {
+ "label": _("Depreciated Amount"),
+ "fieldname": "depreciated_amount",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ },
+ {
+ "label": _("Asset Value"),
+ "fieldname": "asset_value",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ }
+ ]
+
+ return [
+ {
+ "label": _("Asset Id"),
+ "fieldtype": "Link",
+ "fieldname": "asset_id",
+ "options": "Asset",
+ "width": 60
+ },
+ {
+ "label": _("Asset Name"),
+ "fieldtype": "Data",
+ "fieldname": "asset_name",
+ "width": 140
+ },
+ {
+ "label": _("Asset Category"),
+ "fieldtype": "Link",
+ "fieldname": "asset_category",
+ "options": "Asset Category",
+ "width": 100
+ },
+ {
+ "label": _("Status"),
+ "fieldtype": "Data",
+ "fieldname": "status",
+ "width": 80
+ },
+ {
+ "label": _("Purchase Date"),
+ "fieldtype": "Date",
+ "fieldname": "purchase_date",
+ "width": 90
+ },
+ {
+ "label": _("Available For Use Date"),
+ "fieldtype": "Date",
+ "fieldname": "available_for_use_date",
+ "width": 90
+ },
+ {
+ "label": _("Gross Purchase Amount"),
+ "fieldname": "gross_purchase_amount",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ },
+ {
+ "label": _("Asset Value"),
+ "fieldname": "asset_value",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ },
+ {
+ "label": _("Opening Accumulated Depreciation"),
+ "fieldname": "opening_accumulated_depreciation",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 90
+ },
+ {
+ "label": _("Depreciated Amount"),
+ "fieldname": "depreciated_amount",
+ "fieldtype": "Currency",
+ "options": "company:currency",
+ "width": 100
+ },
+ {
+ "label": _("Cost Center"),
+ "fieldtype": "Link",
+ "fieldname": "cost_center",
+ "options": "Cost Center",
+ "width": 100
+ },
+ {
+ "label": _("Department"),
+ "fieldtype": "Link",
+ "fieldname": "department",
+ "options": "Department",
+ "width": 100
+ },
+ {
+ "label": _("Vendor Name"),
+ "fieldtype": "Data",
+ "fieldname": "vendor_name",
+ "width": 100
+ },
+ {
+ "label": _("Location"),
+ "fieldtype": "Link",
+ "fieldname": "location",
+ "options": "Location",
+ "width": 100
+ },
+ ]
\ No newline at end of file
diff --git a/erpnext/buying/dashboard_fixtures.py b/erpnext/buying/dashboard_fixtures.py
new file mode 100644
index 0000000..172c936
--- /dev/null
+++ b/erpnext/buying/dashboard_fixtures.py
@@ -0,0 +1,212 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import json
+from frappe import _
+from frappe.utils import nowdate
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
+
+def get_data():
+
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ fiscal_year_name = fiscal_year.get("name")
+ start_date = str(fiscal_year.get("year_start_date"))
+ end_date = str(fiscal_year.get("year_end_date"))
+
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(company, fiscal_year_name, start_date, end_date),
+ "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
+ })
+
+def get_company_for_dashboards():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company_list = frappe.get_list("Company")
+ if company_list:
+ return company_list[0].name
+ return None
+
+def get_dashboards():
+ return [{
+ "name": "Buying",
+ "dashboard_name": "Buying",
+ "charts": [
+ { "chart": "Purchase Order Trends", "width": "Full"},
+ { "chart": "Material Request Analysis", "width": "Half"},
+ { "chart": "Purchase Order Analysis", "width": "Half"},
+ { "chart": "Top Suppliers", "width": "Full"}
+ ],
+ "cards": [
+ { "card": "Annual Purchase"},
+ { "card": "Purchase Orders to Receive"},
+ { "card": "Purchase Orders to Bill"},
+ { "card": "Active Suppliers"}
+ ]
+ }]
+
+def get_charts(company, fiscal_year_name, start_date, end_date):
+ return [
+ {
+ "name": "Purchase Order Analysis",
+ "chart_name": _("Purchase Order Analysis"),
+ "chart_type": "Report",
+ "custom_options": json.dumps({
+ "type": "donut",
+ "height": 300,
+ "axisOptions": {"shortenYAxisNumbers": 1}
+ }),
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "from_date": start_date,
+ "to_date": end_date
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Purchase Order Analysis",
+ "type": "Donut"
+ },
+ {
+ "name": "Material Request Analysis",
+ "chart_name": _("Material Request Analysis"),
+ "chart_type": "Group By",
+ "custom_options": json.dumps({"height": 300}),
+ "doctype": "Dashboard Chart",
+ "document_type": "Material Request",
+ "filters_json": json.dumps(
+ [["Material Request", "status", "not in", ["Draft", "Cancelled", "Stopped", None], False],
+ ["Material Request", "material_request_type", "=", "Purchase", False],
+ ["Material Request", "company", "=", company.name, False],
+ ["Material Request", "docstatus", "=", 1, False],
+ ["Material Request", "transaction_date", "Between", [start_date, end_date], False]]
+ ),
+ "group_by_based_on": "status",
+ "group_by_type": "Count",
+ "is_custom": 0,
+ "is_public": 1,
+ "number_of_groups": 0,
+ "owner": "Administrator",
+ "type": "Donut"
+ },
+ {
+ "name": "Purchase Order Trends",
+ "chart_name": _("Purchase Order Trends"),
+ "chart_type": "Report",
+ "custom_options": json.dumps({
+ "type": "line",
+ "axisOptions": {"shortenYAxisNumbers": 1},
+ "tooltipOptions": {},
+ "lineOptions": {
+ "regionFill": 1
+ }
+ }),
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "period": "Monthly",
+ "fiscal_year": fiscal_year_name,
+ "period_based_on": "posting_date",
+ "based_on": "Item"
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Purchase Order Trends",
+ "type": "Line"
+ },
+ {
+ "name": "Top Suppliers",
+ "chart_name": _("Top Suppliers"),
+ "chart_type": "Report",
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "period": "Monthly",
+ "fiscal_year": fiscal_year_name,
+ "period_based_on": "posting_date",
+ "based_on": "Supplier"
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Purchase Receipt Trends",
+ "type": "Bar"
+ }
+ ]
+
+def get_number_cards(company, fiscal_year_name, start_date, end_date):
+ return [
+ {
+ "name": "Annual Purchase",
+ "aggregate_function_based_on": "base_net_total",
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "filters_json": json.dumps([
+ ["Purchase Order", "transaction_date", "Between", [start_date, end_date], False],
+ ["Purchase Order", "status", "not in", ["Draft", "Cancelled", "Closed", None], False],
+ ["Purchase Order", "docstatus", "=", 1, False],
+ ["Purchase Order", "company", "=", company.name, False],
+ ["Purchase Order", "transaction_date", "Between", [start_date,end_date], False]
+ ]),
+ "function": "Sum",
+ "is_public": 1,
+ "label": _("Annual Purchase"),
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "name": "Purchase Orders to Receive",
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "filters_json": json.dumps([
+ ["Purchase Order", "status", "in", ["To Receive and Bill", "To Receive", None], False],
+ ["Purchase Order", "docstatus", "=", 1, False],
+ ["Purchase Order", "company", "=", company.name, False]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Purchase Orders to Receive"),
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ },
+ {
+ "name": "Purchase Orders to Bill",
+ "doctype": "Number Card",
+ "document_type": "Purchase Order",
+ "filters_json": json.dumps([
+ ["Purchase Order", "status", "in", ["To Receive and Bill", "To Bill", None], False],
+ ["Purchase Order", "docstatus", "=", 1, False],
+ ["Purchase Order", "company", "=", company.name, False]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Purchase Orders to Bill"),
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ },
+ {
+ "name": "Active Suppliers",
+ "doctype": "Number Card",
+ "document_type": "Supplier",
+ "filters_json": json.dumps([["Supplier", "disabled", "=", "0"]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": "Active Suppliers",
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json
index 5e764cf..bddb957 100644
--- a/erpnext/buying/desk_page/buying/buying.json
+++ b/erpnext/buying/desk_page/buying/buying.json
@@ -2,18 +2,13 @@
"cards": [
{
"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]"
+ "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": "Purchasing",
- "links": "[\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 ],\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\": \"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 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 \"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 \"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]"
+ "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,
@@ -22,73 +17,97 @@
},
{
"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\": \"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\": \"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 {\n \"is_query_report\": true,\n \"label\": \"Requested Items To Be Ordered\",\n \"name\": \"Requested Items To Be Ordered\",\n \"onboard\": 1,\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n }\n]"
+ "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\": \"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]"
+ "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\": \"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\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item 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": "Expenses",
- "label": "Expenses"
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Buying",
- "modified": "2020-04-01 11:28:51.192097",
+ "modified": "2020-05-28 13:32:49.960574",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
+ "onboarding": "Buying",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
- "format": "{} Unpaid",
- "label": "Purchase Invoice",
- "link_to": "Purchase Invoice",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Unpaid\"\n}",
+ "color": "#cef6d1",
+ "format": "{} Available",
+ "label": "Item",
+ "link_to": "Item",
+ "stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType"
},
{
- "format": "{} to receive",
+ "color": "#ffe8cd",
+ "format": "{} Pending",
+ "label": "Material Request",
+ "link_to": "Material Request",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} To Receive",
"label": "Purchase Order",
"link_to": "Purchase Order",
- "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"To Receive\"\n}",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\":[\"in\", [\"To Receive\", \"To Receive and Bill\"]]\n}",
"type": "DocType"
},
{
- "label": "Supplier Quotation",
- "link_to": "Supplier Quotation",
- "type": "DocType"
- },
- {
- "label": "Accounts Payable",
- "link_to": "Accounts Payable",
+ "label": "Purchase Analytics",
+ "link_to": "Purchase Analytics",
"type": "Report"
},
{
- "label": "Purchase Register",
- "link_to": "Purchase Register",
+ "label": "Purchase Order Analysis",
+ "link_to": "Purchase Order Analysis",
"type": "Report"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Buying",
+ "type": "Dashboard"
}
- ]
+ ],
+ "shortcuts_label": ""
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js
index 403b1c9..01b40cd 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.js
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.js
@@ -6,3 +6,26 @@
// }
});
+
+frappe.tour['Buying Settings'] = [
+ {
+ fieldname: "supp_master_name",
+ title: "Supplier Naming By",
+ description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a>" + __(" choose the 'Naming Series' option."),
+ },
+ {
+ fieldname: "buying_price_list",
+ title: "Default Buying Price List",
+ description: __("Configure the default Price List when creating a new Buying transaction, the default is set as 'Standard Buying'. Item prices will be fetched from this Price List.")
+ },
+ {
+ fieldname: "po_required",
+ title: "Purchase Order Required for Purchase Invoice & Receipt Creation",
+ description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice or Receipt without creating a Purchase Order first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Order' checkbox in supplier master.")
+ },
+ {
+ fieldname: "pr_required",
+ title: "Purchase Receipt Required for Purchase Invoice Creation",
+ description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in supplier master.")
+ }
+];
\ No newline at end of file
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index a492519..a0ab2a0 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2013-06-25 11:04:03",
"description": "Settings for Buying Module",
"doctype": "DocType",
"document_type": "Other",
+ "engine": "InnoDB",
"field_order": [
"supp_master_name",
"supplier_group",
@@ -44,13 +46,13 @@
{
"fieldname": "po_required",
"fieldtype": "Select",
- "label": "Purchase Order Required",
+ "label": "Purchase Order Required for Purchase Invoice & Receipt Creation",
"options": "No\nYes"
},
{
"fieldname": "pr_required",
"fieldtype": "Select",
- "label": "Purchase Receipt Required",
+ "label": "Purchase Receipt Required for Purchase Invoice Creation",
"options": "No\nYes"
},
{
@@ -92,7 +94,8 @@
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
- "modified": "2019-08-20 13:13:09.055189",
+ "links": [],
+ "modified": "2020-05-15 14:49:32.513611",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@@ -107,5 +110,7 @@
"share": 1,
"write": 1
}
- ]
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ 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 a4f60fb..13f5cb0 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -38,6 +38,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -135,7 +137,9 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -145,7 +149,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -157,7 +163,9 @@
"options": "PUR-ORD-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -170,14 +178,18 @@
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"description": "Fetch items based on Default Supplier.",
"fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button",
- "label": "Get Items from Open Material Requests"
+ "label": "Get Items from Open Material Requests",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -186,7 +198,9 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -198,13 +212,17 @@
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -216,27 +234,35 @@
"oldfieldname": "transaction_date",
"oldfieldtype": "Date",
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "schedule_date",
"fieldtype": "Date",
- "label": "Required By"
+ "label": "Required By",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.docstatus===1",
"fieldname": "order_confirmation_no",
"fieldtype": "Data",
- "label": "Order Confirmation No"
+ "label": "Order Confirmation No",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.order_confirmation_no",
"fieldname": "order_confirmation_date",
"fieldtype": "Date",
- "label": "Order Confirmation Date"
+ "label": "Order Confirmation Date",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -248,19 +274,25 @@
"oldfieldtype": "Data",
"options": "Purchase Order",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "drop_ship",
"fieldtype": "Section Break",
- "label": "Drop Ship"
+ "label": "Drop Ship",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -268,31 +300,41 @@
"fieldtype": "Data",
"label": "Customer Name",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_19",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_person",
"fieldtype": "Link",
"label": "Customer Contact",
- "options": "Contact"
+ "options": "Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_display",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_mobile",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Customer Mobile No",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "customer_contact_email",
@@ -300,46 +342,60 @@
"hidden": 1,
"label": "Customer Contact Email",
"options": "Email",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
@@ -347,32 +403,42 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -382,7 +448,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "conversion_rate",
@@ -392,18 +460,24 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "cb_price_list",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "buying_price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "price_list_currency",
@@ -411,14 +485,18 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -427,11 +505,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Warehouse' in each row of the Items table.",
@@ -439,11 +521,15 @@
"fieldtype": "Link",
"label": "Set Target Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -452,25 +538,33 @@
"in_standard_filter": 1,
"label": "Supply Raw Materials",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -480,26 +574,34 @@
"oldfieldname": "po_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_break_48",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Purchase Order Pricing Rule",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible_depends_on": "supplied_items",
"fieldname": "raw_material_details",
"fieldtype": "Section Break",
- "label": "Raw Materials Supplied"
+ "label": "Raw Materials Supplied",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
@@ -509,17 +611,23 @@
"oldfieldtype": "Table",
"options": "Purchase Order Item Supplied",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sb_last_purchase",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -527,7 +635,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -538,18 +648,24 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_26",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -559,20 +675,26 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -581,22 +703,30 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
"options": "Shipping Rule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_52",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -604,13 +734,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -619,13 +753,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -635,7 +773,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -645,7 +785,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -656,11 +798,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_39",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_added",
@@ -670,7 +816,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -680,7 +828,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -688,14 +838,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "discount_section",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -703,7 +857,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -711,28 +867,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_45",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals_section",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -743,7 +909,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -752,7 +920,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "In Words will be visible once you save the Purchase Order.",
@@ -762,7 +932,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounded_total",
@@ -772,12 +944,16 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
- "oldfieldtype": "Column Break"
+ "oldfieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "grand_total",
@@ -787,7 +963,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -796,20 +974,26 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -818,7 +1002,9 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "advance_paid",
@@ -827,19 +1013,25 @@
"no_copy": 1,
"options": "party_account_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
- "label": "Payment Terms"
+ "label": "Payment Terms",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"label": "Payment Terms Template",
- "options": "Payment Terms Template"
+ "options": "Payment Terms Template",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "payment_schedule",
@@ -847,7 +1039,9 @@
"label": "Payment Schedule",
"no_copy": 1,
"options": "Payment Schedule",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -856,7 +1050,9 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -865,21 +1061,27 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "More Information",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -894,7 +1096,9 @@
"print_hide": 1,
"read_only": 1,
"reqd": 1,
- "search_index": 1
+ "search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "ref_sq",
@@ -905,7 +1109,9 @@
"oldfieldname": "ref_sq",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "party_account_currency",
@@ -915,18 +1121,24 @@
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_order_reference",
"fieldtype": "Link",
"label": "Inter Company Order Reference",
"options": "Sales Order",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_74",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -936,7 +1148,9 @@
"label": "% Received",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.__islocal",
@@ -946,7 +1160,9 @@
"label": "% Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -956,6 +1172,8 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -966,7 +1184,9 @@
"oldfieldname": "letter_head",
"oldfieldtype": "Select",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -978,11 +1198,15 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_86",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -990,19 +1214,25 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
- "label": "Subscription Section"
+ "label": "Subscription Section",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1010,7 +1240,9 @@
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -1018,11 +1250,15 @@
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -1031,44 +1267,72 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval: doc.auto_repeat",
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
- "label": "Update Auto Repeat Reference"
+ "label": "Update Auto Repeat Reference",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
- "options": "Tax Category"
+ "options": "Tax Category",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "supplied_items",
"fieldname": "set_reserve_warehouse",
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "tracking_section",
"fieldtype": "Section Break",
- "label": "Tracking"
+ "label": "Tracking",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_75",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-24 12:13:14.186280",
+ "modified": "2020-06-13 22:25:47.333850",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1112,6 +1376,12 @@
"read": 1,
"role": "Purchase Manager",
"write": 1
+ },
+ {
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Accounts User"
}
],
"search_fields": "status, transaction_date, supplier,grand_total",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index f62df20..c7efb8a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -71,6 +71,15 @@
"compare_fields": [["project", "="], ["item_code", "="],
["uom", "="], ["conversion_factor", "="]],
"is_child_table": True
+ },
+ "Material Request": {
+ "ref_dn_field": "material_request",
+ "compare_fields": [["company", "="]],
+ },
+ "Material Request Item": {
+ "ref_dn_field": "material_request_item",
+ "compare_fields": [["project", "="], ["item_code", "="]],
+ "is_child_table": True
}
})
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 1712369..813286f 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -118,7 +118,7 @@
self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
-
+
def test_add_new_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -144,7 +144,7 @@
self.assertEquals(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
-
+
def test_remove_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@@ -185,6 +185,23 @@
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
+ def test_update_child_qty_rate_perm(self):
+ po = create_purchase_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Purchase Order', trans_item, po.name)
+ frappe.set_user("Administrator")
+
def test_update_qty(self):
po = create_purchase_order()
@@ -689,7 +706,7 @@
po.save()
self.assertEqual(po.schedule_date, add_days(nowdate(), 2))
-
+
def test_po_optional_blanket_order(self):
"""
Expected result: Blanket order Ordered Quantity should only be affected on Purchase Order with against_blanket_order = 1.
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 95db33b..dfdb487 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -25,6 +25,7 @@
self.validate_duplicate_supplier()
self.validate_supplier_list()
validate_for_items(self)
+ super(RequestforQuotation, self).set_qty_as_per_stock_uom()
self.update_email_id()
def validate_duplicate_supplier(self):
@@ -278,6 +279,7 @@
"description": data.description,
"qty": data.qty,
"rate": data.rate,
+ "conversion_factor": data.conversion_factor if data.conversion_factor else None,
"supplier_part_no": frappe.db.get_value("Item Supplier", {'parent': data.item_code, 'supplier': supplier}, "supplier_part_no"),
"warehouse": data.warehouse or '',
"request_for_quotation_item": data.name,
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index dbd9f02..3de9526 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -6,12 +6,14 @@
import unittest
import frappe
-from erpnext.templates.pages.rfq import check_supplier_has_docname_access
from frappe.utils import nowdate
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.templates.pages.rfq import check_supplier_has_docname_access
+from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
+from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
class TestRequestforQuotation(unittest.TestCase):
def test_quote_status(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
rfq = make_request_for_quotation()
self.assertEqual(rfq.get('suppliers')[0].quote_status, 'Pending')
@@ -31,7 +33,6 @@
self.assertEqual(rfq.get('suppliers')[1].quote_status, 'No Quote')
def test_make_supplier_quotation(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
rfq = make_request_for_quotation()
sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier)
@@ -51,15 +52,13 @@
self.assertEqual(sq1.get('items')[0].qty, 5)
def test_make_supplier_quotation_with_special_characters(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
-
frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1)
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = "_Test Supplier '1"
supplier.supplier_group = "_Test Supplier Group"
supplier.insert()
- rfq = make_request_for_quotation(supplier_wt_appos)
+ rfq = make_request_for_quotation(supplier_data=supplier_wt_appos)
sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier"))
sq.submit()
@@ -76,7 +75,6 @@
frappe.form_dict.name = None
def test_make_supplier_quotation_from_portal(self):
- from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
rfq = make_request_for_quotation()
rfq.get('items')[0].rate = 100
rfq.supplier = rfq.suppliers[0].supplier
@@ -90,12 +88,34 @@
self.assertEqual(supplier_quotation_doc.get('items')[0].qty, 5)
self.assertEqual(supplier_quotation_doc.get('items')[0].amount, 500)
+ def test_make_multi_uom_supplier_quotation(self):
+ item_code = "_Test Multi UOM RFQ Item"
+ if not frappe.db.exists('Item', item_code):
+ item = make_item(item_code, {'stock_uom': '_Test UOM'})
+ row = item.append('uoms', {
+ 'uom': 'Kg',
+ 'conversion_factor': 2
+ })
+ row.db_update()
-def make_request_for_quotation(supplier_data=None):
+ rfq = make_request_for_quotation(item_code="_Test Multi UOM RFQ Item", uom="Kg", conversion_factor=2)
+ rfq.get('items')[0].rate = 100
+ rfq.supplier = rfq.suppliers[0].supplier
+
+ self.assertEqual(rfq.items[0].stock_qty, 10)
+
+ supplier_quotation_name = create_supplier_quotation(rfq)
+ supplier_quotation = frappe.get_doc('Supplier Quotation', supplier_quotation_name)
+
+ self.assertEqual(supplier_quotation.items[0].qty, 5)
+ self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
+
+def make_request_for_quotation(**args):
"""
:param supplier_data: List containing supplier data
"""
- supplier_data = supplier_data if supplier_data else get_supplier_data()
+ args = frappe._dict(args)
+ supplier_data = args.get("supplier_data") if args.get("supplier_data") else get_supplier_data()
rfq = frappe.new_doc('Request for Quotation')
rfq.transaction_date = nowdate()
rfq.status = 'Draft'
@@ -106,11 +126,13 @@
rfq.append('suppliers', data)
rfq.append("items", {
- "item_code": "_Test Item",
+ "item_code": args.item_code or "_Test Item",
"description": "_Test Item",
- "uom": "_Test UOM",
- "qty": 5,
- "warehouse": "_Test Warehouse - _TC",
+ "uom": args.uom or "_Test UOM",
+ "stock_uom": args.stock_uom or "_Test UOM",
+ "qty": args.qty or 5,
+ "conversion_factor": args.conversion_factor or 1.0,
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
"schedule_date": nowdate()
})
diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
index 0159df9..408f49f 100644
--- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
+++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2016-02-25 08:04:02.452958",
"doctype": "DocType",
@@ -9,6 +10,7 @@
"supplier_part_no",
"column_break_3",
"item_name",
+ "schedule_date",
"section_break_5",
"description",
"item_group",
@@ -18,9 +20,11 @@
"image_view",
"quantity",
"qty",
+ "stock_uom",
"col_break2",
- "schedule_date",
"uom",
+ "conversion_factor",
+ "stock_qty",
"warehouse_and_reference",
"warehouse",
"project_name",
@@ -33,7 +37,7 @@
"fields": [
{
"bold": 1,
- "columns": 3,
+ "columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
@@ -98,7 +102,7 @@
{
"fieldname": "quantity",
"fieldtype": "Section Break",
- "label": "Quantity"
+ "label": "Quantity & Stock"
},
{
"bold": 1,
@@ -129,12 +133,12 @@
{
"fieldname": "uom",
"fieldtype": "Link",
+ "in_list_view": 1,
"label": "UOM",
"oldfieldname": "uom",
"oldfieldtype": "Link",
"options": "UOM",
"print_width": "100px",
- "read_only": 1,
"reqd": 1,
"width": "100px"
},
@@ -144,7 +148,7 @@
"label": "Warehouse and Reference"
},
{
- "columns": 3,
+ "columns": 2,
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
@@ -202,6 +206,7 @@
},
{
"allow_on_submit": 1,
+ "default": "0",
"fieldname": "page_break",
"fieldtype": "Check",
"label": "Page Break",
@@ -219,10 +224,36 @@
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "UOM Conversion Factor",
+ "print_hide": 1,
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "stock_qty",
+ "fieldtype": "Float",
+ "label": "Qty as per Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"istable": 1,
- "modified": "2019-05-01 17:50:23.703801",
+ "links": [],
+ "modified": "2020-06-12 19:10:36.333441",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation Item",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 3bc441a..7db1516 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -761,7 +761,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nSubmitted\nStopped\nCancelled",
+ "options": "\nDraft\nSubmitted\nStopped\nCancelled\nExpired",
"print_hide": 1,
"read_only": 1,
"reqd": 1,
@@ -803,7 +803,7 @@
"idx": 29,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-15 11:44:52.958022",
+ "modified": "2020-05-15 21:24:12.639482",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json
new file mode 100644
index 0000000..8fe2f38
--- /dev/null
+++ b/erpnext/buying/module_onboarding/buying/buying.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-06 15:56:35.049205",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-27 17:17:52.075947",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Buying",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Buying"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Setup your Warehouse"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Create a Material Request"
+ },
+ {
+ "step": "Create your first Purchase Order"
+ },
+ {
+ "step": "Buying Settings"
+ }
+ ],
+ "subtitle": "Products, Purchases, Analysis and more.",
+ "success_message": "The Buying Module is all set up!",
+ "title": "Let's Set Up the Buying Module.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/buying_settings/buying_settings.json b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
new file mode 100644
index 0000000..a788ccd
--- /dev/null
+++ b/erpnext/buying/onboarding_step/buying_settings/buying_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-06 15:53:44.667414",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:06.323797",
+ "modified_by": "Administrator",
+ "name": "Buying Settings",
+ "owner": "Administrator",
+ "reference_document": "Buying Settings",
+ "show_full_form": 0,
+ "title": "Configure Buying Settings.",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
new file mode 100644
index 0000000..9dc493d
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-15 14:39:09.818764",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-15 14:39:09.818764",
+ "modified_by": "Administrator",
+ "name": "Create a Material Request",
+ "owner": "Administrator",
+ "reference_document": "Material Request",
+ "show_full_form": 1,
+ "title": "Create a Material Request",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_product/create_a_product.json b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..d2068e1
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:16:06.624554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:02.489949",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
new file mode 100644
index 0000000..9dbed23
--- /dev/null
+++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:17:49.976035",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:31:56.856112",
+ "modified_by": "Administrator",
+ "name": "Create your first Purchase Order",
+ "owner": "Administrator",
+ "reference_document": "Purchase Order",
+ "show_full_form": 0,
+ "title": "Create your first Purchase Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
new file mode 100644
index 0000000..fd98fdd
--- /dev/null
+++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-06 15:37:09.477765",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:25:08.509900",
+ "modified_by": "Administrator",
+ "name": "Introduction to Buying",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to Buying",
+ "validate_action": 1,
+ "video_url": "https://youtu.be/efFajTTQBa8"
+}
\ No newline at end of file
diff --git a/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
new file mode 100644
index 0000000..557c905
--- /dev/null
+++ b/erpnext/buying/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-19 18:54:19.383397",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:54:19.383397",
+ "modified_by": "Administrator",
+ "name": "Setup your Warehouse",
+ "owner": "Administrator",
+ "path": "Tree/Warehouse",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Setup your Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index 3966879..88a865f 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
+from frappe.utils import flt
def execute(filters=None):
columns = get_columns(filters)
@@ -54,15 +55,16 @@
"width": 140
},
{
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 200
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
},
{
"label": _("Quantity"),
"fieldname": "quantity",
- "fieldtype": "Int",
+ "fieldtype": "Float",
"width": 140
},
{
@@ -118,7 +120,7 @@
},
{
"label": _("Purchase Order Amount(Company Currency)"),
- "fieldname": "purchase_order_amt_usd",
+ "fieldname": "purchase_order_amt_in_company_currency",
"fieldtype": "Float",
"width": 140
},
@@ -175,17 +177,17 @@
"requesting_site": po.warehouse,
"requestor": po.owner,
"material_request_no": po.material_request,
- "description": po.description,
- "quantity": po.qty,
+ "item_code": po.item_code,
+ "quantity": flt(po.qty),
"unit_of_measurement": po.stock_uom,
"status": po.status,
"purchase_order_date": po.transaction_date,
"purchase_order": po.parent,
"supplier": po.supplier,
- "estimated_cost": mr_record.get('amount'),
- "actual_cost": pi_records.get(po.name),
- "purchase_order_amt": po.amount,
- "purchase_order_amt_in_company_currency": po.base_amount,
+ "estimated_cost": flt(mr_record.get('amount')),
+ "actual_cost": flt(pi_records.get(po.name)),
+ "purchase_order_amt": flt(po.amount),
+ "purchase_order_amt_in_company_currency": flt(po.base_amount),
"expected_delivery_date": po.schedule_date,
"actual_delivery_date": pr_records.get(po.name)
}
@@ -198,9 +200,14 @@
SELECT
par.transaction_date,
par.per_ordered,
+ par.owner,
child.name,
child.parent,
- child.amount
+ child.amount,
+ child.qty,
+ child.item_code,
+ child.uom,
+ par.status
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
WHERE
par.per_ordered>=0
@@ -217,7 +224,15 @@
procurement_record_details = dict(
material_request_date=record.transaction_date,
material_request_no=record.parent,
- estimated_cost=record.amount
+ requestor=record.owner,
+ item_code=record.item_code,
+ estimated_cost=flt(record.amount),
+ quantity=flt(record.qty),
+ unit_of_measurement=record.uom,
+ status=record.status,
+ actual_cost=0,
+ purchase_order_amt=0,
+ purchase_order_amt_in_company_currency=0
)
procurement_record_against_mr.append(procurement_record_details)
return mr_records, procurement_record_against_mr
@@ -259,7 +274,7 @@
child.warehouse,
child.material_request,
child.material_request_item,
- child.description,
+ child.item_code,
child.stock_uom,
child.qty,
child.amount,
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index bebf0cc..c7204a1 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -15,7 +15,7 @@
def test_result_for_procurement_tracker(self):
filters = {
'company': '_Test Procurement Company',
- 'cost_center': '_Test Cost Center - _TC'
+ 'cost_center': 'Main - _TPC'
}
expected_data = self.generate_expected_data()
report = execute(filters)
@@ -33,24 +33,27 @@
country="Pakistan"
)).insert()
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
- mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse)
+ mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC")
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
- po.get("items")[0].cost_center = "_Test Cost Center - _TC"
+ po.get("items")[0].cost_center = "Main - _TPC"
po.submit()
pr = make_purchase_receipt(po.name)
+ pr.get("items")[0].cost_center = "Main - _TPC"
pr.submit()
frappe.db.commit()
date_obj = datetime.date(datetime.now())
+ po.load_from_db()
+
expected_data = {
"material_request_date": date_obj,
- "cost_center": "_Test Cost Center - _TC",
+ "cost_center": "Main - _TPC",
"project": None,
"requesting_site": "_Test Procurement Warehouse - _TPC",
"requestor": "Administrator",
"material_request_no": mr.name,
- "description": '_Test Item 1',
+ "item_code": '_Test Item',
"quantity": 10.0,
"unit_of_measurement": "_Test UOM",
"status": "To Bill",
@@ -58,9 +61,9 @@
"purchase_order": po.name,
"supplier": "_Test Supplier",
"estimated_cost": 0.0,
- "actual_cost": None,
- "purchase_order_amt": 5000.0,
- "purchase_order_amt_in_company_currency": 300000.0,
+ "actual_cost": 0.0,
+ "purchase_order_amt": po.net_total,
+ "purchase_order_amt_in_company_currency": po.base_net_total,
"expected_delivery_date": date_obj,
"actual_delivery_date": date_obj
}
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/buying/report/purchase_order_analysis/__init__.py
similarity index 100%
copy from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
copy to erpnext/buying/report/purchase_order_analysis/__init__.py
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
new file mode 100644
index 0000000..701da43
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -0,0 +1,78 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Purchase Order Analysis"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname": "purchase_order",
+ "label": __("Purchase Order"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Purchase Order",
+ "get_query": () =>{
+ return {
+ filters: { "docstatus": 1 }
+ }
+ }
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "MultiSelectList",
+ "width": "80",
+ get_data: function(txt) {
+ let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]
+ let options = []
+ for (let option of status){
+ options.push({
+ "value": option,
+ "description": ""
+ })
+ }
+ return options
+ }
+ },
+ {
+ "fieldname": "group_by_po",
+ "label": __("Group by Purchase Order"),
+ "fieldtype": "Check",
+ "default": 0
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ let format_fields = ["received_qty", "billed_amount"];
+
+ if (in_list(format_fields, column.fieldname) && data && data[column.fieldname] > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ return value;
+ }
+};
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json
new file mode 100644
index 0000000..5ba3101
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-05-04 18:41:28.625119",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-15 20:57:52.623455",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Purchase Order Analysis",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Order",
+ "report_name": "Purchase Order Analysis",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Purchase User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Supplier"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
new file mode 100644
index 0000000..89be622
--- /dev/null
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
@@ -0,0 +1,271 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, date_diff, getdate
+
+def execute(filters=None):
+ if not filters:
+ return [], []
+
+ validate_filters(filters)
+
+ columns = get_columns(filters)
+ conditions = get_conditions(filters)
+
+ data = get_data(conditions, filters)
+
+ if not data:
+ return [], [], None, []
+
+ data, chart_data = prepare_data(data, filters)
+
+ return columns, data, None, chart_data
+
+def validate_filters(filters):
+ from_date, to_date = filters.get("from_date"), filters.get("to_date")
+
+ if not from_date and to_date:
+ frappe.throw(_("From and To Dates are required."))
+ elif date_diff(to_date, from_date) < 0:
+ frappe.throw(_("To Date cannot be before From Date."))
+
+def get_conditions(filters):
+ conditions = ""
+ if filters.get("from_date") and filters.get("to_date"):
+ conditions += " and po.transaction_date between %(from_date)s and %(to_date)s"
+
+ if filters.get("company"):
+ conditions += " and po.company = %(company)s"
+
+ if filters.get("purchase_order"):
+ conditions += " and po.name = %(purchase_order)s"
+
+ if filters.get("status"):
+ conditions += " and po.status in %(status)s"
+
+ return conditions
+
+def get_data(conditions, filters):
+ data = frappe.db.sql("""
+ SELECT
+ po.transaction_date as date,
+ poi.schedule_date as required_date,
+ po.name as purchase_order,
+ po.status, po.supplier, poi.item_code,
+ poi.qty, poi.received_qty,
+ (poi.qty - poi.received_qty) AS pending_qty,
+ IFNULL(pii.qty, 0) as billed_qty,
+ poi.base_amount as amount,
+ (poi.received_qty * poi.base_rate) as received_qty_amount,
+ (poi.billed_amt * IFNULL(po.conversion_rate, 1)) as billed_amount,
+ (poi.base_amount - (poi.billed_amt * IFNULL(po.conversion_rate, 1))) as pending_amount,
+ po.set_warehouse as warehouse,
+ po.company, poi.name
+ FROM
+ `tabPurchase Order` po,
+ `tabPurchase Order Item` poi
+ LEFT JOIN `tabPurchase Invoice Item` pii
+ ON pii.po_detail = poi.name
+ WHERE
+ poi.parent = po.name
+ and po.status not in ('Stopped', 'Closed')
+ and po.docstatus = 1
+ {0}
+ GROUP BY poi.name
+ ORDER BY po.transaction_date ASC
+ """.format(conditions), filters, as_dict=1)
+
+ return data
+
+def prepare_data(data, filters):
+ completed, pending = 0, 0
+ pending_field = "pending_amount"
+ completed_field = "billed_amount"
+
+ if filters.get("group_by_po"):
+ purchase_order_map = {}
+
+ for row in data:
+ # sum data for chart
+ completed += row[completed_field]
+ pending += row[pending_field]
+
+ # prepare data for report view
+ row["qty_to_bill"] = flt(row["qty"]) - flt(row["billed_qty"])
+
+ if filters.get("group_by_po"):
+ po_name = row["purchase_order"]
+
+ if not po_name in purchase_order_map:
+ # create an entry
+ row_copy = copy.deepcopy(row)
+ purchase_order_map[po_name] = row_copy
+ else:
+ # update existing entry
+ po_row = purchase_order_map[po_name]
+ po_row["required_date"] = min(getdate(po_row["required_date"]), getdate(row["required_date"]))
+
+ # sum numeric columns
+ fields = ["qty", "received_qty", "pending_qty", "billed_qty", "qty_to_bill", "amount",
+ "received_qty_amount", "billed_amount", "pending_amount"]
+ for field in fields:
+ po_row[field] = flt(row[field]) + flt(po_row[field])
+
+ chart_data = prepare_chart_data(pending, completed)
+
+ if filters.get("group_by_po"):
+ data = []
+ for po in purchase_order_map:
+ data.append(purchase_order_map[po])
+ return data, chart_data
+
+ return data, chart_data
+
+def prepare_chart_data(pending, completed):
+ labels = ["Amount to Bill", "Billed Amount"]
+
+ return {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {"values": [pending, completed]}
+ ]
+ },
+ "type": 'donut',
+ "height": 300
+ }
+
+def get_columns(filters):
+ columns = [
+ {
+ "label":_("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label":_("Required By"),
+ "fieldname": "required_date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label": _("Purchase Order"),
+ "fieldname": "purchase_order",
+ "fieldtype": "Link",
+ "options": "Purchase Order",
+ "width": 160
+ },
+ {
+ "label":_("Status"),
+ "fieldname": "status",
+ "fieldtype": "Data",
+ "width": 130
+ },
+ {
+ "label": _("Supplier"),
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "options": "Supplier",
+ "width": 130
+ }]
+
+ if not filters.get("group_by_po"):
+ columns.append({
+ "label":_("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100
+ })
+
+ columns.extend([
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Received Qty"),
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Pending Qty"),
+ "fieldname": "pending_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Billed Qty"),
+ "fieldname": "billed_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Qty to Bill"),
+ "fieldname": "qty_to_bill",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Billed Amount"),
+ "fieldname": "billed_amount",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Pending Amount"),
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Received Qty Amount"),
+ "fieldname": "received_qty_amount",
+ "fieldtype": "Currency",
+ "width": 130,
+ "options": "Company:company:default_currency",
+ "convertible": "rate"
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100
+ }
+ ])
+
+ return columns
+
diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
index 888676c..1ed6cad 100644
--- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
+++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -10,5 +11,48 @@
data = []
conditions = get_columns(filters, "Purchase Order")
data = get_data(filters, conditions)
+ chart_data = get_chart_data(data, conditions, filters)
- return conditions["columns"], data
\ No newline at end of file
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, conditions, filters):
+ if not (data and conditions):
+ return []
+
+ datapoints = []
+
+ start = 2 if filters.get("based_on") in ["Item", "Supplier"] else 1
+ if filters.get("group_by"):
+ start += 1
+
+ # fetch only periodic columns as labels
+ columns = conditions.get("columns")[start:-2][1::2]
+ labels = [column.split(':')[0] for column in columns]
+ datapoints = [0] * len(labels)
+
+ for row in data:
+ # If group by filter, don't add first row of group (it's already summed)
+ if not row[start-1]:
+ continue
+ # Remove None values and compute only periodic data
+ row = [x if x else 0 for x in row[start:-2]]
+ row = row[1::2]
+
+ for i in range(len(row)):
+ datapoints[i] += row[i]
+
+ return {
+ "data" : {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name" : _("{0}").format(filters.get("period")) + _(" Purchase Value"),
+ "values" : datapoints
+ }
+ ]
+ },
+ "type" : "line",
+ "lineOptions": {
+ "regionFill": 1
+ }
+ }
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json b/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json
deleted file mode 100644
index bb11269..0000000
--- a/erpnext/buying/report/requested_items_to_be_ordered/requested_items_to_be_ordered.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "add_total_row": 1,
- "creation": "2013-05-13 16:10:02",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2019-04-18 19:02:03.099422",
- "modified_by": "Administrator",
- "module": "Buying",
- "name": "Requested Items To Be Ordered",
- "owner": "Administrator",
- "prepared_report": 0,
- "query": "select \n mr.name as \"Material Request:Link/Material Request:120\",\n\tmr.transaction_date as \"Date:Date:100\",\n\tmr_item.item_code as \"Item Code:Link/Item:120\",\n\tsum(ifnull(mr_item.stock_qty, 0)) as \"Qty:Float:100\",\n\tifnull(mr_item.stock_uom, '') as \"UOM:Link/UOM:100\",\n\tsum(ifnull(mr_item.ordered_qty, 0)) as \"Ordered Qty:Float:100\", \n\t(sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as \"Qty to Order:Float:100\",\n\tmr_item.item_name as \"Item Name::150\",\n\tmr_item.description as \"Description::200\",\n\tmr.company as \"Company:Link/Company:\"\nfrom\n\t`tabMaterial Request` mr, `tabMaterial Request Item` mr_item\nwhere\n\tmr_item.parent = mr.name\n\tand mr.material_request_type = \"Purchase\"\n\tand mr.docstatus = 1\n\tand mr.status != \"Stopped\"\ngroup by mr.name, mr_item.item_code\nhaving\n\tsum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))\norder by mr.transaction_date asc",
- "ref_doctype": "Purchase Order",
- "report_name": "Requested Items To Be Ordered",
- "report_type": "Query Report",
- "roles": [
- {
- "role": "Stock User"
- },
- {
- "role": "Purchase Manager"
- },
- {
- "role": "Purchase User"
- }
- ]
- }
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/buying/report/requested_items_to_order/__init__.py
similarity index 100%
rename from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
rename to erpnext/buying/report/requested_items_to_order/__init__.py
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js
new file mode 100644
index 0000000..9555e82
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.js
@@ -0,0 +1,76 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Requested Items to Order"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "reqd": 1,
+ "default": frappe.datetime.get_today()
+ },
+ {
+ "fieldname": "material_request",
+ "label": __("Material Request"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Material Request",
+ "get_query": () => {
+ return {
+ filters: {
+ "docstatus": 1,
+ "material_request_type": "Purchase",
+ "per_received": ["<", 100]
+ }
+ }
+ }
+ },
+ {
+ "fieldname": "item_code",
+ "label": __("Item"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Item",
+ "get_query": () => {
+ return {
+ query: "erpnext.controllers.queries.item_query"
+ }
+ }
+ },
+ {
+ "fieldname": "group_by_mr",
+ "label": __("Group by Material Request"),
+ "fieldtype": "Check",
+ "default": 0
+ }
+ ],
+
+ "formatter": function (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "ordered_qty" && data && data.ordered_qty > 0) {
+ value = "<span style='color:green'>" + value + "</span>";
+ }
+ return value;
+ }
+};
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
new file mode 100644
index 0000000..4a0578b
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.json
@@ -0,0 +1,34 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-05-04 20:23:57.750719",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-05 13:05:51.723951",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Requested Items to Order",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Material Request",
+ "report_name": "Requested Items to Order",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Purchase Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Purchase User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
new file mode 100644
index 0000000..cca01b1
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order/requested_items_to_order.py
@@ -0,0 +1,233 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, date_diff, getdate
+
+def execute(filters=None):
+ if not filters:
+ return [],[]
+
+ validate_filters(filters)
+
+ columns = get_columns(filters)
+ conditions = get_conditions(filters)
+
+ #get queried data
+ data = get_data(filters, conditions)
+
+ #prepare data for report and chart views
+ data, chart_data = prepare_data(data, filters)
+
+ return columns, data, None, chart_data
+
+def validate_filters(filters):
+ from_date, to_date = filters.get("from_date"), filters.get("to_date")
+
+ if not from_date and to_date:
+ frappe.throw(_("From and To Dates are required."))
+ elif date_diff(to_date, from_date) < 0:
+ frappe.throw(_("To Date cannot be before From Date."))
+
+def get_conditions(filters):
+ conditions = ''
+
+ if filters.get("from_date") and filters.get("to_date"):
+ conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_date"),filters.get("to_date"))
+
+ if filters.get("company"):
+ conditions += " and mr.company = '{0}'".format(filters.get("company"))
+
+ if filters.get("material_request"):
+ conditions += " and mr.name = '{0}'".format(filters.get("material_request"))
+
+ if filters.get("item_code"):
+ conditions += " and mr_item.item_code = '{0}'".format(filters.get("item_code"))
+
+ return conditions
+
+def get_data(filters, conditions):
+ data = frappe.db.sql("""
+ select
+ mr.name as material_request,
+ mr.transaction_date as date,
+ mr_item.schedule_date as required_date,
+ mr_item.item_code as item_code,
+ sum(ifnull(mr_item.stock_qty, 0)) as qty,
+ ifnull(mr_item.stock_uom, '') as uom,
+ sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty,
+ (sum(mr_item.stock_qty) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
+ mr_item.item_name as item_name,
+ mr.company as company
+ from
+ `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
+ where
+ mr_item.parent = mr.name
+ and mr.material_request_type = "Purchase"
+ and mr.docstatus = 1
+ and mr.status != "Stopped"
+ {conditions}
+ group by mr.name, mr_item.item_code
+ having
+ sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))
+ order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1)
+
+ return data
+
+def update_qty_columns(row_to_update, data_row):
+ fields = ["qty", "ordered_qty", "qty_to_order"]
+ for field in fields:
+ row_to_update[field] += flt(data_row[field])
+
+def prepare_data(data, filters):
+ """Prepare consolidated Report data and Chart data"""
+ material_request_map, item_qty_map = {}, {}
+
+ for row in data:
+ # item wise map for charts
+ if not row["item_code"] in item_qty_map:
+ item_qty_map[row["item_code"]] = {
+ "qty" : row["qty"],
+ "ordered_qty" : row["ordered_qty"],
+ "qty_to_order" : row["qty_to_order"]
+ }
+ else:
+ item_entry = item_qty_map[row["item_code"]]
+ update_qty_columns(item_entry, row)
+
+ if filters.get("group_by_mr"):
+ # consolidated material request map for group by filter
+ if not row["material_request"] in material_request_map:
+ # create an entry with mr as key
+ row_copy = copy.deepcopy(row)
+ material_request_map[row["material_request"]] = row_copy
+ else:
+ mr_row = material_request_map[row["material_request"]]
+ mr_row["required_date"] = min(getdate(mr_row["required_date"]), getdate(row["required_date"]))
+
+ #sum numeric columns
+ update_qty_columns(mr_row, row)
+
+ chart_data = prepare_chart_data(item_qty_map)
+
+ if filters.get("group_by_mr"):
+ data =[]
+ for mr in material_request_map:
+ data.append(material_request_map[mr])
+ return data, chart_data
+
+ return data, chart_data
+
+def prepare_chart_data(item_data):
+ labels, qty_to_order, ordered_qty = [], [], []
+
+ if len(item_data) > 30:
+ item_data = dict(list(item_data.items())[:30])
+
+ for row in item_data:
+ mr_row = item_data[row]
+ labels.append(row)
+ qty_to_order.append(mr_row["qty_to_order"])
+ ordered_qty.append(mr_row["ordered_qty"])
+
+ chart_data = {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {
+ 'name': _('Qty to Order'),
+ 'values': qty_to_order
+ },
+ {
+ 'name': _('Ordered Qty'),
+ 'values': ordered_qty
+ }
+ ]
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ },
+ }
+
+ return chart_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Material Request"),
+ "fieldname": "material_request",
+ "fieldtype": "Link",
+ "options": "Material Request",
+ "width": 150
+ },
+ {
+ "label":_("Date"),
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "width": 90
+ },
+ {
+ "label":_("Required By"),
+ "fieldname": "required_date",
+ "fieldtype": "Date",
+ "width": 100
+ }
+ ]
+
+ if not filters.get("group_by_mr"):
+ columns.extend([{
+ "label":_("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100
+ },
+ {
+ "label":_("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("UOM"),
+ "fieldname": "uom",
+ "fieldtype": "Data",
+ "width": 100,
+ }])
+
+ columns.extend([
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Ordered Qty"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Qty to Order"),
+ "fieldname": "qty_to_order",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100
+ }
+ ])
+
+ return columns
diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py
index 1d40547..b06bb76 100644
--- a/erpnext/config/buying.py
+++ b/erpnext/config/buying.py
@@ -1,8 +1,9 @@
from __future__ import unicode_literals
+import frappe
from frappe import _
def get_data():
- return [
+ config = [
{
"label": _("Purchasing"),
"icon": "fa fa-star",
@@ -166,7 +167,7 @@
{
"type": "report",
"is_query_report": True,
- "name": "Requested Items To Be Ordered",
+ "name": "Requested Items To Order",
"reference_doctype": "Material Request",
"onboard": 1,
},
@@ -243,3 +244,21 @@
},
]
+
+ 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/manufacturing.py b/erpnext/config/manufacturing.py
index 2c18eeb..012f1ca 100644
--- a/erpnext/config/manufacturing.py
+++ b/erpnext/config/manufacturing.py
@@ -120,13 +120,7 @@
{
"type": "report",
"is_query_report": True,
- "name": "Open Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
- "name": "Work Orders in Progress",
+ "name": "Work Order Summary",
"doctype": "Work Order"
},
{
@@ -138,12 +132,6 @@
{
"type": "report",
"is_query_report": True,
- "name": "Completed Work Orders",
- "doctype": "Work Order"
- },
- {
- "type": "report",
- "is_query_report": True,
"name": "Production Analytics",
"doctype": "Work Order"
},
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index eecb143..f54b593 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1137,8 +1137,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
@@ -1157,8 +1157,8 @@
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date
+ child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.uom = item.stock_uom
- child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
return child_item
@@ -1190,6 +1190,26 @@
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
+ def check_permissions(doc, perm_type='create'):
+ try:
+ doc.check_permission(perm_type)
+ except:
+ action = "add" if perm_type == 'create' else "update"
+ frappe.throw(_("You do not have permissions to {} items in a Sales Order.").format(action), title=_("Insufficient Permissions"))
+
+ def get_new_child_item(item_row):
+ if parent_doctype == "Sales Order":
+ return set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+ if parent_doctype == "Purchase Order":
+ return set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_row)
+
+ def validate_quantity(child_item, d):
+ if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
+ frappe.throw(_("Cannot set quantity less than delivered quantity"))
+
+ if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
+ frappe.throw(_("Cannot set quantity less than received quantity"))
+
data = json.loads(trans_items)
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
@@ -1201,20 +1221,29 @@
new_child_flag = False
if not d.get("docname"):
new_child_flag = True
- if parent_doctype == "Sales Order":
- child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
- if parent_doctype == "Purchase Order":
- child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d)
+ check_permissions(parent, 'create')
+ child_item = get_new_child_item(d)
else:
+ check_permissions(parent, 'write')
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
- if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
+
+ prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
+ prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
+ prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(d.get("conversion_factor"))
+
+ if parent_doctype == 'Sales Order':
+ prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
+ elif parent_doctype == 'Purchase Order':
+ prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
+
+ rate_unchanged = prev_rate == new_rate
+ qty_unchanged = prev_qty == new_qty
+ conversion_factor_unchanged = prev_con_fac == new_con_fac
+ date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc
+ if rate_unchanged and qty_unchanged and conversion_factor_unchanged and date_unchanged:
continue
- if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
- frappe.throw(_("Cannot set quantity less than delivered quantity"))
-
- if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
- frappe.throw(_("Cannot set quantity less than received quantity"))
+ validate_quantity(child_item, d)
child_item.qty = flt(d.get("qty"))
precision = child_item.precision("rate") or 2
@@ -1225,6 +1254,18 @@
else:
child_item.rate = flt(d.get("rate"))
+ if d.get("conversion_factor"):
+ if child_item.stock_uom == child_item.uom:
+ child_item.conversion_factor = 1
+ else:
+ child_item.conversion_factor = flt(d.get('conversion_factor'))
+
+ if d.get("delivery_date") and parent_doctype == 'Sales Order':
+ child_item.delivery_date = d.get('delivery_date')
+
+ if d.get("schedule_date") and parent_doctype == 'Purchase Order':
+ child_item.schedule_date = d.get('schedule_date')
+
if flt(child_item.price_list_rate):
if flt(child_item.rate) > flt(child_item.price_list_rate):
# if rate is greater than price_list_rate, set margin
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 608e537..89b48f0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -349,7 +349,7 @@
})
if not rm.rate:
- rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
+ rm.rate = get_valuation_rate(raw_material_data.rm_item_code, self.supplier_warehouse,
self.doctype, self.name, currency=self.company_currency, company=self.company)
rm.amount = qty * flt(rm.rate)
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 29f8dd5..1f95e00 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -70,7 +70,7 @@
else:
attributes_list = attribute_values.get(attribute.lower(), [])
- validate_item_attribute_value(attributes_list, attribute, value, item.name)
+ validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
def validate_is_incremental(numeric_attribute, attribute, value, item):
from_range = numeric_attribute.from_range
@@ -93,13 +93,20 @@
.format(attribute, from_range, to_range, increment, item),
InvalidItemAttributeValueError, title=_('Invalid Attribute'))
-def validate_item_attribute_value(attributes_list, attribute, attribute_value, item):
+def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
if allow_rename_attribute_value:
pass
elif attribute_value not in attributes_list:
- frappe.throw(_("The value {0} is already assigned to an exisiting Item {2}.").format(
- attribute_value, attribute, item), InvalidItemAttributeValueError, title=_('Rename Not Allowed'))
+ if from_variant:
+ frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
+ frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
+ else:
+ msg = _("The value {0} is already assigned to an existing Item {1}.").format(
+ frappe.bold(attribute_value), frappe.bold(item))
+ msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
+
+ frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
def get_attribute_values(item):
if not frappe.flags.attribute_values:
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 5fbc460..f6a8d27 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -188,12 +188,6 @@
# scan description only if items are less than 50000
description_cond = 'or tabItem.description LIKE %(txt)s'
- extra_cond = " and tabItem.has_variants=0"
- if (filters and isinstance(filters, dict)
- and filters.get("doctype") == "BOM"):
- extra_cond = ""
- del filters["doctype"]
-
return frappe.db.sql("""select tabItem.name,
if(length(tabItem.item_name) > 40,
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
@@ -204,10 +198,10 @@
from tabItem
where tabItem.docstatus < 2
and tabItem.disabled=0
+ and tabItem.has_variants=0
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
{description_cond})
- {extra_cond}
{fcond} {mcond}
order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
@@ -218,7 +212,6 @@
key=searchfield,
columns=columns,
scond=searchfields,
- extra_cond=extra_cond,
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
mcond=get_match_cond(doctype).replace('%', '%%'),
description_cond = description_cond),
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 1e0a48c..b696ac3 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -361,7 +361,7 @@
self.po_no = ', '.join(list(set([d.po_no for d in po_nos if d.po_no])))
def set_gross_profit(self):
- if self.doctype == "Sales Order":
+ if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items:
item.gross_profit = flt(((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item))
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 86de808..759c6cd 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -19,7 +19,8 @@
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
- self.validate_inspection()
+ if not self.get('is_return'):
+ self.validate_inspection()
self.validate_serialized_batch()
self.validate_customer_provided_item()
@@ -226,7 +227,9 @@
def check_expense_account(self, item):
if not item.get("expense_account"):
- frappe.throw(_("Expense or Difference account is mandatory for Item {0} as it impacts overall stock value").format(item.item_code))
+ 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"))
else:
is_expense_account = frappe.db.get_value("Account",
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index ed37938..ecf041e 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -155,7 +155,7 @@
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
- return frappe.db.exists(doctype, filters={
+ return frappe.db.exists(doctype, {
'name': doc.name,
fieldname: ["in", suppliers]
})
diff --git a/erpnext/crm/dashboard_fixtures.py b/erpnext/crm/dashboard_fixtures.py
new file mode 100644
index 0000000..0535cbb
--- /dev/null
+++ b/erpnext/crm/dashboard_fixtures.py
@@ -0,0 +1,214 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe, erpnext, json
+from frappe import _
+
+def get_data():
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards()
+ })
+
+def get_dashboards():
+ return [{
+ "doctype": "Dashboard",
+ "name": "CRM",
+ "dashboard_name": "CRM",
+ "charts": [
+ { "chart": "Incoming Leads", "width": "Full" },
+ { "chart": "Opportunity Trends", "width": "Full"},
+ { "chart": "Won Opportunities", "width": "Full" },
+ { "chart": "Territory Wise Opportunity Count", "width": "Half"},
+ { "chart": "Opportunities via Campaigns", "width": "Half" },
+ { "chart": "Territory Wise Sales", "width": "Full"},
+ { "chart": "Lead Source", "width": "Half"}
+ ],
+ "cards": [
+ { "card": "New Lead (Last 1 Month)" },
+ { "card": "New Opportunity (Last 1 Month)" },
+ { "card": "Won Opportunity (Last 1 Month)" },
+ { "card": "Open Opportunity"},
+ ]
+ }]
+
+def get_company_for_dashboards():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company_list = frappe.get_list("Company")
+ if company_list:
+ return company_list[0].name
+ return None
+
+def get_charts():
+ company = get_company_for_dashboards()
+
+ return [{
+ "name": "Incoming Leads",
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Count",
+ "chart_name": _("Incoming Leads"),
+ "timespan": "Last Quarter",
+ "time_interval": "Weekly",
+ "document_type": "Lead",
+ "based_on": "creation",
+ 'is_public': 1,
+ 'timeseries': 1,
+ "owner": "Administrator",
+ "filters_json": json.dumps([]),
+ "type": "Bar"
+ },
+ {
+ "name": "Opportunity Trends",
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Count",
+ "chart_name": _("Opportunity Trends"),
+ "timespan": "Last Quarter",
+ "time_interval": "Weekly",
+ "document_type": "Opportunity",
+ "based_on": "creation",
+ 'is_public': 1,
+ 'timeseries': 1,
+ "owner": "Administrator",
+ "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]),
+ "type": "Bar"
+ },
+ {
+ "name": "Opportunities via Campaigns",
+ "chart_name": _("Opportunities via Campaigns"),
+ "doctype": "Dashboard Chart",
+ "chart_type": "Group By",
+ "group_by_type": "Count",
+ "group_by_based_on": "campaign",
+ "document_type": "Opportunity",
+ 'is_public': 1,
+ 'timeseries': 1,
+ "owner": "Administrator",
+ "filters_json": json.dumps([["Opportunity", "company", "=", company, False]]),
+ "type": "Pie",
+ "custom_options": json.dumps({
+ "truncateLegends": 1,
+ "maxSlices": 8
+ })
+ },
+ {
+ "name": "Won Opportunities",
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Count",
+ "chart_name": _("Won Opportunities"),
+ "timespan": "Last Year",
+ "time_interval": "Monthly",
+ "document_type": "Opportunity",
+ "based_on": "modified",
+ 'is_public': 1,
+ 'timeseries': 1,
+ "owner": "Administrator",
+ "filters_json": json.dumps([
+ ["Opportunity", "company", "=", company, False],
+ ["Opportunity", "status", "=", "Converted", False]]),
+ "type": "Bar"
+ },
+ {
+ "name": "Territory Wise Opportunity Count",
+ "doctype": "Dashboard Chart",
+ "chart_type": "Group By",
+ "group_by_type": "Count",
+ "group_by_based_on": "territory",
+ "chart_name": _("Territory Wise Opportunity Count"),
+ "document_type": "Opportunity",
+ 'is_public': 1,
+ "filters_json": json.dumps([
+ ["Opportunity", "company", "=", company, False]
+ ]),
+ "owner": "Administrator",
+ "type": "Donut",
+ "custom_options": json.dumps({
+ "truncateLegends": 1,
+ "maxSlices": 8
+ })
+ },
+ {
+ "name": "Territory Wise Sales",
+ "doctype": "Dashboard Chart",
+ "chart_type": "Group By",
+ "group_by_type": "Sum",
+ "group_by_based_on": "territory",
+ "chart_name": _("Territory Wise Sales"),
+ "aggregate_function_based_on": "opportunity_amount",
+ "document_type": "Opportunity",
+ 'is_public': 1,
+ "owner": "Administrator",
+ "filters_json": json.dumps([
+ ["Opportunity", "company", "=", company, False],
+ ["Opportunity", "status", "=", "Converted", False]
+ ]),
+ "type": "Bar"
+ },
+ {
+ "name": "Lead Source",
+ "doctype": "Dashboard Chart",
+ "chart_type": "Group By",
+ "group_by_type": "Count",
+ "group_by_based_on": "source",
+ "chart_name": _("Lead Source"),
+ "document_type": "Lead",
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Pie",
+ "custom_options": json.dumps({
+ "truncateLegends": 1,
+ "maxSlices": 8
+ })
+ }]
+
+def get_number_cards():
+ return [{
+ "doctype": "Number Card",
+ "document_type": "Lead",
+ "name": "New Lead (Last 1 Month)",
+ "filters_json": json.dumps([["Lead","creation","Previous","1 month",False]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("New Lead (Last 1 Month)"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "name": "New Opportunity (Last 1 Month)",
+ "filters_json": json.dumps([["Opportunity","creation","Previous","1 month",False]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("New Opportunity (Last 1 Month)"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "name": "Won Opportunity (Last 1 Month)",
+ "filters_json": json.dumps([["Opportunity","creation","Previous","1 month",False]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Won Opportunity (Last 1 Month)"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Opportunity",
+ "name": "Open Opportunity",
+ "filters_json": json.dumps([["Opportunity","status","=","Open",False]]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Open Opportunity"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ }]
\ No newline at end of file
diff --git a/erpnext/crm/desk_page/crm/crm.json b/erpnext/crm/desk_page/crm/crm.json
index ca13d6a..eb69dc0 100644
--- a/erpnext/crm/desk_page/crm/crm.json
+++ b/erpnext/crm/desk_page/crm/crm.json
@@ -27,26 +27,32 @@
}
],
"category": "Modules",
- "charts": [],
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "CRM",
- "modified": "2020-04-27 22:32:26.682911",
+ "modified": "2020-05-28 13:33:52.906750",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
+ "onboarding": "CRM",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#ffe8cd",
"format": "{} Open",
"label": "Lead",
"link_to": "Lead",
@@ -54,6 +60,7 @@
"type": "DocType"
},
{
+ "color": "#cef6d1",
"format": "{} Assigned",
"label": "Opportunity",
"link_to": "Opportunity",
@@ -69,6 +76,11 @@
"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/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 6fef0c4..f5f8b4e 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -6,6 +6,7 @@
"creation": "2013-04-10 11:45:37",
"doctype": "DocType",
"document_type": "Document",
+ "email_append_to": 1,
"engine": "InnoDB",
"field_order": [
"organization_lead",
@@ -448,7 +449,7 @@
"idx": 5,
"image_field": "image",
"links": [],
- "modified": "2020-05-11 20:27:45.868960",
+ "modified": "2020-06-18 14:39:41.835416",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
@@ -508,8 +509,10 @@
}
],
"search_fields": "lead_name,lead_owner,status",
+ "sender_field": "email_id",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "subject_field": "title",
"title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
index 9ed616a..68f0104 100644
--- a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
+++ b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
@@ -3,11 +3,7 @@
def get_data():
return {
- 'fieldname': 'prevdoc_docname',
- 'non_standard_fieldnames': {
- 'Supplier Quotation': 'opportunity',
- 'Quotation': 'opportunity'
- },
+ 'fieldname': 'opportunity',
'transactions': [
{
'items': ['Quotation', 'Supplier Quotation']
diff --git a/erpnext/crm/doctype/sales_stage/sales_stage.json b/erpnext/crm/doctype/sales_stage/sales_stage.json
index 4374bb5..77aa559 100644
--- a/erpnext/crm/doctype/sales_stage/sales_stage.json
+++ b/erpnext/crm/doctype/sales_stage/sales_stage.json
@@ -1,96 +1,44 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:stage_name",
- "beta": 0,
- "creation": "2018-10-01 09:28:16.399518",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:stage_name",
+ "creation": "2018-10-01 09:28:16.399518",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "stage_name"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "stage_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Stage Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "stage_name",
+ "fieldtype": "Data",
+ "label": "Stage Name",
"unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-10-01 09:29:43.230378",
- "modified_by": "Administrator",
- "module": "CRM",
- "name": "Sales Stage",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2020-05-20 12:22:01.866472",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Sales Stage",
+ "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": "Sales Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.json b/erpnext/crm/doctype/social_media_post/social_media_post.json
index 2601c14..0a00dca 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.json
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.json
@@ -30,24 +30,32 @@
"fieldname": "text",
"fieldtype": "Small Text",
"label": "Tweet",
- "mandatory_depends_on": "eval:doc.twitter ==1"
+ "mandatory_depends_on": "eval:doc.twitter ==1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
- "label": "Image"
+ "label": "Image",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "twitter",
"fieldtype": "Check",
- "label": "Twitter"
+ "label": "Twitter",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "linkedin",
"fieldtype": "Check",
- "label": "LinkedIn"
+ "label": "LinkedIn",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "amended_from",
@@ -56,13 +64,17 @@
"no_copy": 1,
"options": "Social Media Post",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.twitter ==1",
"fieldname": "content",
"fieldtype": "Section Break",
- "label": "Twitter"
+ "label": "Twitter",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -70,7 +82,9 @@
"fieldtype": "Select",
"label": "Post Status",
"options": "\nScheduled\nPosted\nError",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -78,7 +92,9 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Twitter Post Id",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -86,68 +102,89 @@
"fieldtype": "Data",
"hidden": 1,
"label": "LinkedIn Post Id",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "campaign_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Campaign",
- "options": "Campaign"
+ "options": "Campaign",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break",
- "label": "Share On"
+ "label": "Share On",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_14",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tweet_preview",
- "fieldtype": "HTML"
+ "fieldtype": "HTML",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"depends_on": "eval:doc.linkedin==1",
"fieldname": "linkedin_section",
"fieldtype": "Section Break",
- "label": "LinkedIn"
+ "label": "LinkedIn",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "attachments_section",
"fieldtype": "Section Break",
- "label": "Attachments"
+ "label": "Attachments",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "linkedin_post",
"fieldtype": "Text",
"label": "Post",
- "mandatory_depends_on": "eval:doc.linkedin ==1"
+ "mandatory_depends_on": "eval:doc.linkedin ==1",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_15",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
"fieldname": "scheduled_time",
"fieldtype": "Datetime",
"label": "Scheduled Time",
- "read_only_depends_on": "eval:doc.post_status == \"Posted\""
+ "read_only_depends_on": "eval:doc.post_status == \"Posted\"",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-21 15:10:04.953713",
+ "modified": "2020-06-14 10:31:33.961381",
"modified_by": "Administrator",
"module": "CRM",
"name": "Social Media Post",
"owner": "Administrator",
"permissions": [
{
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -157,6 +194,35 @@
"report": 1,
"role": "System Manager",
"share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales User",
+ "share": 1,
+ "submit": 1,
+ "write": 1
+ },
+ {
+ "cancel": 1,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Sales Manager",
+ "share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json
new file mode 100644
index 0000000..44d672a
--- /dev/null
+++ b/erpnext/crm/module_onboarding/crm/crm.json
@@ -0,0 +1,42 @@
+{
+ "allow_roles": [
+ {
+ "role": "Sales Master Manager"
+ },
+ {
+ "role": "Sales Manager"
+ },
+ {
+ "role": "Sales User"
+ }
+ ],
+ "creation": "2020-05-09 23:42:50.901548",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-28 21:07:41.278784",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "CRM",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to CRM"
+ },
+ {
+ "step": "Create Lead"
+ },
+ {
+ "step": "Create Opportunity"
+ },
+ {
+ "step": "Create and Send Quotation"
+ }
+ ],
+ "subtitle": "Lead, Opportunity, Customer and more.",
+ "success_message": "CRM Module is all Set Up!",
+ "title": "Let's Set Up Your CRM.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding/crm/crm.json b/erpnext/crm/onboarding/crm/crm.json
new file mode 100644
index 0000000..016a830
--- /dev/null
+++ b/erpnext/crm/onboarding/crm/crm.json
@@ -0,0 +1,45 @@
+{
+ "allow_roles": [
+ {
+ "role": "Sales Master Manager"
+ },
+ {
+ "role": "Administrator"
+ },
+ {
+ "role": "Sales Manager"
+ }
+ ],
+ "creation": "2020-05-09 23:42:50.901548",
+ "docstatus": 0,
+ "doctype": "Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-09 23:42:50.901548",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "CRM",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to CRM"
+ },
+ {
+ "step": "Start Campaign"
+ },
+ {
+ "step": "Create Lead"
+ },
+ {
+ "step": "Convert Lead to Customer"
+ },
+ {
+ "step": "Create and Send Quotation"
+ }
+ ],
+ "subtitle": "Campaign, Lead, Opportunity, Customer and more",
+ "success_message": "CRM Module is all setup!",
+ "title": "Let's Setup Your CRM",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
new file mode 100644
index 0000000..78f7e4d
--- /dev/null
+++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-09 23:42:46.592075",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-28 21:07:11.461172",
+ "modified_by": "Administrator",
+ "name": "Create and Send Quotation",
+ "owner": "Administrator",
+ "reference_document": "Quotation",
+ "show_full_form": 1,
+ "title": "Create and Send Quotation",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json
new file mode 100644
index 0000000..c45e8b0
--- /dev/null
+++ b/erpnext/crm/onboarding_step/create_lead/create_lead.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-09 23:40:25.192503",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-28 21:07:01.373403",
+ "modified_by": "Administrator",
+ "name": "Create Lead",
+ "owner": "Administrator",
+ "reference_document": "Lead",
+ "show_full_form": 1,
+ "title": "Create Lead",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
new file mode 100644
index 0000000..9f996d9
--- /dev/null
+++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 17:38:27.496696",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:38:27.496696",
+ "modified_by": "Administrator",
+ "name": "Create Opportunity",
+ "owner": "Administrator",
+ "reference_document": "Opportunity",
+ "show_full_form": 0,
+ "title": "Create Opportunity",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
new file mode 100644
index 0000000..fa26921a
--- /dev/null
+++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-09 23:37:08.926812",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 17:28:16.448676",
+ "modified_by": "Administrator",
+ "name": "Introduction to CRM",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to CRM",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=o9XCSZHJfpA"
+}
\ No newline at end of file
diff --git a/erpnext/education/desk_page/education/education.json b/erpnext/education/desk_page/education/education.json
index fc2697f..b341ec4 100644
--- a/erpnext/education/desk_page/education/education.json
+++ b/erpnext/education/desk_page/education/education.json
@@ -64,6 +64,11 @@
"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": "Reports",
+ "links": "[\n {\n \"dependencies\": [\n \"Fees\"\n ],\n \"doctype\": \"Fees\",\n \"is_query_report\": true,\n \"label\": \"Student Fee Collection\",\n \"name\": \"Student Fee Collection\",\n \"type\": \"report\"\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 \"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 {\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]"
}
],
"category": "Domains",
@@ -77,7 +82,7 @@
"idx": 0,
"is_standard": 1,
"label": "Education",
- "modified": "2020-04-01 11:28:51.011309",
+ "modified": "2020-05-22 01:09:13.058482",
"modified_by": "Administrator",
"module": "Education",
"name": "Education",
diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
new file mode 100644
index 0000000..c36dfb1
--- /dev/null
+++ b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'assessment_plan',
+ 'non_standard_fieldnames': {
+ },
+ 'transactions': [
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Result']
+ }
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/course/course_dashboard.py b/erpnext/education/doctype/course/course_dashboard.py
new file mode 100644
index 0000000..752af29
--- /dev/null
+++ b/erpnext/education/doctype/course/course_dashboard.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+ return {
+ 'fieldname': 'course',
+ 'non_standard_fieldnames': {
+ },
+ 'transactions': [
+ {
+ 'label': _('Course'),
+ 'items': ['Course Enrollment', 'Course Schedule']
+ },
+ {
+ 'label': _('Student'),
+ 'items': ['Student Group']
+ },
+ {
+ 'label': _('Assessment'),
+ 'items': ['Assessment Plan']
+ },
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.json b/erpnext/education/doctype/fee_schedule/fee_schedule.json
index 1e98709..7918318 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.json
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2017-07-18 15:21:21.527136",
@@ -7,6 +8,7 @@
"engine": "InnoDB",
"field_order": [
"fee_structure",
+ "posting_date",
"due_date",
"naming_series",
"fee_creation_status",
@@ -259,10 +261,18 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "Today",
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date",
+ "reqd": 1
}
],
"is_submittable": 1,
- "modified": "2019-05-26 09:10:34.522409",
+ "links": [],
+ "modified": "2020-05-15 08:39:20.682837",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Schedule",
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.py b/erpnext/education/doctype/fee_schedule/fee_schedule.py
index a42800a..1543acd 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.py
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py
@@ -87,6 +87,7 @@
}
}
})
+ fees_doc.posting_date = doc.posting_date
fees_doc.student = student.student
fees_doc.student_name = student.student_name
fees_doc.program = student.program
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index 7606565..f09d2ef 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -9,6 +9,14 @@
},
onload: function(frm) {
+ frm.set_query("academic_term", function() {
+ return {
+ "filters": {
+ "academic_year": frm.doc.academic_year
+ }
+ };
+ });
+
frm.set_query("receivable_account", function(doc) {
return {
filters: {
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.json b/erpnext/education/doctype/fee_structure/fee_structure.json
index 8ff6851..67e4637 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.json
+++ b/erpnext/education/doctype/fee_structure/fee_structure.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
@@ -11,8 +12,8 @@
"program",
"student_category",
"column_break_2",
- "academic_term",
"academic_year",
+ "academic_term",
"section_break_4",
"components",
"section_break_6",
@@ -157,7 +158,8 @@
],
"icon": "fa fa-flag",
"is_submittable": 1,
- "modified": "2019-05-26 09:04:17.765758",
+ "links": [],
+ "modified": "2020-06-16 15:34:57.295010",
"modified_by": "Administrator",
"module": "Education",
"name": "Fee Structure",
diff --git a/erpnext/education/doctype/program_course/program_course.json b/erpnext/education/doctype/program_course/program_course.json
index a24e88a..940358e 100644
--- a/erpnext/education/doctype/program_course/program_course.json
+++ b/erpnext/education/doctype/program_course/program_course.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2015-09-07 14:37:01.886859",
"doctype": "DocType",
"editable_grid": 1,
@@ -16,26 +17,33 @@
"in_list_view": 1,
"label": "Course",
"options": "Course",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
- {
+ {
+ "fetch_from": "course.course_name",
"fieldname": "course_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Course Name",
- "fetch_from": "course.course_name",
- "read_only":1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "required",
"fieldtype": "Check",
"in_list_view": 1,
- "label": "Mandatory"
+ "label": "Mandatory",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"istable": 1,
- "modified": "2019-06-12 12:42:12.845972",
+ "links": [],
+ "modified": "2020-06-09 18:56:10.213241",
"modified_by": "Administrator",
"module": "Education",
"name": "Program Course",
@@ -45,4 +53,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_admission/student_admission.json b/erpnext/education/doctype/student_admission/student_admission.json
index b3c10d4..1096888 100644
--- a/erpnext/education/doctype/student_admission/student_admission.json
+++ b/erpnext/education/doctype/student_admission/student_admission.json
@@ -1,398 +1,119 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 1,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "",
- "beta": 0,
- "creation": "2016-09-13 03:05:27.154713",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
+ "actions": [],
+ "allow_guest_to_view": 1,
+ "allow_rename": 1,
+ "creation": "2016-09-13 03:05:27.154713",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "route",
+ "column_break_3",
+ "academic_year",
+ "admission_start_date",
+ "admission_end_date",
+ "published",
+ "enable_admission_application",
+ "section_break_5",
+ "program_details",
+ "introduction"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "label": "Title"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "route",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Route",
- "length": 0,
- "no_copy": 1,
- "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,
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route",
+ "no_copy": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_form_route",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Application Form Route",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Academic Year",
+ "no_copy": 1,
+ "options": "Academic Year",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "academic_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Academic Year",
- "length": 0,
- "no_copy": 1,
- "options": "Academic Year",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "admission_start_date",
+ "fieldtype": "Date",
+ "label": "Admission Start Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_start_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Admission Start Date",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "admission_end_date",
+ "fieldtype": "Date",
+ "label": "Admission End Date",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "admission_end_date",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Admission End Date",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "label": "Publish on website"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "published",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Publish on website",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "section_break_5",
+ "fieldtype": "Section Break",
+ "label": "Eligibility and Details"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_5",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Eligibility and Details",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "program_details",
+ "fieldtype": "Table",
+ "label": "Eligibility and Details",
+ "options": "Student Admission Program"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program_details",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Eligibility and Details",
- "length": 0,
- "no_copy": 0,
- "options": "Student Admission Program",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "introduction",
+ "fieldtype": "Text Editor",
+ "label": "Introduction"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "introduction",
- "fieldtype": "Text Editor",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Introduction",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "default": "0",
+ "fieldname": "enable_admission_application",
+ "fieldtype": "Check",
+ "label": "Enable Admission Application"
}
- ],
- "has_web_view": 1,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_published_field": "published",
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-11-10 18:57:34.570376",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "has_web_view": 1,
+ "is_published_field": "published",
+ "links": [],
+ "modified": "2020-06-15 20:18:38.591626",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Academics User",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Academics User",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "route": "admissions",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "restrict_to_domain": "Education",
+ "route": "admissions",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_admission/templates/student_admission.html b/erpnext/education/doctype/student_admission/templates/student_admission.html
index 25afaca..e5a9ead 100644
--- a/erpnext/education/doctype/student_admission/templates/student_admission.html
+++ b/erpnext/education/doctype/student_admission/templates/student_admission.html
@@ -43,8 +43,8 @@
<thead>
<tr class="active">
<th style="width: 90px">Program/Std.</th>
- <th style="width: 170px">Minumum Age(DOB)</th>
- <th style="width: 170px">Maximum Age(DOB)</th>
+ <th style="width: 170px">Minumum Age</th>
+ <th style="width: 170px">Maximum Age</th>
<th style="width: 100px">Application Fee</th>
</tr>
</thead>
@@ -52,8 +52,8 @@
{% for row in program_details %}
<tr>
<td>{{ row.program }}</td>
- <td>{{ row.minimum_age }}</td>
- <td>{{ row.maximum_age }}</td>
+ <td>{{ row.min_age }}</td>
+ <td>{{ row.max_age }}</td>
<td>{{ row.application_fee }}</td>
</tr>
{% endfor %}
@@ -61,12 +61,11 @@
</table>
</div>
{% endif %}
-
- {%- if application_form_route -%}
+ {%- if doc.enable_admission_application -%}
<br>
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}?new=1'>
+ href='/student-applicant?new=1&student_admission={{doc.name}}'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/education/doctype/student_admission/test_student_admission.js b/erpnext/education/doctype/student_admission/test_student_admission.js
index ed794b2..3a0bb0b 100644
--- a/erpnext/education/doctype/student_admission/test_student_admission.js
+++ b/erpnext/education/doctype/student_admission/test_student_admission.js
@@ -11,7 +11,7 @@
{admission_start_date: '2016-04-20'},
{admission_end_date: '2016-05-31'},
{title: '2016-17 Admissions'},
- {application_form_route: 'student-applicant'},
+ {enable_admission_application: 1},
{introduction: 'Test intro'},
{program_details: [
[
@@ -28,7 +28,7 @@
assert.ok(cur_frm.doc.admission_start_date == '2016-04-20');
assert.ok(cur_frm.doc.admission_end_date == '2016-05-31');
assert.ok(cur_frm.doc.title == '2016-17 Admissions');
- assert.ok(cur_frm.doc.application_form_route == 'student-applicant');
+ assert.ok(cur_frm.doc.enable_admission_application == 1);
assert.ok(cur_frm.doc.introduction == 'Test intro');
assert.ok(cur_frm.doc.program_details[0].program == 'Standard Test', 'Program correctly selected');
assert.ok(cur_frm.doc.program_details[0].application_fee == 1000);
diff --git a/erpnext/education/doctype/student_admission_program/student_admission_program.json b/erpnext/education/doctype/student_admission_program/student_admission_program.json
index 97b1bba..e9f041e 100644
--- a/erpnext/education/doctype/student_admission_program/student_admission_program.json
+++ b/erpnext/education/doctype/student_admission_program/student_admission_program.json
@@ -1,237 +1,77 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2017-09-15 12:59:43.207923",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-09-15 12:59:43.207923",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "program",
+ "min_age",
+ "max_age",
+ "column_break_4",
+ "application_fee",
+ "applicant_naming_series"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "program",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Program",
- "length": 0,
- "no_copy": 0,
- "options": "Program",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Program",
+ "options": "Program",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "minimum_age",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Minimum Age",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "maximum_age",
- "fieldtype": "Date",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Maximum Age",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "application_fee",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Application Fee",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_4",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "applicant_naming_series",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Naming Series (for Student Applicant)",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "application_fee",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Application Fee",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "min_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Minimum Age",
+ "show_days": 1,
+ "show_seconds": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "applicant_naming_series",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Naming Series (for Student Applicant)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "max_age",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Maximum Age",
+ "show_days": 1,
+ "show_seconds": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-11-04 03:37:17.408427",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Student Admission Program",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-10 23:06:30.037404",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Student Admission Program",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/education/doctype/student_applicant/student_applicant.py b/erpnext/education/doctype/student_applicant/student_applicant.py
index ab94780..2113482 100644
--- a/erpnext/education/doctype/student_applicant/student_applicant.py
+++ b/erpnext/education/doctype/student_applicant/student_applicant.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import getdate
+from frappe.utils import getdate, add_years, nowdate, date_diff
class StudentApplicant(Document):
def autoname(self):
@@ -31,6 +31,7 @@
def validate(self):
self.validate_dates()
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
+
if self.student_admission and self.program and self.date_of_birth:
self.validation_from_student_admission()
@@ -48,16 +49,16 @@
frappe.throw(_("Please select Student Admission which is mandatory for the paid student applicant"))
def validation_from_student_admission(self):
+
student_admission = get_student_admission_data(self.student_admission, self.program)
- # different validation for minimum and maximum age so that either min/max can also work independently.
- if student_admission and student_admission.minimum_age and \
- getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.min_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age)) < 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
- if student_admission and student_admission.maximum_age and \
- getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+ if student_admission and student_admission.max_age and \
+ date_diff(nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age)) > 0:
+ frappe.throw(_("Not eligible for the admission in this program as per Date Of Birth"))
def on_payment_authorized(self, *args, **kwargs):
@@ -65,10 +66,12 @@
def get_student_admission_data(student_admission, program):
+
student_admission = frappe.db.sql("""select sa.admission_start_date, sa.admission_end_date,
- sap.program, sap.minimum_age, sap.maximum_age, sap.applicant_naming_series
+ sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series
from `tabStudent Admission` sa, `tabStudent Admission Program` sap
where sa.name = sap.parent and sa.name = %s and sap.program = %s""", (student_admission, program), as_dict=1)
+
if student_admission:
return student_admission[0]
else:
diff --git a/erpnext/education/web_form/student_applicant/student_applicant.json b/erpnext/education/web_form/student_applicant/student_applicant.json
index b1ad754..1810f07 100644
--- a/erpnext/education/web_form/student_applicant/student_applicant.json
+++ b/erpnext/education/web_form/student_applicant/student_applicant.json
@@ -1,200 +1,248 @@
{
- "accept_payment": 0,
- "allow_comments": 0,
- "allow_delete": 0,
- "allow_edit": 1,
- "allow_incomplete": 0,
- "allow_multiple": 1,
- "allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
- "creation": "2016-09-22 13:10:10.792735",
- "doc_type": "Student Applicant",
- "docstatus": 0,
- "doctype": "Web Form",
- "idx": 0,
- "is_standard": 1,
- "login_required": 1,
- "max_attachment_size": 0,
- "modified": "2017-02-21 05:44:46.022738",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "student-applicant",
- "owner": "Administrator",
- "payment_button_label": "Buy Now",
- "published": 1,
- "route": "student-applicant",
- "show_sidebar": 1,
- "sidebar_items": [],
- "success_url": "/student-applicant",
- "title": "Student Applicant",
+ "accept_payment": 0,
+ "allow_comments": 0,
+ "allow_delete": 0,
+ "allow_edit": 1,
+ "allow_incomplete": 0,
+ "allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
+ "amount_based_on_field": 0,
+ "creation": "2016-09-22 13:10:10.792735",
+ "doc_type": "Student Applicant",
+ "docstatus": 0,
+ "doctype": "Web Form",
+ "idx": 0,
+ "is_standard": 1,
+ "login_required": 1,
+ "max_attachment_size": 0,
+ "modified": "2020-06-11 22:53:45.875310",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "student-applicant",
+ "owner": "Administrator",
+ "payment_button_label": "Buy Now",
+ "published": 1,
+ "route": "student-applicant",
+ "route_to_success_link": 0,
+ "show_attachments": 0,
+ "show_in_grid": 0,
+ "show_sidebar": 1,
+ "sidebar_items": [],
+ "success_url": "/student-applicant",
+ "title": "Student Applicant",
"web_form_fields": [
{
- "fieldname": "first_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "First Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "First Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Middle Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Middle Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "last_name",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Last Name",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Last Name",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "image",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Image",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "image",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Image",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "program",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Program",
- "max_length": 0,
- "max_value": 0,
- "options": "Program",
- "read_only": 0,
- "reqd": 1
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "program",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Program",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Program",
+ "read_only": 0,
+ "reqd": 1,
+ "show_in_filter": 0
+ },
{
- "fieldname": "academic_year",
- "fieldtype": "Link",
- "hidden": 0,
- "label": "Academic Year",
- "max_length": 0,
- "max_value": 0,
- "options": "Academic Year",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "academic_year",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Academic Year",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Academic Year",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "date_of_birth",
- "fieldtype": "Date",
- "hidden": 0,
- "label": "Date of Birth",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "date_of_birth",
+ "fieldtype": "Date",
+ "hidden": 0,
+ "label": "Date of Birth",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "blood_group",
- "fieldtype": "Select",
- "hidden": 0,
- "label": "Blood Group",
- "max_length": 0,
- "max_value": 0,
- "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "blood_group",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "label": "Blood Group",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_email_id",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Email ID",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_email_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Email ID",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "student_mobile_number",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Student Mobile Number",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_mobile_number",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Student Mobile Number",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "default": "INDIAN",
- "fieldname": "nationality",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Nationality",
- "max_length": 0,
- "max_value": 0,
- "options": "",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "default": "INDIAN",
+ "fieldname": "nationality",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Nationality",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_1",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 1",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_1",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 1",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "address_line_2",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Address Line 2",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "address_line_2",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Address Line 2",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "pincode",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Pincode",
- "max_length": 0,
- "max_value": 0,
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "pincode",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Pincode",
+ "max_length": 0,
+ "max_value": 0,
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "guardians",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Guardians",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Guardian",
- "read_only": 0,
- "reqd": 0
- },
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "guardians",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "label": "Guardians",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Guardian",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
{
- "fieldname": "siblings",
- "fieldtype": "Table",
- "hidden": 0,
- "label": "Siblings",
- "max_length": 0,
- "max_value": 0,
- "options": "Student Sibling",
- "read_only": 0,
- "reqd": 0
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "siblings",
+ "fieldtype": "Table",
+ "hidden": 0,
+ "label": "Siblings",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Sibling",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
+ },
+ {
+ "allow_read_on_all_link_options": 0,
+ "fieldname": "student_admission",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "label": "Student Admission",
+ "max_length": 0,
+ "max_value": 0,
+ "options": "Student Admission",
+ "read_only": 0,
+ "reqd": 0,
+ "show_in_filter": 0
}
]
}
\ 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 ca0e160..d59f909 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -95,10 +95,10 @@
items = get_order_items(shopify_order.get("line_items"), shopify_settings)
if not items:
- message = 'Following items are exists in order but relevant record not found in Product master'
+ message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
message += "\n" + ", ".join(product_not_exists)
- make_shopify_log(status="Error", exception=e, rollback=True)
+ make_shopify_log(status="Error", exception=message, rollback=True)
return ''
@@ -241,14 +241,17 @@
return taxes
def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
+ """Shipping lines represents the shipping details,
+ each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
- 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"),
+ "account_head": get_tax_account_head(tax),
+ "description": tax["title"],
+ "tax_amount": tax["price"],
+ "cost_center": shopify_settings.cost_center
+ })
return taxes
diff --git a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
index 6188652..6dedaa8 100644
--- a/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
+++ b/erpnext/erpnext_integrations/connectors/woocommerce_connection.py
@@ -49,12 +49,13 @@
if event == "created":
sys_lang = frappe.get_single("System Settings").language or 'en'
raw_billing_data = order.get("billing")
+ raw_shipping_data = order.get("shipping")
customer_name = raw_billing_data.get("first_name") + " " + raw_billing_data.get("last_name")
- link_customer_and_address(raw_billing_data, customer_name)
+ link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name)
link_items(order.get("line_items"), woocommerce_settings, sys_lang)
create_sales_order(order, woocommerce_settings, customer_name, sys_lang)
-def link_customer_and_address(raw_billing_data, customer_name):
+def link_customer_and_address(raw_billing_data, raw_shipping_data, customer_name):
customer_woo_com_email = raw_billing_data.get("email")
customer_exists = frappe.get_value("Customer", {"woocommerce_email": customer_woo_com_email})
if not customer_exists:
@@ -68,38 +69,80 @@
customer.customer_name = customer_name
customer.woocommerce_email = customer_woo_com_email
customer.flags.ignore_mandatory = True
- customer.save()
+ customer.save()
if customer_exists:
frappe.rename_doc("Customer", old_name, customer_name)
- address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email})
+ for address_type in ("Billing", "Shipping",):
+ try:
+ address = frappe.get_doc("Address", {"woocommerce_email": customer_woo_com_email, "address_type": address_type})
+ rename_address(address, customer)
+ except (
+ frappe.DoesNotExistError,
+ frappe.DuplicateEntryError,
+ frappe.ValidationError,
+ ):
+ pass
else:
- address = frappe.new_doc("Address")
+ create_address(raw_billing_data, customer, "Billing")
+ create_address(raw_shipping_data, customer, "Shipping")
+ create_contact(raw_billing_data, customer)
- address.address_line1 = raw_billing_data.get("address_1", "Not Provided")
- address.address_line2 = raw_billing_data.get("address_2", "Not Provided")
- address.city = raw_billing_data.get("city", "Not Provided")
- address.woocommerce_email = customer_woo_com_email
- address.address_type = "Billing"
- address.country = frappe.get_value("Country", {"code": raw_billing_data.get("country", "IN").lower()})
- address.state = raw_billing_data.get("state")
- address.pincode = raw_billing_data.get("postcode")
- address.phone = raw_billing_data.get("phone")
- address.email_id = customer_woo_com_email
+def create_contact(data, customer):
+ email = data.get("email", None)
+ phone = data.get("phone", None)
+
+ if not email and not phone:
+ return
+
+ contact = frappe.new_doc("Contact")
+ contact.first_name = data.get("first_name")
+ contact.last_name = data.get("last_name")
+ contact.is_primary_contact = 1
+ contact.is_billing_contact = 1
+
+ if phone:
+ contact.add_phone(phone, is_primary_mobile_no=1, is_primary_phone=1)
+
+ if email:
+ contact.add_email(email, is_primary=1)
+
+ contact.append("links", {
+ "link_doctype": "Customer",
+ "link_name": customer.name
+ })
+
+ contact.flags.ignore_mandatory = True
+ contact.save()
+
+def create_address(raw_data, customer, address_type):
+ address = frappe.new_doc("Address")
+
+ address.address_line1 = raw_data.get("address_1", "Not Provided")
+ address.address_line2 = raw_data.get("address_2", "Not Provided")
+ address.city = raw_data.get("city", "Not Provided")
+ address.woocommerce_email = customer.woocommerce_email
+ address.address_type = address_type
+ address.country = frappe.get_value("Country", {"code": raw_data.get("country", "IN").lower()})
+ address.state = raw_data.get("state")
+ address.pincode = raw_data.get("postcode")
+ address.phone = raw_data.get("phone")
+ address.email_id = customer.woocommerce_email
address.append("links", {
"link_doctype": "Customer",
- "link_name": customer.customer_name
+ "link_name": customer.name
})
+
address.flags.ignore_mandatory = True
- address = address.save()
+ address.save()
- if customer_exists:
- old_address_title = address.name
- new_address_title = customer.customer_name + "-billing"
- address.address_title = customer.customer_name
- address.save()
+def rename_address(address, customer):
+ old_address_title = address.name
+ new_address_title = customer.name + "-" + address.address_type
+ address.address_title = customer.customer_name
+ address.save()
- frappe.rename_doc("Address", old_address_title, new_address_title)
+ frappe.rename_doc("Address", old_address_title, new_address_title)
def link_items(items_list, woocommerce_settings, sys_lang):
for item_data in items_list:
@@ -111,7 +154,7 @@
else:
#Create Item
item = frappe.new_doc("Item")
-
+
item.item_name = item_data.get("name")
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
item.woocommerce_id = item_data.get("product_id")
@@ -145,7 +188,8 @@
company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
default_warehouse = _("Stores - {0}", sys_lang).format(company_abbr)
- if not frappe.db.exists("Warehouse", default_warehouse):
+ if not frappe.db.exists("Warehouse", default_warehouse) \
+ and not woocommerce_settings.warehouse:
frappe.throw(_("Please set Warehouse in Woocommerce Settings"))
for item in order.get("line_items"):
@@ -171,7 +215,7 @@
add_tax_details(new_sales_order, order.get("shipping_tax"), "Shipping Tax", woocommerce_settings.f_n_f_account)
add_tax_details(new_sales_order, order.get("shipping_total"), "Shipping Total", woocommerce_settings.f_n_f_account)
-
+
def add_tax_details(sales_order, price, desc, tax_account_head):
sales_order.append("taxes", {
"charge_type":"Actual",
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index a706223..c3371ed 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -124,10 +124,11 @@
@frappe.whitelist()
def sync_transactions(bank, bank_account):
-
- last_sync_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
- if last_sync_date:
- start_date = formatdate(last_sync_date, "YYYY-MM-dd")
+ '''Sync transactions based on the last integration date as the start date, after the sync is completed
+ add the transaction date of the oldest transaction as the last integration date'''
+ last_transaction_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
+ if last_transaction_date:
+ start_date = formatdate(last_transaction_date, "YYYY-MM-dd")
else:
start_date = formatdate(add_months(today(), -12), "YYYY-MM-dd")
end_date = formatdate(today(), "YYYY-MM-dd")
@@ -139,12 +140,14 @@
for transaction in reversed(transactions):
result += new_bank_transaction(transaction)
- frappe.logger().info("Plaid added {} new Bank Transactions from '{}' between {} and {}".format(
- len(result), bank_account, start_date, end_date))
+ if result:
+ last_transaction_date = frappe.db.get_value('Bank Transaction', result.pop(), 'date')
- frappe.db.set_value("Bank Account", bank_account, "last_integration_date", getdate(end_date))
+ frappe.logger().info("Plaid added {} new Bank Transactions from '{}' between {} and {}".format(
+ len(result), bank_account, start_date, end_date))
- return result
+ frappe.db.set_value("Bank Account", bank_account, "last_integration_date", last_transaction_date)
+
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
index 8f1b746..5339c99 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
@@ -133,7 +133,7 @@
"label": "Customer Settings"
},
{
- "description": "If Shopify not contains a customer in Order, then while syncing Orders, the system will consider default customer for order",
+ "description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order",
"fieldname": "default_customer",
"fieldtype": "Link",
"label": "Default Customer",
@@ -258,7 +258,7 @@
}
],
"issingle": 1,
- "modified": "2019-09-13 12:32:11.384757",
+ "modified": "2020-05-28 12:32:11.384757",
"modified_by": "umair@erpnext.com",
"module": "ERPNext Integrations",
"name": "Shopify Settings",
@@ -277,4 +277,4 @@
],
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 64c3b2d..25ffd28 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -8,6 +8,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import get_request_session
+from requests.exceptions import HTTPError
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.erpnext_integrations.utils import get_webhook_address
from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
@@ -29,19 +30,24 @@
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
# url = get_shopify_url('admin/webhooks.json', self)
created_webhooks = [d.method for d in self.webhooks]
- url = get_shopify_url('admin/api/2019-04/webhooks.json', self)
+ url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
for method in webhooks:
session = get_request_session()
try:
- d = session.post(url, data=json.dumps({
+ res = session.post(url, data=json.dumps({
"webhook": {
"topic": method,
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
"format": "json"
}
}), headers=get_header(self))
- d.raise_for_status()
- self.update_webhook_table(method, d.json())
+ res.raise_for_status()
+ self.update_webhook_table(method, res.json())
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
make_shopify_log(status="Warning", exception=e, rollback=True)
@@ -50,13 +56,18 @@
deleted_webhooks = []
for d in self.webhooks:
- url = get_shopify_url('admin/api/2019-04/webhooks/{0}.json'.format(d.webhook_id), self)
+ url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
try:
res = session.delete(url, headers=get_header(self))
res.raise_for_status()
deleted_webhooks.append(d)
+
+ except HTTPError as e:
+ error_message = res.json().get('errors', e)
+ make_shopify_log(status="Warning", exception=error_message, rollback=True)
+
except Exception as e:
- frappe.log_error(message=frappe.get_traceback(), title=e)
+ frappe.log_error(message=e, title='Shopify Webhooks Issue')
for d in deleted_webhooks:
self.remove(d)
@@ -125,4 +136,3 @@
}
create_custom_fields(custom_fields)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
index bde1011..f9f0bb3 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
@@ -8,7 +8,7 @@
shopify_variants_attr_list = ["option1", "option2", "option3"]
def sync_item_from_shopify(shopify_settings, item):
- url = get_shopify_url("admin/api/2019-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
+ url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
session = get_request_session()
try:
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
index d84c823..fd16d1e 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
@@ -1,7 +1,9 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Tally Migration', {
+frappe.provide("erpnext.tally_migration");
+
+frappe.ui.form.on("Tally Migration", {
onload: function (frm) {
let reload_status = true;
frappe.realtime.on("tally_migration_progress_update", function (data) {
@@ -35,7 +37,17 @@
}
});
},
+
refresh: function (frm) {
+ frm.trigger("show_logs_preview");
+ erpnext.tally_migration.failed_import_log = JSON.parse(frm.doc.failed_import_log);
+ erpnext.tally_migration.fixed_errors_log = JSON.parse(frm.doc.fixed_errors_log);
+
+ ["default_round_off_account", "default_warehouse", "default_cost_center"].forEach(account => {
+ frm.toggle_reqd(account, frm.doc.is_master_data_imported === 1)
+ frm.toggle_enable(account, frm.doc.is_day_book_data_processed != 1)
+ })
+
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
if (frm.doc.is_master_data_processed) {
if (frm.doc.status != "Importing Master Data") {
@@ -47,6 +59,7 @@
}
}
}
+
if (frm.doc.day_book_data && !frm.doc.is_day_book_data_imported) {
if (frm.doc.is_day_book_data_processed) {
if (frm.doc.status != "Importing Day Book Data") {
@@ -59,6 +72,17 @@
}
}
},
+
+ erpnext_company: function (frm) {
+ frappe.db.exists("Company", frm.doc.erpnext_company).then(exists => {
+ if (exists) {
+ frappe.msgprint(
+ __("Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts", [frm.doc.erpnext_company]),
+ );
+ }
+ });
+ },
+
add_button: function (frm, label, method) {
frm.add_custom_button(
label,
@@ -71,5 +95,255 @@
frm.reload_doc();
}
);
+ },
+
+ render_html_table(frm, shown_logs, hidden_logs, field) {
+ if (shown_logs && shown_logs.length > 0) {
+ frm.toggle_display(field, true);
+ } else {
+ frm.toggle_display(field, false);
+ return
+ }
+ let rows = erpnext.tally_migration.get_html_rows(shown_logs, field);
+ let rows_head, table_caption;
+
+ let table_footer = (hidden_logs && (hidden_logs.length > 0)) ? `<tr class="text-muted">
+ <td colspan="4">And ${hidden_logs.length} more others</td>
+ </tr>`: "";
+
+ if (field === "fixed_error_log_preview") {
+ rows_head = `<th width="75%">${__("Meta Data")}</th>
+ <th width="10%">${__("Unresolve")}</th>`
+ table_caption = "Resolved Issues"
+ } else {
+ rows_head = `<th width="75%">${__("Error Message")}</th>
+ <th width="10%">${__("Create")}</th>`
+ table_caption = "Error Log"
+ }
+
+ frm.get_field(field).$wrapper.html(`
+ <table class="table table-bordered">
+ <caption>${table_caption}</caption>
+ <tr class="text-muted">
+ <th width="5%">${__("#")}</th>
+ <th width="10%">${__("DocType")}</th>
+ ${rows_head}
+ </tr>
+ ${rows}
+ ${table_footer}
+ </table>
+ `);
+ },
+
+ show_error_summary(frm) {
+ let summary = erpnext.tally_migration.failed_import_log.reduce((summary, row) => {
+ if (row.doc) {
+ if (summary[row.doc.doctype]) {
+ summary[row.doc.doctype] += 1;
+ } else {
+ summary[row.doc.doctype] = 1;
+ }
+ }
+ return summary
+ }, {});
+ console.table(summary);
+ },
+
+ show_logs_preview(frm) {
+ let empty = "[]";
+ let import_log = frm.doc.failed_import_log || empty;
+ let completed_log = frm.doc.fixed_errors_log || empty;
+ let render_section = !(import_log === completed_log && import_log === empty);
+
+ frm.toggle_display("import_log_section", render_section);
+ if (render_section) {
+ frm.trigger("show_error_summary");
+ frm.trigger("show_errored_import_log");
+ frm.trigger("show_fixed_errors_log");
+ }
+ },
+
+ show_errored_import_log(frm) {
+ let import_log = erpnext.tally_migration.failed_import_log;
+ let logs = import_log.slice(0, 20);
+ let hidden_logs = import_log.slice(20);
+
+ frm.events.render_html_table(frm, logs, hidden_logs, "failed_import_preview");
+ },
+
+ show_fixed_errors_log(frm) {
+ let completed_log = erpnext.tally_migration.fixed_errors_log;
+ let logs = completed_log.slice(0, 20);
+ let hidden_logs = completed_log.slice(20);
+
+ frm.events.render_html_table(frm, logs, hidden_logs, "fixed_error_log_preview");
}
});
+
+erpnext.tally_migration.getError = (traceback) => {
+ /* Extracts the Error Message from the Python Traceback or Solved error */
+ let is_multiline = traceback.trim().indexOf("\n") != -1;
+ let message;
+
+ if (is_multiline) {
+ let exc_error_idx = traceback.trim().lastIndexOf("\n") + 1
+ let error_line = traceback.substr(exc_error_idx)
+ let split_str_idx = (error_line.indexOf(':') > 0) ? error_line.indexOf(':') + 1 : 0;
+ message = error_line.slice(split_str_idx).trim();
+ } else {
+ message = traceback;
+ }
+
+ return message
+}
+
+erpnext.tally_migration.cleanDoc = (obj) => {
+ /* Strips all null and empty values of your JSON object */
+ let temp = obj;
+ $.each(temp, function(key, value){
+ if (value === "" || value === null){
+ delete obj[key];
+ } else if (Object.prototype.toString.call(value) === '[object Object]') {
+ erpnext.tally_migration.cleanDoc(value);
+ } else if ($.isArray(value)) {
+ $.each(value, function (k,v) { erpnext.tally_migration.cleanDoc(v); });
+ }
+ });
+ return temp;
+}
+
+erpnext.tally_migration.unresolve = (document) => {
+ /* Mark document migration as unresolved ie. move to failed error log */
+ let frm = cur_frm;
+ let failed_log = erpnext.tally_migration.failed_import_log;
+ let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+ let modified_fixed_log = fixed_log.filter(row => {
+ if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+ return row
+ }
+ });
+
+ failed_log.push({ doc: document, exc: `Marked unresolved on ${Date()}` });
+
+ frm.doc.failed_import_log = JSON.stringify(failed_log);
+ frm.doc.fixed_errors_log = JSON.stringify(modified_fixed_log);
+
+ frm.dirty();
+ frm.save();
+}
+
+erpnext.tally_migration.resolve = (document) => {
+ /* Mark document migration as resolved ie. move to fixed error log */
+ let frm = cur_frm;
+ let failed_log = erpnext.tally_migration.failed_import_log;
+ let fixed_log = erpnext.tally_migration.fixed_errors_log;
+
+ let modified_failed_log = failed_log.filter(row => {
+ if (!frappe.utils.deep_equal(erpnext.tally_migration.cleanDoc(row.doc), document)) {
+ return row
+ }
+ });
+ fixed_log.push({ doc: document, exc: `Solved on ${Date()}` });
+
+ frm.doc.failed_import_log = JSON.stringify(modified_failed_log);
+ frm.doc.fixed_errors_log = JSON.stringify(fixed_log);
+
+ frm.dirty();
+ frm.save();
+}
+
+erpnext.tally_migration.create_new_doc = (document) => {
+ /* Mark as resolved and create new document */
+ erpnext.tally_migration.resolve(document);
+ return frappe.call({
+ type: "POST",
+ method: 'erpnext.erpnext_integrations.doctype.tally_migration.tally_migration.new_doc',
+ args: {
+ document
+ },
+ freeze: true,
+ callback: function(r) {
+ if(!r.exc) {
+ frappe.model.sync(r.message);
+ frappe.get_doc(r.message.doctype, r.message.name).__run_link_triggers = true;
+ frappe.set_route("Form", r.message.doctype, r.message.name);
+ }
+ }
+ });
+}
+
+erpnext.tally_migration.get_html_rows = (logs, field) => {
+ let index = 0;
+ let rows = logs
+ .map(({ doc, exc }) => {
+ let id = frappe.dom.get_unique_id();
+ let traceback = exc;
+
+ let error_message = erpnext.tally_migration.getError(traceback);
+ index++;
+
+ let show_traceback = `
+ <button class="btn btn-default btn-xs m-3" type="button" data-toggle="collapse" data-target="#${id}-traceback" aria-expanded="false" aria-controls="${id}-traceback">
+ ${__("Show Traceback")}
+ </button>
+ <div class="collapse margin-top" id="${id}-traceback">
+ <div class="well">
+ <pre style="font-size: smaller;">${traceback}</pre>
+ </div>
+ </div>`;
+
+ let show_doc = `
+ <button class='btn btn-default btn-xs m-3' type='button' data-toggle='collapse' data-target='#${id}-doc' aria-expanded='false' aria-controls='${id}-doc'>
+ ${__("Show Document")}
+ </button>
+ <div class="collapse margin-top" id="${id}-doc">
+ <div class="well">
+ <pre style="font-size: smaller;">${JSON.stringify(erpnext.tally_migration.cleanDoc(doc), null, 1)}</pre>
+ </div>
+ </div>`;
+
+ let create_button = `
+ <button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.create_new_doc(${JSON.stringify(doc)})'>
+ ${__("Create Document")}
+ </button>`
+
+ let mark_as_unresolved = `
+ <button class='btn btn-default btn-xs m-3' type='button' onclick='erpnext.tally_migration.unresolve(${JSON.stringify(doc)})'>
+ ${__("Mark as unresolved")}
+ </button>`
+
+ if (field === "fixed_error_log_preview") {
+ return `<tr>
+ <td>${index}</td>
+ <td>
+ <div>${doc.doctype}</div>
+ </td>
+ <td>
+ <div>${error_message}</div>
+ <div>${show_doc}</div>
+ </td>
+ <td>
+ <div>${mark_as_unresolved}</div>
+ </td>
+ </tr>`;
+ } else {
+ return `<tr>
+ <td>${index}</td>
+ <td>
+ <div>${doc.doctype}</div>
+ </td>
+ <td>
+ <div>${error_message}</div>
+ <div>${show_traceback}</div>
+ <div>${show_doc}</div>
+ </td>
+ <td>
+ <div>${create_button}</div>
+ </td>
+ </tr>`;
+ }
+ }).join("");
+
+ return rows
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
index dc6f093..417d943 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
@@ -28,14 +28,19 @@
"vouchers",
"accounts_section",
"default_warehouse",
- "round_off_account",
+ "default_round_off_account",
"column_break_21",
"default_cost_center",
"day_book_section",
"day_book_data",
"column_break_27",
"is_day_book_data_processed",
- "is_day_book_data_imported"
+ "is_day_book_data_imported",
+ "import_log_section",
+ "failed_import_log",
+ "fixed_errors_log",
+ "failed_import_preview",
+ "fixed_error_log_preview"
],
"fields": [
{
@@ -57,6 +62,7 @@
"fieldname": "tally_creditors_account",
"fieldtype": "Data",
"label": "Tally Creditors Account",
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1",
"reqd": 1
},
{
@@ -69,6 +75,7 @@
"fieldname": "tally_debtors_account",
"fieldtype": "Data",
"label": "Tally Debtors Account",
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1",
"reqd": 1
},
{
@@ -92,7 +99,7 @@
"fieldname": "erpnext_company",
"fieldtype": "Data",
"label": "ERPNext Company",
- "read_only_depends_on": "eval:doc.is_master_data_processed == 1"
+ "read_only_depends_on": "eval:doc.is_master_data_processed==1"
},
{
"fieldname": "processed_files_section",
@@ -136,6 +143,7 @@
},
{
"depends_on": "is_master_data_imported",
+ "description": "The accounts are set by the system automatically but do confirm these defaults",
"fieldname": "accounts_section",
"fieldtype": "Section Break",
"label": "Accounts"
@@ -147,12 +155,6 @@
"options": "Warehouse"
},
{
- "fieldname": "round_off_account",
- "fieldtype": "Link",
- "label": "Round Off Account",
- "options": "Account"
- },
- {
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
@@ -212,11 +214,47 @@
"fieldname": "default_uom",
"fieldtype": "Link",
"label": "Default UOM",
- "options": "UOM"
+ "options": "UOM",
+ "read_only_depends_on": "eval:doc.is_master_data_imported==1"
+ },
+ {
+ "default": "[]",
+ "fieldname": "failed_import_log",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "options": "JSON"
+ },
+ {
+ "fieldname": "failed_import_preview",
+ "fieldtype": "HTML",
+ "label": "Failed Import Log"
+ },
+ {
+ "fieldname": "import_log_section",
+ "fieldtype": "Section Break",
+ "label": "Import Log"
+ },
+ {
+ "fieldname": "default_round_off_account",
+ "fieldtype": "Link",
+ "label": "Default Round Off Account",
+ "options": "Account"
+ },
+ {
+ "default": "[]",
+ "fieldname": "fixed_errors_log",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "options": "JSON"
+ },
+ {
+ "fieldname": "fixed_error_log_preview",
+ "fieldtype": "HTML",
+ "label": "Fixed Error Log"
}
],
"links": [],
- "modified": "2020-04-16 13:03:28.894919",
+ "modified": "2020-04-28 00:29:18.039826",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Tally Migration",
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 13474e1..462685f 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -6,6 +6,7 @@
import json
import re
+import sys
import traceback
import zipfile
from decimal import Decimal
@@ -15,18 +16,34 @@
import frappe
from erpnext import encode_company_abbr
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
+from erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer import unset_existing_data
+
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
from frappe.model.naming import getseries, revert_series_if_last
from frappe.utils.data import format_datetime
-
PRIMARY_ACCOUNT = "Primary"
VOUCHER_CHUNK_SIZE = 500
+@frappe.whitelist()
+def new_doc(document):
+ document = json.loads(document)
+ doctype = document.pop("doctype")
+ document.pop("name", None)
+ doc = frappe.new_doc(doctype)
+ doc.update(document)
+
+ return doc
+
class TallyMigration(Document):
+ def validate(self):
+ failed_import_log = json.loads(self.failed_import_log)
+ sorted_failed_import_log = sorted(failed_import_log, key=lambda row: row["doc"]["creation"])
+ self.failed_import_log = json.dumps(sorted_failed_import_log)
+
def autoname(self):
if not self.name:
self.name = "Tally Migration on " + format_datetime(self.creation)
@@ -65,9 +82,17 @@
"attached_to_name": self.name,
"content": json.dumps(value),
"is_private": True
- }).insert()
+ })
+ try:
+ f.insert()
+ except frappe.DuplicateEntryError:
+ pass
setattr(self, key, f.file_url)
+ def set_account_defaults(self):
+ self.default_cost_center, self.default_round_off_account = frappe.db.get_value("Company", self.erpnext_company, ["cost_center", "round_off_account"])
+ self.default_warehouse = frappe.db.get_value("Stock Settings", "Stock Settings", "default_warehouse")
+
def _process_master_data(self):
def get_company_name(collection):
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
@@ -84,7 +109,11 @@
children, parents = get_children_and_parent_dict(accounts)
group_set = [acc[1] for acc in accounts if acc[2]]
children, customers, suppliers = remove_parties(parents, children, group_set)
- coa = traverse({}, children, roots, roots, group_set)
+
+ try:
+ coa = traverse({}, children, roots, roots, group_set)
+ except RecursionError:
+ self.log(_("Error occured while parsing Chart of Accounts: Please make sure that no two accounts have the same name"))
for account in coa:
coa[account]["root_type"] = root_type_map[account]
@@ -126,14 +155,18 @@
def remove_parties(parents, children, group_set):
customers, suppliers = set(), set()
for account in parents:
+ found = False
if self.tally_creditors_account in parents[account]:
- children.pop(account, None)
+ found = True
if account not in group_set:
suppliers.add(account)
- elif self.tally_debtors_account in parents[account]:
- children.pop(account, None)
+ if self.tally_debtors_account in parents[account]:
+ found = True
if account not in group_set:
customers.add(account)
+ if found:
+ children.pop(account, None)
+
return children, customers, suppliers
def traverse(tree, children, accounts, roots, group_set):
@@ -151,6 +184,7 @@
parties, addresses = [], []
for account in collection.find_all("LEDGER"):
party_type = None
+ links = []
if account.NAME.string.strip() in customers:
party_type = "Customer"
parties.append({
@@ -161,7 +195,9 @@
"territory": "All Territories",
"customer_type": "Individual",
})
- elif account.NAME.string.strip() in suppliers:
+ links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
+ if account.NAME.string.strip() in suppliers:
party_type = "Supplier"
parties.append({
"doctype": party_type,
@@ -170,6 +206,8 @@
"supplier_group": "All Supplier Groups",
"supplier_type": "Individual",
})
+ links.append({"link_doctype": party_type, "link_name": account["NAME"]})
+
if party_type:
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
addresses.append({
@@ -183,7 +221,7 @@
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
- "links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
+ "links": links
})
return parties, addresses
@@ -242,12 +280,18 @@
def create_company_and_coa(coa_file_url):
coa_file = frappe.get_doc("File", {"file_url": coa_file_url})
frappe.local.flags.ignore_chart_of_accounts = True
- company = frappe.get_doc({
- "doctype": "Company",
- "company_name": self.erpnext_company,
- "default_currency": "INR",
- "enable_perpetual_inventory": 0,
- }).insert()
+
+ try:
+ company = frappe.get_doc({
+ "doctype": "Company",
+ "company_name": self.erpnext_company,
+ "default_currency": "INR",
+ "enable_perpetual_inventory": 0,
+ }).insert()
+ except frappe.DuplicateEntryError:
+ company = frappe.get_doc("Company", self.erpnext_company)
+ unset_existing_data(self.erpnext_company)
+
frappe.local.flags.ignore_chart_of_accounts = False
create_charts(company.name, custom_chart=json.loads(coa_file.get_content()))
company.create_default_warehouses()
@@ -256,36 +300,35 @@
parties_file = frappe.get_doc("File", {"file_url": parties_file_url})
for party in json.loads(parties_file.get_content()):
try:
- frappe.get_doc(party).insert()
+ party_doc = frappe.get_doc(party)
+ party_doc.insert()
except:
- self.log(party)
+ self.log(party_doc)
addresses_file = frappe.get_doc("File", {"file_url": addresses_file_url})
for address in json.loads(addresses_file.get_content()):
try:
- frappe.get_doc(address).insert(ignore_mandatory=True)
+ address_doc = frappe.get_doc(address)
+ address_doc.insert(ignore_mandatory=True)
except:
- try:
- gstin = address.pop("gstin", None)
- frappe.get_doc(address).insert(ignore_mandatory=True)
- self.log({"address": address, "message": "Invalid GSTIN: {}. Address was created without GSTIN".format(gstin)})
- except:
- self.log(address)
+ self.log(address_doc)
def create_items_uoms(items_file_url, uoms_file_url):
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
for uom in json.loads(uoms_file.get_content()):
if not frappe.db.exists(uom):
try:
- frappe.get_doc(uom).insert()
+ uom_doc = frappe.get_doc(uom)
+ uom_doc.insert()
except:
- self.log(uom)
+ self.log(uom_doc)
items_file = frappe.get_doc("File", {"file_url": items_file_url})
for item in json.loads(items_file.get_content()):
try:
- frappe.get_doc(item).insert()
+ item_doc = frappe.get_doc(item)
+ item_doc.insert()
except:
- self.log(item)
+ self.log(item_doc)
try:
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
@@ -299,10 +342,13 @@
self.publish("Import Master Data", _("Done"), 4, 4)
+ self.set_account_defaults()
self.is_master_data_imported = 1
+ frappe.db.commit()
except:
self.publish("Import Master Data", _("Process Failed"), -1, 5)
+ frappe.db.rollback()
self.log()
finally:
@@ -323,7 +369,9 @@
processed_voucher = function(voucher)
if processed_voucher:
vouchers.append(processed_voucher)
+ frappe.db.commit()
except:
+ frappe.db.rollback()
self.log(voucher)
return vouchers
@@ -349,6 +397,7 @@
journal_entry = {
"doctype": "Journal Entry",
"tally_guid": voucher.GUID.string.strip(),
+ "tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
"posting_date": voucher.DATE.string.strip(),
"company": self.erpnext_company,
"accounts": accounts,
@@ -377,6 +426,7 @@
"doctype": doctype,
party_field: voucher.PARTYNAME.string.strip(),
"tally_guid": voucher.GUID.string.strip(),
+ "tally_voucher_no": voucher.VOUCHERNUMBER.string.strip() if voucher.VOUCHERNUMBER else "",
"posting_date": voucher.DATE.string.strip(),
"due_date": voucher.DATE.string.strip(),
"items": get_voucher_items(voucher, doctype),
@@ -468,14 +518,21 @@
oldest_year = new_year
def create_custom_fields(doctypes):
- for doctype in doctypes:
- df = {
- "fieldtype": "Data",
- "fieldname": "tally_guid",
- "read_only": 1,
- "label": "Tally GUID"
- }
- create_custom_field(doctype, df)
+ tally_guid_df = {
+ "fieldtype": "Data",
+ "fieldname": "tally_guid",
+ "read_only": 1,
+ "label": "Tally GUID"
+ }
+ tally_voucher_no_df = {
+ "fieldtype": "Data",
+ "fieldname": "tally_voucher_no",
+ "read_only": 1,
+ "label": "Tally Voucher Number"
+ }
+ for df in [tally_guid_df, tally_voucher_no_df]:
+ for doctype in doctypes:
+ create_custom_field(doctype, df)
def create_price_list():
frappe.get_doc({
@@ -490,7 +547,7 @@
try:
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
- frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
+ frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.default_round_off_account)
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
vouchers = json.loads(vouchers_file.get_content())
@@ -521,11 +578,14 @@
for index, voucher in enumerate(chunk, start=start):
try:
- doc = frappe.get_doc(voucher).insert()
- doc.submit()
+ voucher_doc = frappe.get_doc(voucher)
+ voucher_doc.insert()
+ voucher_doc.submit()
self.publish("Importing Vouchers", _("{} of {}").format(index, total), index, total)
+ frappe.db.commit()
except:
- self.log(voucher)
+ frappe.db.rollback()
+ self.log(voucher_doc)
if is_last:
self.status = ""
@@ -551,9 +611,22 @@
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
def log(self, data=None):
- data = data or self.status
- message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
- return frappe.log_error(title="Tally Migration Error", message=message)
+ if isinstance(data, frappe.model.document.Document):
+ if sys.exc_info()[1].__class__ != frappe.DuplicateEntryError:
+ failed_import_log = json.loads(self.failed_import_log)
+ doc = data.as_dict()
+ failed_import_log.append({
+ "doc": doc,
+ "exc": traceback.format_exc()
+ })
+ self.failed_import_log = json.dumps(failed_import_log, separators=(',', ':'))
+ self.save()
+ frappe.db.commit()
+
+ else:
+ data = data or self.status
+ message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
+ return frappe.log_error(title="Tally Migration Error", message=message)
def set_status(self, status=""):
self.status = status
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/healthcare/dashboard_chart_source/__init__.py
similarity index 100%
copy from erpnext/hr/report/department_analytics/__init__.py
copy to erpnext/healthcare/dashboard_chart_source/__init__.py
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
similarity index 100%
copy from erpnext/hr/report/department_analytics/__init__.py
copy to erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
new file mode 100644
index 0000000..dd6dc66
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Department wise Patient Appointments"] = {
+ method: "erpnext.healthcare.dashboard_chart_source.department_wise_patient_appointments.department_wise_patient_appointments.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
new file mode 100644
index 0000000..00301ef
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-18 19:18:42.571045",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-18 19:18:42.571045",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Department wise Patient Appointments",
+ "owner": "Administrator",
+ "source_name": "Department wise Patient Appointments",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
new file mode 100644
index 0000000..062da6e
--- /dev/null
+++ b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils.dashboard import cache_source
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ if chart_name:
+ chart = frappe.get_doc('Dashboard Chart', chart_name)
+ else:
+ chart = frappe._dict(frappe.parse_json(chart))
+
+ filters = frappe.parse_json(filters)
+
+ data = frappe.db.get_list('Medical Department', fields=['name'])
+ if not filters:
+ filters = {}
+
+ status = ['Open', 'Scheduled', 'Closed', 'Cancelled']
+ for department in data:
+ filters['department'] = department.name
+ department['total_appointments'] = frappe.db.count('Patient Appointment', filters=filters)
+
+ for entry in status:
+ filters['status'] = entry
+ department[frappe.scrub(entry)] = frappe.db.count('Patient Appointment', filters=filters)
+ filters.pop('status')
+
+ sorted_department_map = sorted(data, key = lambda i: i['total_appointments'], reverse=True)
+
+ if len(sorted_department_map) > 10:
+ sorted_department_map = sorted_department_map[:10]
+
+ labels = []
+ open_appointments = []
+ scheduled = []
+ closed = []
+ cancelled = []
+
+ for department in sorted_department_map:
+ labels.append(department.name)
+ open_appointments.append(department.open)
+ scheduled.append(department.scheduled)
+ closed.append(department.closed)
+ cancelled.append(department.cancelled)
+
+ return {
+ 'labels': labels,
+ 'datasets': [
+ {
+ 'name': 'Open',
+ 'values': open_appointments
+ },
+ {
+ 'name': 'Scheduled',
+ 'values': scheduled
+ },
+ {
+ 'name': 'Closed',
+ 'values': closed
+ },
+ {
+ 'name': 'Cancelled',
+ 'values': cancelled
+ }
+ ],
+ 'type': 'bar'
+ }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py
index fc3d62f..94668a1 100644
--- a/erpnext/healthcare/dashboard_fixtures.py
+++ b/erpnext/healthcare/dashboard_fixtures.py
@@ -3,33 +3,60 @@
import frappe
import json
-
+from frappe import _
def get_data():
return frappe._dict({
"dashboards": get_dashboards(),
"charts": get_charts(),
+ "number_cards": get_number_cards(),
})
+def get_company():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company = frappe.get_list("Company", limit=1)
+ if company:
+ return company[0].name
+ return None
+
def get_dashboards():
return [{
"name": "Healthcare",
"dashboard_name": "Healthcare",
"charts": [
- { "chart": "Patient Appointments" }
+ { "chart": "Patient Appointments", "width": "Full"},
+ { "chart": "In-Patient Status", "width": "Half"},
+ { "chart": "Clinical Procedures Status", "width": "Half"},
+ { "chart": "Lab Tests", "width": "Half"},
+ { "chart": "Clinical Procedures", "width": "Half"},
+ { "chart": "Symptoms", "width": "Half"},
+ { "chart": "Diagnoses", "width": "Half"},
+ { "chart": "Department wise Patient Appointments", "width": "Full"}
+ ],
+ "cards": [
+ { "card": "Total Patients" },
+ { "card": "Total Patient Admitted" },
+ { "card": "Open Appointments" },
+ { "card": "Appointments to Bill" }
]
}]
def get_charts():
+ company = get_company()
return [
{
"doctype": "Dashboard Chart",
"time_interval": "Daily",
"name": "Patient Appointments",
- "chart_name": "Patient Appointments",
+ "chart_name": _("Patient Appointments"),
"timespan": "Last Month",
- "color": "#77ecca",
- "filters_json": json.dumps({}),
+ "filters_json": json.dumps([
+ ["Patient Appointment", "company", "=", company, False],
+ ["Patient Appointment", "status", "!=", "Cancelled"]
+ ]),
"chart_type": "Count",
"timeseries": 1,
"based_on": "appointment_datetime",
@@ -37,5 +64,182 @@
"document_type": "Patient Appointment",
"type": "Line",
"width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Department wise Patient Appointments",
+ "chart_name": _("Department wise Patient Appointments"),
+ "chart_type": "Custom",
+ "source": "Department wise Patient Appointments",
+ "filters_json": json.dumps([]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Bar",
+ "width": "Full",
+ "custom_options": json.dumps({
+ "colors": ["#7CD5FA", "#5F62F6", "#7544E2", "#EE5555"],
+ "barOptions":{
+ "stacked":1
+ },
+ "height": 300
+ })
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Lab Tests",
+ "chart_name": _("Lab Tests"),
+ "chart_type": "Group By",
+ "document_type": "Lab Test",
+ "group_by_type": "Count",
+ "group_by_based_on": "template",
+ "filters_json": json.dumps([
+ ["Lab Test", "company", "=", company, False],
+ ["Lab Test", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Clinical Procedures",
+ "chart_name": _("Clinical Procedures"),
+ "chart_type": "Group By",
+ "document_type": "Clinical Procedure",
+ "group_by_type": "Count",
+ "group_by_based_on": "procedure_template",
+ "filters_json": json.dumps([
+ ["Clinical Procedure", "company", "=", company, False],
+ ["Clinical Procedure", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "In-Patient Status",
+ "chart_name": _("In-Patient Status"),
+ "chart_type": "Group By",
+ "document_type": "Inpatient Record",
+ "group_by_type": "Count",
+ "group_by_based_on": "status",
+ "filters_json": json.dumps([
+ ["Inpatient Record", "company", "=", company, False]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Bar",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Clinical Procedures Status",
+ "chart_name": _("Clinical Procedure Status"),
+ "chart_type": "Group By",
+ "document_type": "Clinical Procedure",
+ "group_by_type": "Count",
+ "group_by_based_on": "status",
+ "filters_json": json.dumps([
+ ["Clinical Procedure", "company", "=", company, False],
+ ["Clinical Procedure", "docstatus", "=", 1]
+ ]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Pie",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Symptoms",
+ "chart_name": _("Symptoms"),
+ "chart_type": "Group By",
+ "document_type": "Patient Encounter Symptom",
+ "group_by_type": "Count",
+ "group_by_based_on": "complaint",
+ "filters_json": json.dumps([]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Diagnoses",
+ "chart_name": _("Diagnoses"),
+ "chart_type": "Group By",
+ "document_type": "Patient Encounter Diagnosis",
+ "group_by_type": "Count",
+ "group_by_based_on": "diagnosis",
+ "filters_json": json.dumps([]),
+ 'is_public': 1,
+ "owner": "Administrator",
+ "type": "Percentage",
+ "width": "Half",
}
]
+
+def get_number_cards():
+ company = get_company()
+ return [
+ {
+ "name": "Total Patients",
+ "label": _("Total Patients"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": json.dumps(
+ [["Patient","status","=","Active",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Total Patients Admitted",
+ "label": _("Total Patients Admitted"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient",
+ "filters_json": json.dumps(
+ [["Patient","inpatient_status","=","Admitted",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Open Appointments",
+ "label": _("Open Appointments"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "filters_json": json.dumps(
+ [["Patient Appointment","company","=",company,False],
+ ["Patient Appointment","status","=","Open",False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ },
+ {
+ "name": "Appointments to Bill",
+ "label": _("Appointments To Bill"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Patient Appointment",
+ "filters_json": json.dumps(
+ [["Patient Appointment","company","=",company,False],
+ ["Patient Appointment","invoiced","=",0,False]]
+ ),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json
index 5cf09b3..334b655 100644
--- a/erpnext/healthcare/desk_page/healthcare/healthcare.json
+++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json
@@ -60,26 +60,30 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
- "modified": "2020-04-25 22:31:36.576444",
+ "modified": "2020-05-28 19:02:28.824995",
"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": "#ffe8cd",
"format": "{} Open",
"label": "Patient Appointment",
"link_to": "Patient Appointment",
- "stats_filter": "{\n \"status\": \"Open\"\n}",
+ "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}",
"type": "DocType"
},
{
+ "color": "#ffe8cd",
"format": "{} Active",
"label": "Patient",
"link_to": "Patient",
@@ -87,10 +91,11 @@
"type": "DocType"
},
{
+ "color": "#cef6d1",
"format": "{} Vacant",
"label": "Healthcare Service Unit",
"link_to": "Healthcare Service Unit",
- "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}",
+ "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}",
"type": "DocType"
},
{
@@ -102,6 +107,11 @@
"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_template/clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
index 57f4cdf..16d4540 100644
--- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
+++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js
@@ -144,3 +144,38 @@
}
};
});
+
+frappe.tour['Clinical Procedure Template'] = [
+ {
+ fieldname: 'template',
+ title: __('Template Name'),
+ description: __('Enter a name for the Clinical Procedure Template')
+ },
+ {
+ fieldname: 'item_code',
+ title: __('Item Code'),
+ description: __('Set the Item Code which will be used for billing the Clinical Procedure.')
+ },
+ {
+ fieldname: 'item_group',
+ title: __('Item Group'),
+ description: __('Select an Item Group for the Clinical Procedure Item.')
+ },
+ {
+ fieldname: 'is_billable',
+ title: __('Clinical Procedure Rate'),
+ description: __('Check this if the Clinical Procedure is billable and also set the rate.')
+ },
+ {
+ fieldname: 'consume_stock',
+ title: __('Allow Stock Consumption'),
+ description: __('Check this if the Clinical Procedure utilises consumables. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/clinical_procedure_template#22-manage-procedure-consumables' target='_blank'>here</a>" + __(' to know more')
+
+ },
+ {
+ fieldname: 'medical_department',
+ title: __('Medical Department'),
+ description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
index 4ab3b6e..fc0b241 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js
@@ -108,3 +108,38 @@
});
}
});
+
+frappe.tour['Healthcare Practitioner'] = [
+ {
+ fieldname: 'employee',
+ title: __('Employee'),
+ description: __('If you want to track Payroll and other HRMS operations for a Practitoner, create an Employee and link it here.')
+ },
+ {
+ fieldname: 'practitioner_schedules',
+ title: __('Practitioner Schedules'),
+ description: __('Set the Practitioner Schedule you just created. This will be used while booking appointments.')
+ },
+ {
+ fieldname: 'op_consulting_charge_item',
+ title: __('Out Patient Consulting Charge Item'),
+ description: __('Create a service item for Out Patient Consulting.')
+ },
+ {
+ fieldname: 'inpatient_visit_charge_item',
+ title: __('Inpatient Visit Charge Item'),
+ description: __('If this Healthcare Practitioner works for the In-Patient Department, create a service item for Inpatient Visits.')
+ },
+ {
+ fieldname: 'op_consulting_charge',
+ title: __('Out Patient Consulting Charge'),
+ description: __('Set the Out Patient Consulting Charge for this Practitioner.')
+
+ },
+ {
+ fieldname: 'inpatient_visit_charge',
+ title: __('Inpatient Visit Charge'),
+ description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.')
+ }
+];
+
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
index 0c13b6a..3dc7c1e 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py
@@ -70,6 +70,7 @@
if frappe.db.get_value('Item', item, 'is_stock_item'):
frappe.throw(_(msg))
+@frappe.whitelist()
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ['name', 'practitioner_name', 'mobile_phone']
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
index ea4ae84..9ee865a 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
@@ -12,7 +12,6 @@
"engine": "InnoDB",
"field_order": [
"healthcare_service_unit_name",
- "parent_healthcare_service_unit",
"is_group",
"service_unit_type",
"allow_appointments",
@@ -20,8 +19,10 @@
"inpatient_occupancy",
"occupancy_status",
"column_break_9",
- "warehouse",
"company",
+ "warehouse",
+ "tree_details_section",
+ "parent_healthcare_service_unit",
"lft",
"rgt",
"old_parent"
@@ -51,7 +52,6 @@
"depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1",
"fieldname": "is_group",
"fieldtype": "Check",
- "in_list_view": 1,
"label": "Is Group"
},
{
@@ -63,12 +63,12 @@
"options": "Healthcare Service Unit Type"
},
{
- "bold": 1,
"default": "0",
"depends_on": "eval:doc.is_group != 1 && doc.inpatient_occupancy != 1",
"fetch_from": "service_unit_type.allow_appointments",
"fieldname": "allow_appointments",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Allow Appointments",
"no_copy": 1,
"read_only": 1
@@ -90,6 +90,7 @@
"fetch_from": "service_unit_type.inpatient_occupancy",
"fieldname": "inpatient_occupancy",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Inpatient Occupancy",
"no_copy": 1,
"read_only": 1,
@@ -101,7 +102,7 @@
"fieldtype": "Select",
"label": "Occupancy Status",
"no_copy": 1,
- "options": "\nVacant\nOccupied",
+ "options": "Vacant\nOccupied",
"read_only": 1
},
{
@@ -157,10 +158,16 @@
"options": "Healthcare Service Unit",
"print_hide": 1,
"report_hide": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "tree_details_section",
+ "fieldtype": "Section Break",
+ "label": "Tree Details"
}
],
"links": [],
- "modified": "2020-03-26 16:13:08.675952",
+ "modified": "2020-05-20 18:26:56.065543",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit",
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
index 13cc43d..9e0417a 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
@@ -22,10 +22,16 @@
super(HealthcareServiceUnit, self).on_update()
self.validate_one_root()
- def validate(self):
+ def after_insert(self):
if self.is_group:
self.allow_appointments = 0
self.overlap_appointments = 0
self.inpatient_occupancy = 0
- elif not self.allow_appointments:
- self.overlap_appointments = 0
+ elif self.service_unit_type:
+ service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type)
+ self.allow_appointments = service_unit_type.allow_appointments
+ self.overlap_appointments = service_unit_type.overlap_appointments
+ self.inpatient_occupancy = service_unit_type.inpatient_occupancy
+ if self.inpatient_occupancy:
+ self.occupancy_status = 'Vacant'
+ self.overlap_appointments = 0
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
index 5fa47d9..4b8503d 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
@@ -31,6 +31,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Service Unit Type",
+ "no_copy": 1,
"reqd": 1,
"unique": 1
},
@@ -40,8 +41,7 @@
"depends_on": "eval:doc.inpatient_occupancy != 1",
"fieldname": "allow_appointments",
"fieldtype": "Check",
- "label": "Allow Appointments",
- "no_copy": 1
+ "label": "Allow Appointments"
},
{
"bold": 1,
@@ -49,8 +49,7 @@
"depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1",
"fieldname": "overlap_appointments",
"fieldtype": "Check",
- "label": "Allow Overlap",
- "no_copy": 1
+ "label": "Allow Overlap"
},
{
"bold": 1,
@@ -58,8 +57,7 @@
"depends_on": "eval:doc.allow_appointments != 1",
"fieldname": "inpatient_occupancy",
"fieldtype": "Check",
- "label": "Inpatient Occupancy",
- "no_copy": 1
+ "label": "Inpatient Occupancy"
},
{
"bold": 1,
@@ -79,6 +77,7 @@
"fieldname": "item",
"fieldtype": "Link",
"label": "Item",
+ "no_copy": 1,
"options": "Item",
"read_only": 1
},
@@ -86,7 +85,8 @@
"fieldname": "item_code",
"fieldtype": "Data",
"label": "Item Code",
- "mandatory_depends_on": "eval: doc.is_billable == 1"
+ "mandatory_depends_on": "eval: doc.is_billable == 1",
+ "no_copy": 1
},
{
"fieldname": "item_group",
@@ -138,7 +138,7 @@
}
],
"links": [],
- "modified": "2020-01-30 16:06:00.624496",
+ "modified": "2020-05-20 15:31:09.627516",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit Type",
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
index 286ecc0..bb86eaa 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py
@@ -10,6 +10,22 @@
class HealthcareServiceUnitType(Document):
def validate(self):
+ if self.allow_appointments and self.inpatient_occupancy:
+ frappe.msgprint(
+ _('Healthcare Service Unit Type cannot have both {0} and {1}').format(
+ frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')),
+ raise_exception=1, title=_('Validation Error'), indicator='red'
+ )
+ elif not self.allow_appointments and not self.inpatient_occupancy:
+ frappe.msgprint(
+ _('Healthcare Service Unit Type must allow atleast one among {0} and {1}').format(
+ frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')),
+ raise_exception=1, title=_('Validation Error'), indicator='red'
+ )
+
+ if not self.allow_appointments:
+ self.overlap_appointments = 0
+
if self.is_billable:
if self.disabled:
frappe.db.set_value('Item', self.item, 'disabled', 1)
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
index 22fbf50..cf2276f 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js
@@ -39,3 +39,37 @@
};
});
};
+
+frappe.tour['Healthcare Settings'] = [
+ {
+ fieldname: 'link_customer_to_patient',
+ title: __('Link Customer to Patient'),
+ description: __('If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.')
+ },
+ {
+ fieldname: 'collect_registration_fee',
+ title: __('Collect Registration Fee'),
+ description: __('If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.')
+ },
+ {
+ fieldname: 'automate_appointment_invoicing',
+ title: __('Automate Appointment Invoicing'),
+ description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.')
+ },
+ {
+ fieldname: 'inpatient_visit_charge_item',
+ title: __('Healthcare Service Items'),
+ description: __('You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#2-default-healthcare-service-items' target='_blank'>here</a>" + __(' to know more')
+ },
+ {
+ fieldname: 'income_account',
+ title: __('Set up default Accounts for the Healthcare Facility'),
+ description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.')
+
+ },
+ {
+ fieldname: 'send_registration_msg',
+ title: __('Out Patient SMS alerts'),
+ description: __('If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ') + "<a href='https://docs.erpnext.com/docs/user/manual/en/healthcare/healthcare_settings#4-out-patient-sms-alerts' target='_blank'>here</a>" + __(' to know more')
+ }
+];
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
index 67c12f6..971e166 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js
@@ -2,22 +2,37 @@
// For license information, please see license.txt
frappe.ui.form.on('Inpatient Record', {
+ setup: function(frm) {
+ frm.get_field('drug_prescription').grid.editable_fields = [
+ {fieldname: 'drug_code', columns: 2},
+ {fieldname: 'drug_name', columns: 2},
+ {fieldname: 'dosage', columns: 2},
+ {fieldname: 'period', columns: 2}
+ ];
+ },
refresh: function(frm) {
- if(!frm.doc.__islocal && frm.doc.status == "Admission Scheduled"){
+ if (!frm.doc.__islocal && (frm.doc.status == 'Admission Scheduled' || frm.doc.status == 'Admitted')) {
+ frm.enable_save();
+ } else {
+ frm.disable_save();
+ }
+
+ if (!frm.doc.__islocal && frm.doc.status == 'Admission Scheduled') {
frm.add_custom_button(__('Admit'), function() {
admit_patient_dialog(frm);
} );
- frm.set_df_property("btn_transfer", "hidden", 1);
}
- if(!frm.doc.__islocal && frm.doc.status == "Discharge Scheduled"){
+
+ if (!frm.doc.__islocal && frm.doc.status == 'Discharge Scheduled') {
frm.add_custom_button(__('Discharge'), function() {
discharge_patient(frm);
} );
- frm.set_df_property("btn_transfer", "hidden", 0);
}
- if(!frm.doc.__islocal && (frm.doc.status == "Discharged" || frm.doc.status == "Discharge Scheduled")){
+ if (!frm.doc.__islocal && frm.doc.status != 'Admitted') {
frm.disable_save();
- frm.set_df_property("btn_transfer", "hidden", 1);
+ frm.set_df_property('btn_transfer', 'hidden', 1);
+ } else {
+ frm.set_df_property('btn_transfer', 'hidden', 0);
}
},
btn_transfer: function(frm) {
@@ -25,39 +40,47 @@
}
});
-var discharge_patient = function(frm) {
+let discharge_patient = function(frm) {
frappe.call({
doc: frm.doc,
- method: "discharge",
+ method: 'discharge',
callback: function(data) {
- if(!data.exc){
+ if (!data.exc) {
frm.reload_doc();
}
},
freeze: true,
- freeze_message: "Process Discharge"
+ freeze_message: __('Processing Inpatient Discharge')
});
};
-var admit_patient_dialog = function(frm){
- var dialog = new frappe.ui.Dialog({
+let admit_patient_dialog = function(frm) {
+ let dialog = new frappe.ui.Dialog({
title: 'Admit Patient',
width: 100,
fields: [
- {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"},
- {fieldtype: "Link", label: "Service Unit", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1},
- {fieldtype: "Datetime", label: "Admission Datetime", fieldname: "check_in", reqd: 1},
- {fieldtype: "Date", label: "Expected Discharge", fieldname: "expected_discharge"}
+ {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type',
+ options: 'Healthcare Service Unit Type', default: frm.doc.admission_service_unit_type
+ },
+ {fieldtype: 'Link', label: 'Service Unit', fieldname: 'service_unit',
+ options: 'Healthcare Service Unit', reqd: 1
+ },
+ {fieldtype: 'Datetime', label: 'Admission Datetime', fieldname: 'check_in',
+ reqd: 1, default: frappe.datetime.now_datetime()
+ },
+ {fieldtype: 'Date', label: 'Expected Discharge', fieldname: 'expected_discharge',
+ default: frm.doc.expected_length_of_stay ? frappe.datetime.add_days(frappe.datetime.now_datetime(), frm.doc.expected_length_of_stay) : ''
+ }
],
- primary_action_label: __("Admit"),
+ primary_action_label: __('Admit'),
primary_action : function(){
- var service_unit = dialog.get_value('service_unit');
- var check_in = dialog.get_value('check_in');
- var expected_discharge = null;
- if(dialog.get_value('expected_discharge')){
+ let service_unit = dialog.get_value('service_unit');
+ let check_in = dialog.get_value('check_in');
+ let expected_discharge = null;
+ if (dialog.get_value('expected_discharge')) {
expected_discharge = dialog.get_value('expected_discharge');
}
- if(!service_unit && !check_in){
+ if (!service_unit && !check_in) {
return;
}
frappe.call({
@@ -69,32 +92,33 @@
'expected_discharge': expected_discharge
},
callback: function(data) {
- if(!data.exc){
+ if (!data.exc) {
frm.reload_doc();
}
},
freeze: true,
- freeze_message: "Process Admission"
+ freeze_message: __('Processing Patient Admission')
});
frm.refresh_fields();
dialog.hide();
}
});
- dialog.fields_dict["service_unit_type"].get_query = function(){
+ dialog.fields_dict['service_unit_type'].get_query = function() {
return {
filters: {
- "inpatient_occupancy": 1,
- "allow_appointments": 0
+ 'inpatient_occupancy': 1,
+ 'allow_appointments': 0
}
};
};
- dialog.fields_dict["service_unit"].get_query = function(){
+ dialog.fields_dict['service_unit'].get_query = function() {
return {
filters: {
- "is_group": 0,
- "service_unit_type": dialog.get_value("service_unit_type"),
- "occupancy_status" : "Vacant"
+ 'is_group': 0,
+ 'company': frm.doc.company,
+ 'service_unit_type': dialog.get_value('service_unit_type'),
+ 'occupancy_status' : 'Vacant'
}
};
};
@@ -102,21 +126,21 @@
dialog.show();
};
-var transfer_patient_dialog = function(frm){
- var dialog = new frappe.ui.Dialog({
+let transfer_patient_dialog = function(frm) {
+ let dialog = new frappe.ui.Dialog({
title: 'Transfer Patient',
width: 100,
fields: [
- {fieldtype: "Link", label: "Leave From", fieldname: "leave_from", options: "Healthcare Service Unit", reqd: 1, read_only:1},
- {fieldtype: "Link", label: "Service Unit Type", fieldname: "service_unit_type", options: "Healthcare Service Unit Type"},
- {fieldtype: "Link", label: "Transfer To", fieldname: "service_unit", options: "Healthcare Service Unit", reqd: 1},
- {fieldtype: "Datetime", label: "Check In", fieldname: "check_in", reqd: 1}
+ {fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1},
+ {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'},
+ {fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1},
+ {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1}
],
- primary_action_label: __("Transfer"),
- primary_action : function(){
- var service_unit = null;
- var check_in = dialog.get_value('check_in');
- var leave_from = null;
+ primary_action_label: __('Transfer'),
+ primary_action : function() {
+ let service_unit = null;
+ let check_in = dialog.get_value('check_in');
+ let leave_from = null;
if(dialog.get_value('leave_from')){
leave_from = dialog.get_value('leave_from');
}
@@ -135,47 +159,47 @@
'leave_from': leave_from
},
callback: function(data) {
- if(!data.exc){
+ if (!data.exc) {
frm.reload_doc();
}
},
freeze: true,
- freeze_message: "Process Transfer"
+ freeze_message: __('Process Transfer')
});
frm.refresh_fields();
dialog.hide();
}
});
- dialog.fields_dict["leave_from"].get_query = function(){
+ dialog.fields_dict['leave_from'].get_query = function(){
return {
- query : "erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from",
+ query : 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from',
filters: {docname:frm.doc.name}
};
};
- dialog.fields_dict["service_unit_type"].get_query = function(){
+ dialog.fields_dict['service_unit_type'].get_query = function(){
return {
filters: {
- "inpatient_occupancy": 1,
- "allow_appointments": 0
+ 'inpatient_occupancy': 1,
+ 'allow_appointments': 0
}
};
};
- dialog.fields_dict["service_unit"].get_query = function(){
+ dialog.fields_dict['service_unit'].get_query = function(){
return {
filters: {
- "is_group": 0,
- "service_unit_type": dialog.get_value("service_unit_type"),
- "occupancy_status" : "Vacant"
+ 'is_group': 0,
+ 'service_unit_type': dialog.get_value('service_unit_type'),
+ 'occupancy_status' : 'Vacant'
}
};
};
dialog.show();
- var not_left_service_unit = null;
- for(let inpatient_occupancy in frm.doc.inpatient_occupancies){
- if(frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1){
+ let not_left_service_unit = null;
+ for (let inpatient_occupancy in frm.doc.inpatient_occupancies) {
+ if (frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1) {
not_left_service_unit = frm.doc.inpatient_occupancies[inpatient_occupancy].service_unit;
}
}
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index c1b516d..5ced845 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -22,17 +22,41 @@
"scheduled_date",
"admitted_datetime",
"expected_discharge",
- "discharge_date",
"references",
- "cb_admission",
- "admission_practitioner",
"admission_encounter",
- "cb_discharge",
- "discharge_practitioner",
- "discharge_encounter",
+ "admission_practitioner",
+ "medical_department",
+ "admission_ordered_for",
+ "expected_length_of_stay",
+ "admission_service_unit_type",
+ "cb_admission",
+ "primary_practitioner",
+ "secondary_practitioner",
+ "admission_instruction",
+ "encounter_details_section",
+ "chief_complaint",
+ "column_break_29",
+ "diagnosis",
+ "medication_section",
+ "drug_prescription",
+ "investigations_section",
+ "lab_test_prescription",
+ "procedures_section",
+ "procedure_prescription",
+ "rehabilitation_section",
+ "therapy_plan",
+ "therapies",
"sb_inpatient_occupancy",
"inpatient_occupancies",
"btn_transfer",
+ "sb_discharge_details",
+ "discharge_ordered_date",
+ "discharge_practitioner",
+ "discharge_encounter",
+ "discharge_date",
+ "cb_discharge",
+ "discharge_instructions",
+ "followup_date",
"sb_discharge_note",
"discharge_note"
],
@@ -54,7 +78,8 @@
"in_list_view": 1,
"label": "Patient",
"options": "Patient",
- "reqd": 1
+ "reqd": 1,
+ "set_only_once": 1
},
{
"fetch_from": "patient.patient_name",
@@ -109,10 +134,30 @@
"read_only": 1
},
{
+ "fieldname": "medical_department",
+ "fieldtype": "Link",
+ "label": "Medical Department",
+ "options": "Medical Department",
+ "set_only_once": 1
+ },
+ {
+ "fieldname": "primary_practitioner",
+ "fieldtype": "Link",
+ "label": "Healthcare Practitioner (Primary)",
+ "options": "Healthcare Practitioner"
+ },
+ {
+ "fieldname": "secondary_practitioner",
+ "fieldtype": "Link",
+ "label": "Healthcare Practitioner (Secondary)",
+ "options": "Healthcare Practitioner"
+ },
+ {
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
+ "default": "Admission Scheduled",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
@@ -126,37 +171,45 @@
"fieldtype": "Date",
"in_list_view": 1,
"label": "Admission Schedule Date",
+ "read_only": 1,
"reqd": 1
},
{
- "default": "Today",
+ "fieldname": "admission_ordered_for",
+ "fieldtype": "Date",
+ "label": "Admission Ordered For",
+ "read_only": 1
+ },
+ {
"fieldname": "admitted_datetime",
"fieldtype": "Datetime",
"in_list_view": 1,
- "label": "Admitted Datetime"
+ "label": "Admitted Datetime",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:(doc.expected_length_of_stay > 0)",
+ "fieldname": "expected_length_of_stay",
+ "fieldtype": "Int",
+ "label": "Expected Length of Stay",
+ "set_only_once": 1
},
{
"fieldname": "expected_discharge",
"fieldtype": "Date",
"in_list_view": 1,
- "label": "Expected Discharge"
- },
- {
- "fieldname": "discharge_date",
- "fieldtype": "Date",
- "in_list_view": 1,
- "label": "Discharge Date"
+ "label": "Expected Discharge",
+ "read_only": 1
},
{
"collapsible": 1,
"fieldname": "references",
"fieldtype": "Section Break",
- "label": "References"
+ "label": "Admission Order Details"
},
{
"fieldname": "cb_admission",
- "fieldtype": "Column Break",
- "label": "Admission"
+ "fieldtype": "Column Break"
},
{
"fieldname": "admission_practitioner",
@@ -173,9 +226,21 @@
"read_only": 1
},
{
+ "fieldname": "chief_complaint",
+ "fieldtype": "Table MultiSelect",
+ "label": "Chief Complaint",
+ "options": "Patient Encounter Symptom",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "admission_instruction",
+ "fieldtype": "Small Text",
+ "label": "Admission Instruction",
+ "set_only_once": 1
+ },
+ {
"fieldname": "cb_discharge",
- "fieldtype": "Column Break",
- "label": "Discharge"
+ "fieldtype": "Column Break"
},
{
"fieldname": "discharge_practitioner",
@@ -192,11 +257,58 @@
"read_only": 1
},
{
+ "collapsible": 1,
+ "fieldname": "medication_section",
+ "fieldtype": "Section Break",
+ "label": "Medications",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "drug_prescription",
+ "fieldtype": "Table",
+ "options": "Drug Prescription",
+ "permlevel": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "investigations_section",
+ "fieldtype": "Section Break",
+ "label": "Investigations",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "lab_test_prescription",
+ "fieldtype": "Table",
+ "options": "Lab Prescription",
+ "permlevel": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "procedures_section",
+ "fieldtype": "Section Break",
+ "label": "Procedures",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "procedure_prescription",
+ "fieldtype": "Table",
+ "options": "Procedure Prescription",
+ "permlevel": 1
+ },
+ {
+ "depends_on": "eval:(doc.status != \"Admission Scheduled\")",
"fieldname": "sb_inpatient_occupancy",
"fieldtype": "Section Break",
"label": "Inpatient Occupancy"
},
{
+ "fieldname": "admission_service_unit_type",
+ "fieldtype": "Link",
+ "label": "Admission Service Unit Type",
+ "options": "Healthcare Service Unit Type",
+ "read_only": 1
+ },
+ {
"fieldname": "inpatient_occupancies",
"fieldtype": "Table",
"options": "Inpatient Occupancy",
@@ -208,14 +320,15 @@
"label": "Transfer"
},
{
- "depends_on": "eval:doc.status != \"Admission Scheduled\"",
+ "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")",
"fieldname": "sb_discharge_note",
"fieldtype": "Section Break",
- "label": "Discharge Note"
+ "label": "Discharge Notes"
},
{
"fieldname": "discharge_note",
- "fieldtype": "Text Editor"
+ "fieldtype": "Text Editor",
+ "permlevel": 1
},
{
"fetch_from": "admission_encounter.company",
@@ -224,10 +337,81 @@
"in_standard_filter": 1,
"label": "Company",
"options": "Company"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval:(doc.status == \"Admitted\")",
+ "fieldname": "encounter_details_section",
+ "fieldtype": "Section Break",
+ "label": "Encounter Impression",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "column_break_29",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "diagnosis",
+ "fieldtype": "Table MultiSelect",
+ "label": "Diagnosis",
+ "options": "Patient Encounter Diagnosis",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "followup_date",
+ "fieldtype": "Date",
+ "label": "Follow Up Date"
+ },
+ {
+ "collapsible": 1,
+ "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")",
+ "fieldname": "sb_discharge_details",
+ "fieldtype": "Section Break",
+ "label": "Discharge Detials"
+ },
+ {
+ "fieldname": "discharge_instructions",
+ "fieldtype": "Small Text",
+ "label": "Discharge Instructions"
+ },
+ {
+ "fieldname": "discharge_ordered_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "Discharge Ordered Date",
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "rehabilitation_section",
+ "fieldtype": "Section Break",
+ "label": "Rehabilitation",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "therapy_plan",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "label": "Therapy Plan",
+ "options": "Therapy Plan",
+ "permlevel": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "therapies",
+ "fieldtype": "Table",
+ "options": "Therapy Plan Detail",
+ "permlevel": 1
+ },
+ {
+ "fieldname": "discharge_date",
+ "fieldtype": "Date",
+ "label": "Discharge Date",
+ "read_only": 1
}
],
"links": [],
- "modified": "2020-04-07 13:13:39.351977",
+ "modified": "2020-05-21 02:26:22.144575",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Inpatient Record",
@@ -244,6 +428,42 @@
"role": "Healthcare Administrator",
"share": 1,
"write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Physician",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Nursing User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "permlevel": 1,
+ "read": 1,
+ "role": "Physician",
+ "write": 1
+ },
+ {
+ "permlevel": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Nursing User"
}
],
"restrict_to_domain": "Healthcare",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index 835b38b..cf63b65 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -3,7 +3,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, json
from frappe import _
from frappe.utils import today, now_datetime, getdate
from frappe.model.document import Document
@@ -11,8 +11,12 @@
class InpatientRecord(Document):
def after_insert(self):
- frappe.db.set_value("Patient", self.patient, "inpatient_status", "Admission Scheduled")
- frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name)
+ frappe.db.set_value('Patient', self.patient, 'inpatient_record', self.name)
+ frappe.db.set_value('Patient', self.patient, 'inpatient_status', self.status)
+
+ if self.admission_encounter: # Update encounter
+ frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_record', self.name)
+ frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_status', self.status)
def validate(self):
self.validate_dates()
@@ -22,13 +26,10 @@
frappe.db.set_value("Patient", self.patient, "inpatient_record", None)
def validate_dates(self):
- if (getdate(self.scheduled_date) < getdate(today())) or \
- (getdate(self.admitted_datetime) < getdate(today())):
- frappe.throw(_("Scheduled and Admitted dates can not be less than today"))
if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \
- (getdate(self.discharge_date) < getdate(self.scheduled_date)):
- frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date"))
-
+ (getdate(self.discharge_ordered_date) < getdate(self.scheduled_date)):
+ frappe.throw(_('Expected and Discharge dates cannot be less than Admission Schedule date'))
+
def validate_already_scheduled_or_admitted(self):
query = """
select name, status
@@ -59,37 +60,76 @@
if service_unit:
transfer_patient(self, service_unit, check_in)
+
@frappe.whitelist()
-def schedule_inpatient(patient, encounter_id, practitioner):
- patient_obj = frappe.get_doc('Patient', patient)
+def schedule_inpatient(args):
+ admission_order = json.loads(args) # admission order via Encounter
+ if not admission_order or not admission_order['patient'] or not admission_order['admission_encounter']:
+ frappe.throw(_('Missing required details, did not create Inpatient Record'))
+
inpatient_record = frappe.new_doc('Inpatient Record')
- inpatient_record.patient = patient
- inpatient_record.patient_name = patient_obj.patient_name
- inpatient_record.gender = patient_obj.sex
- inpatient_record.blood_group = patient_obj.blood_group
- inpatient_record.dob = patient_obj.dob
- inpatient_record.mobile = patient_obj.mobile
- inpatient_record.email = patient_obj.email
- inpatient_record.phone = patient_obj.phone
- inpatient_record.status = "Admission Scheduled"
+
+ # Admission order details
+ set_details_from_ip_order(inpatient_record, admission_order)
+
+ # Patient details
+ patient = frappe.get_doc('Patient', admission_order['patient'])
+ inpatient_record.patient = patient.name
+ inpatient_record.patient_name = patient.patient_name
+ inpatient_record.gender = patient.sex
+ inpatient_record.blood_group = patient.blood_group
+ inpatient_record.dob = patient.dob
+ inpatient_record.mobile = patient.mobile
+ inpatient_record.email = patient.email
+ inpatient_record.phone = patient.phone
inpatient_record.scheduled_date = today()
- inpatient_record.admission_practitioner = practitioner
- inpatient_record.admission_encounter = encounter_id
+
+ # Set encounter detials
+ encounter = frappe.get_doc('Patient Encounter', admission_order['admission_encounter'])
+ if encounter and encounter.symptoms: # Symptoms
+ set_ip_child_records(inpatient_record, 'chief_complaint', encounter.symptoms)
+
+ if encounter and encounter.diagnosis: # Diagnosis
+ set_ip_child_records(inpatient_record, 'diagnosis', encounter.diagnosis)
+
+ if encounter and encounter.drug_prescription: # Medication
+ set_ip_child_records(inpatient_record, 'drug_prescription', encounter.drug_prescription)
+
+ if encounter and encounter.lab_test_prescription: # Lab Tests
+ set_ip_child_records(inpatient_record, 'lab_test_prescription', encounter.lab_test_prescription)
+
+ if encounter and encounter.procedure_prescription: # Procedure Prescription
+ set_ip_child_records(inpatient_record, 'procedure_prescription', encounter.procedure_prescription)
+
+ if encounter and encounter.therapies: # Therapies
+ inpatient_record.therapy_plan = encounter.therapy_plan
+ set_ip_child_records(inpatient_record, 'therapies', encounter.therapies)
+
+ inpatient_record.status = 'Admission Scheduled'
inpatient_record.save(ignore_permissions = True)
@frappe.whitelist()
-def schedule_discharge(patient, encounter_id=None, practitioner=None):
- inpatient_record_id = frappe.db.get_value('Patient', patient, 'inpatient_record')
+def schedule_discharge(args):
+ discharge_order = json.loads(args)
+ inpatient_record_id = frappe.db.get_value('Patient', discharge_order['patient'], 'inpatient_record')
if inpatient_record_id:
- inpatient_record = frappe.get_doc("Inpatient Record", inpatient_record_id)
- inpatient_record.discharge_practitioner = practitioner
- inpatient_record.discharge_encounter = encounter_id
- inpatient_record.status = "Discharge Scheduled"
-
+ inpatient_record = frappe.get_doc('Inpatient Record', inpatient_record_id)
check_out_inpatient(inpatient_record)
-
+ set_details_from_ip_order(inpatient_record, discharge_order)
+ inpatient_record.status = 'Discharge Scheduled'
inpatient_record.save(ignore_permissions = True)
- frappe.db.set_value("Patient", patient, "inpatient_status", "Discharge Scheduled")
+ frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
+ frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
+
+def set_details_from_ip_order(inpatient_record, ip_order):
+ for key in ip_order:
+ inpatient_record.set(key, ip_order[key])
+
+def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
+ for item in encounter_child:
+ table = inpatient_record.append(inpatient_record_child)
+ for df in table.meta.get('fields'):
+ table.set(df.fieldname, item.get(df.fieldname))
def check_out_inpatient(inpatient_record):
if inpatient_record.inpatient_occupancies:
@@ -128,7 +168,7 @@
if pending_invoices:
frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
- .join(pending_invoices)))
+ .join(pending_invoices)), title=_('Unbilled Invoices'))
def get_pending_doc(doc, doc_name_list, pending_invoices):
if doc_name_list:
@@ -144,19 +184,19 @@
return pending_invoices
def get_inpatient_docs_not_invoiced(doc, inpatient_record):
- return frappe.db.get_list(doc, filters = {"patient": inpatient_record.patient,
- "inpatient_record": inpatient_record.name, "invoiced": 0})
+ return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
+ 'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
inpatient_record.admitted_datetime = check_in
- inpatient_record.status = "Admitted"
+ inpatient_record.status = 'Admitted'
inpatient_record.expected_discharge = expected_discharge
inpatient_record.set('inpatient_occupancies', [])
transfer_patient(inpatient_record, service_unit, check_in)
- frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_status", "Admitted")
- frappe.db.set_value("Patient", inpatient_record.patient, "inpatient_record", inpatient_record.name)
+ frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
+ frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
def transfer_patient(inpatient_record, service_unit, check_in):
item_line = inpatient_record.append('inpatient_occupancies', {})
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index 4c2d3f6..2bef5fb 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -15,6 +15,7 @@
patient = create_patient()
# Schedule Admission
ip_record = create_inpatient(patient)
+ ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record"))
self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status"))
@@ -26,7 +27,7 @@
self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
# Discharge
- schedule_discharge(patient=patient)
+ schedule_discharge(frappe.as_json({'patient': patient}))
self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
@@ -44,8 +45,10 @@
patient = create_patient()
ip_record = create_inpatient(patient)
+ ip_record.expected_length_of_stay = 0
ip_record.save(ignore_permissions = True)
ip_record_new = create_inpatient(patient)
+ ip_record_new.expected_length_of_stay = 0
self.assertRaises(frappe.ValidationError, ip_record_new.save)
service_unit = get_healthcare_service_unit()
diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
index e2b47b4..3521561 100644
--- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
+++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py
@@ -115,7 +115,7 @@
"price_list": price_list_name,
"item_code": item,
"price_list_rate": item_price
- }).insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_test_code_from_template(lab_test_code, doc):
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index b8a400c..ac35acc 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -39,9 +39,9 @@
"section_break_16",
"mode_of_payment",
"billing_item",
+ "invoiced",
"column_break_2",
"paid_amount",
- "invoiced",
"ref_sales_invoice",
"section_break_3",
"referring_practitioner",
@@ -348,7 +348,7 @@
}
],
"links": [],
- "modified": "2020-04-27 21:36:06.404062",
+ "modified": "2020-05-21 03:04:21.400893",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Appointment",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 9eb6e77..512fb48 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -447,7 +447,7 @@
"""
SELECT
t.therapy_type, t.name, t.parent, e.practitioner,
- e.encounter_date, e.therapy_plan, e.visit_department
+ e.encounter_date, e.therapy_plan, e.medical_department
FROM
`tabPatient Encounter` e, `tabTherapy Plan Detail` t
WHERE
diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
index 3952a81..15c9434 100644
--- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
+++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json
@@ -11,6 +11,7 @@
"patient",
"assessment_template",
"column_break_4",
+ "company",
"healthcare_practitioner",
"assessment_datetime",
"assessment_description",
@@ -127,11 +128,18 @@
"fieldname": "assessment_description",
"fieldtype": "Small Text",
"label": "Assessment Description"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-21 13:23:09.815007",
+ "modified": "2020-05-25 14:38:38.302399",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment",
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
index 2410f8e..edcee99 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
@@ -180,35 +180,114 @@
}
});
-let schedule_inpatient = function(frm) {
- frappe.call({
- method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient',
- args: {patient: frm.doc.patient, encounter_id: frm.doc.name, practitioner: frm.doc.practitioner},
- callback: function(data) {
- if (!data.exc) {
- frm.reload_doc();
+var schedule_inpatient = function(frm) {
+ var dialog = new frappe.ui.Dialog({
+ title: 'Patient Admission',
+ fields: [
+ {fieldtype: 'Link', label: 'Medical Department', fieldname: 'medical_department', options: 'Medical Department', reqd: 1},
+ {fieldtype: 'Link', label: 'Healthcare Practitioner (Primary)', fieldname: 'primary_practitioner', options: 'Healthcare Practitioner', reqd: 1},
+ {fieldtype: 'Link', label: 'Healthcare Practitioner (Secondary)', fieldname: 'secondary_practitioner', options: 'Healthcare Practitioner'},
+ {fieldtype: 'Column Break'},
+ {fieldtype: 'Date', label: 'Admission Ordered For', fieldname: 'admission_ordered_for', default: 'Today'},
+ {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'},
+ {fieldtype: 'Int', label: 'Expected Length of Stay', fieldname: 'expected_length_of_stay'},
+ {fieldtype: 'Section Break'},
+ {fieldtype: 'Long Text', label: 'Admission Instructions', fieldname: 'admission_instruction'}
+ ],
+ primary_action_label: __('Order Admission'),
+ primary_action : function() {
+ var args = {
+ patient: frm.doc.patient,
+ admission_encounter: frm.doc.name,
+ referring_practitioner: frm.doc.practitioner,
+ company: frm.doc.company,
+ medical_department: dialog.get_value('medical_department'),
+ primary_practitioner: dialog.get_value('primary_practitioner'),
+ secondary_practitioner: dialog.get_value('secondary_practitioner'),
+ admission_ordered_for: dialog.get_value('admission_ordered_for'),
+ admission_service_unit_type: dialog.get_value('service_unit_type'),
+ expected_length_of_stay: dialog.get_value('expected_length_of_stay'),
+ admission_instruction: dialog.get_value('admission_instruction')
}
- },
- freeze: true,
- freeze_message: __('Process Inpatient Scheduling')
+ frappe.call({
+ method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient',
+ args: {
+ args: args
+ },
+ callback: function(data) {
+ if (!data.exc) {
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: 'Scheduling Patient Admission'
+ });
+ frm.refresh_fields();
+ dialog.hide();
+ }
});
+
+ dialog.set_values({
+ 'medical_department': frm.doc.medical_department,
+ 'primary_practitioner': frm.doc.practitioner,
+ });
+
+ dialog.fields_dict['service_unit_type'].get_query = function() {
+ return {
+ filters: {
+ 'inpatient_occupancy': 1,
+ 'allow_appointments': 0
+ }
+ };
+ };
+
+ dialog.show();
+ dialog.$wrapper.find('.modal-dialog').css('width', '800px');
};
-let schedule_discharge = function(frm) {
- frappe.call({
- method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge',
- args: {patient: frm.doc.patient, encounter_id: frm.doc.name, practitioner: frm.doc.practitioner},
- callback: function(data) {
- if (!data.exc) {
- frm.reload_doc();
+var schedule_discharge = function(frm) {
+ var dialog = new frappe.ui.Dialog ({
+ title: 'Inpatient Discharge',
+ fields: [
+ {fieldtype: 'Date', label: 'Discharge Ordered Date', fieldname: 'discharge_ordered_date', default: 'Today', read_only: 1},
+ {fieldtype: 'Date', label: 'Followup Date', fieldname: 'followup_date'},
+ {fieldtype: 'Column Break'},
+ {fieldtype: 'Small Text', label: 'Discharge Instructions', fieldname: 'discharge_instructions'},
+ {fieldtype: 'Section Break', label:'Discharge Summary'},
+ {fieldtype: 'Long Text', label: 'Discharge Note', fieldname: 'discharge_note'}
+ ],
+ primary_action_label: __('Order Discharge'),
+ primary_action : function() {
+ var args = {
+ patient: frm.doc.patient,
+ discharge_encounter: frm.doc.name,
+ discharge_practitioner: frm.doc.practitioner,
+ discharge_ordered_date: dialog.get_value('discharge_ordered_date'),
+ followup_date: dialog.get_value('followup_date'),
+ discharge_instructions: dialog.get_value('discharge_instructions'),
+ discharge_note: dialog.get_value('discharge_note')
}
- },
- freeze: true,
- freeze_message: 'Process Discharge'
+ frappe.call ({
+ method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge',
+ args: {args},
+ callback: function(data) {
+ if(!data.exc){
+ frm.reload_doc();
+ }
+ },
+ freeze: true,
+ freeze_message: 'Scheduling Inpatient Discharge'
+ });
+ frm.refresh_fields();
+ dialog.hide();
+ }
});
+
+ dialog.show();
+ dialog.$wrapper.find('.modal-dialog').css('width', '800px');
};
-let create_medical_record = function (frm) {
+let create_medical_record = function(frm) {
if (!frm.doc.patient) {
frappe.throw(__('Please select patient'));
}
@@ -221,7 +300,7 @@
frappe.new_doc('Patient Medical Record');
};
-let create_vital_signs = function (frm) {
+let create_vital_signs = function(frm) {
if (!frm.doc.patient) {
frappe.throw(__('Please select patient'));
}
@@ -233,7 +312,7 @@
frappe.new_doc('Vital Signs');
};
-let create_procedure = function (frm) {
+let create_procedure = function(frm) {
if (!frm.doc.patient) {
frappe.throw(__('Please select patient'));
}
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index 05eec87..15675f4 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -52,6 +52,7 @@
],
"fields": [
{
+ "allow_on_submit": 1,
"fieldname": "inpatient_record",
"fieldtype": "Link",
"label": "Inpatient Record",
@@ -296,6 +297,7 @@
"read_only": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "inpatient_status",
"fieldtype": "Data",
"label": "Inpatient Status",
@@ -326,7 +328,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-27 21:58:29.789797",
+ "modified": "2020-05-16 21:00:08.644531",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Encounter",
diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
index c352287..016cfbc 100644
--- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json
+++ b/erpnext/healthcare/doctype/sample_collection/sample_collection.json
@@ -85,11 +85,9 @@
{
"fieldname": "company",
"fieldtype": "Link",
- "hidden": 1,
+ "in_standard_filter": 1,
"label": "Company",
- "options": "Company",
- "print_hide": 1,
- "report_hide": 1
+ "options": "Company"
},
{
"fieldname": "section_break_6",
@@ -167,7 +165,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-04 19:17:02.707203",
+ "modified": "2020-05-25 14:36:46.990469",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Sample Collection",
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
index ca78b66..9edfeb2 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
@@ -10,6 +10,7 @@
"patient",
"patient_name",
"column_break_4",
+ "company",
"status",
"start_date",
"section_break_3",
@@ -98,10 +99,17 @@
"label": "Status",
"options": "Not Started\nIn Progress\nCompleted\nCancelled",
"read_only": 1
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company"
}
],
"links": [],
- "modified": "2020-04-21 13:13:43.956014",
+ "modified": "2020-05-25 14:38:53.649315",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Plan",
diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
new file mode 100644
index 0000000..3e50726
--- /dev/null
+++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
@@ -0,0 +1,42 @@
+{
+ "allow_roles": [
+ {
+ "role": "Healthcare Administrator"
+ }
+ ],
+ "creation": "2020-05-19 10:32:43.025852",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-26 23:16:37.603361",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Healthcare",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Create Patient"
+ },
+ {
+ "step": "Create Practitioner Schedule"
+ },
+ {
+ "step": "Introduction to Healthcare Practitioner"
+ },
+ {
+ "step": "Create Healthcare Practitioner"
+ },
+ {
+ "step": "Explore Healthcare Settings"
+ },
+ {
+ "step": "Explore Clinical Procedure Templates"
+ }
+ ],
+ "subtitle": "Patients, Practitioner Schedules, Settings and more.",
+ "success_message": "Yayy! The Healthcare Module is all set up!",
+ "title": "Let's Setup the Healthcare Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json
new file mode 100644
index 0000000..c45a347
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:39:55.728058",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-26 23:16:31.965521",
+ "modified_by": "Administrator",
+ "name": "Create Healthcare Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 1,
+ "title": "Create Healthcare Practitioner",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
new file mode 100644
index 0000000..77bc5bd
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:32:27.648902",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:26:24.023418",
+ "modified_by": "Administrator",
+ "name": "Create Patient",
+ "owner": "Administrator",
+ "reference_document": "Patient",
+ "show_full_form": 1,
+ "title": "Create Patient",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
new file mode 100644
index 0000000..65980ef
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 10:41:19.065753",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:27:09.437825",
+ "modified_by": "Administrator",
+ "name": "Create Practitioner Schedule",
+ "owner": "Administrator",
+ "reference_document": "Practitioner Schedule",
+ "show_full_form": 1,
+ "title": "Create Practitioner Schedule",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
new file mode 100644
index 0000000..697b761
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:40:51.963741",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-26 23:10:24.504030",
+ "modified_by": "Administrator",
+ "name": "Explore Clinical Procedure Templates",
+ "owner": "Administrator",
+ "reference_document": "Clinical Procedure Template",
+ "show_full_form": 0,
+ "title": "Explore Clinical Procedure Templates",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
new file mode 100644
index 0000000..b2d5aef
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:14:33.044989",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-26 23:10:24.507648",
+ "modified_by": "Administrator",
+ "name": "Explore Healthcare Settings",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Settings",
+ "show_full_form": 0,
+ "title": "Explore Healthcare Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json
new file mode 100644
index 0000000..fa4c903
--- /dev/null
+++ b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json
@@ -0,0 +1,20 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 10:43:56.231679",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "field": "schedule",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-26 22:07:07.482530",
+ "modified_by": "Administrator",
+ "name": "Introduction to Healthcare Practitioner",
+ "owner": "Administrator",
+ "reference_document": "Healthcare Practitioner",
+ "show_full_form": 0,
+ "title": "Introduction to Healthcare Practitioner",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py
index 2087f49..0684080 100644
--- a/erpnext/healthcare/setup.py
+++ b/erpnext/healthcare/setup.py
@@ -195,10 +195,21 @@
def add_healthcare_service_unit_tree_root():
record = [
- {
- "doctype": "Healthcare Service Unit",
- "healthcare_service_unit_name": "All Healthcare Service Units",
- "is_group": 1
- }
+ {
+ "doctype": "Healthcare Service Unit",
+ "healthcare_service_unit_name": "All Healthcare Service Units",
+ "is_group": 1,
+ "company": get_company()
+ }
]
insert_record(record)
+
+def get_company():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company = frappe.get_list("Company", limit=1)
+ if company:
+ return company[0].name
+ return None
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index f092578..9abaa07 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -512,10 +512,10 @@
def get_patient_vitals(patient, from_date=None, to_date=None):
if not patient: return
- vitals = frappe.db.get_all('Vital Signs', {
+ vitals = frappe.db.get_all('Vital Signs', filters={
'docstatus': 1,
'patient': patient
- }, order_by='signs_date, signs_time')
+ }, order_by='signs_date, signs_time', fields=['*'])
if len(vitals):
return vitals
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index ab161aa..2a69589 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -41,7 +41,7 @@
notification_config = "erpnext.startup.notifications.get_notification_config"
get_help_messages = "erpnext.utilities.activation.get_help_messages"
leaderboards = "erpnext.startup.leaderboard.get_leaderboards"
-
+filters_config = "erpnext.startup.filters.get_filters_config"
on_session_creation = [
"erpnext.portal.utils.create_customer_or_supplier",
@@ -238,6 +238,9 @@
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"on_trash": "erpnext.regional.check_deletion_permission"
},
+ "Purchase Invoice": {
+ "on_submit": "erpnext.regional.india.utils.make_reverse_charge_entries"
+ },
"Payment Entry": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status"],
"on_trash": "erpnext.regional.check_deletion_permission"
@@ -320,8 +323,7 @@
"erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual.process_loan_interest_accrual_for_term_loans"
],
"monthly_long": [
- "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",
- "erpnext.accounts.deferred_revenue.convert_deferred_expense_to_expense",
+ "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"
]
diff --git a/erpnext/hr/dashboard_fixtures.py b/erpnext/hr/dashboard_fixtures.py
new file mode 100644
index 0000000..6e042ac
--- /dev/null
+++ b/erpnext/hr/dashboard_fixtures.py
@@ -0,0 +1,228 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import erpnext
+import json
+from frappe import _
+
+def get_data():
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards(),
+ })
+
+def get_dashboards():
+ dashboards = []
+ dashboards.append(get_human_resource_dashboard())
+ return dashboards
+
+def get_human_resource_dashboard():
+ return {
+ "name": "Human Resource",
+ "dashboard_name": "Human Resource",
+ "is_default": 1,
+ "charts": [
+ { "chart": "Outgoing Salary", "width": "Full"},
+ { "chart": "Gender Diversity Ratio", "width": "Half"},
+ { "chart": "Job Application Status", "width": "Half"},
+ { "chart": 'Designation Wise Employee Count', "width": "Half"},
+ { "chart": 'Department Wise Employee Count', "width": "Half"},
+ { "chart": 'Designation Wise Openings', "width": "Half"},
+ { "chart": 'Department Wise Openings', "width": "Half"},
+ { "chart": "Attendance Count", "width": "Full"}
+ ],
+ "cards": [
+ {"card": "Total Employees"},
+ {"card": "New Joinees (Last year)"},
+ {'card': "Employees Left (Last year)"},
+ {'card': "Total Job Openings (Last month)"},
+ {'card': "Total Applicants (Last month)"},
+ {'card': "Shortlisted Candidates (Last month)"},
+ {'card': "Rejected Candidates (Last month)"},
+ {'card': "Total Job Offered (Last month)"},
+ ]
+ }
+
+def get_recruitment_dashboard():
+ pass
+
+
+def get_charts():
+ company = erpnext.get_default_company()
+ date = frappe.utils.get_datetime()
+
+ month_map = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov","Dec"]
+
+
+ if not company:
+ company = frappe.db.get_value("Company", {"is_group": 0}, "name")
+
+ dashboard_charts = [
+ get_dashboards_chart_doc('Gender Diversity Ratio', "Group By", "Pie",
+ document_type = "Employee", group_by_type="Count", group_by_based_on="gender",
+ filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
+ ]
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Job Application Status', "Group By", "Pie",
+ document_type = "Job Applicant", group_by_type="Count", group_by_based_on="status",
+ filters_json = json.dumps([["Job Applicant", "creation", "Previous", "1 month"]]))
+ )
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Outgoing Salary', "Sum", "Line",
+ document_type = "Salary Slip", based_on="end_date",
+ value_based_on = "rounded_total", time_interval = "Monthly", timeseries = 1,
+ filters_json = json.dumps([["Salary Slip", "docstatus", "=", 1]]))
+ )
+
+ custom_options = '''{
+ "type": "line",
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "tooltipOptions": {}
+ }'''
+
+ filters_json = json.dumps({
+ "month": month_map[date.month - 1],
+ "year": str(date.year),
+ "company":company
+ })
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Attendance Count', "Report", "Line",
+ report_name = "Monthly Attendance Sheet", is_custom =1, group_by_type="Count",
+ filters_json = filters_json, custom_options=custom_options)
+ )
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Department Wise Employee Count', "Group By", "Donut",
+ document_type = "Employee", group_by_type="Count", group_by_based_on="department",
+ filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
+ )
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Designation Wise Employee Count', "Group By", "Donut",
+ document_type = "Employee", group_by_type="Count", group_by_based_on="designation",
+ filters_json = json.dumps([["Employee", "status", "=", "Active"]]))
+ )
+
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Designation Wise Openings', "Group By", "Bar",
+ document_type = "Job Opening", group_by_type="Sum", group_by_based_on="designation",
+ time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies")
+ )
+ dashboard_charts.append(
+ get_dashboards_chart_doc('Department Wise Openings', "Group By", "Bar",
+ document_type = "Job Opening", group_by_type="Sum", group_by_based_on="department",
+ time_interval = "Monthly", aggregate_function_based_on = "planned_vacancies")
+ )
+ return dashboard_charts
+
+
+def get_number_cards():
+ number_cards = []
+
+ number_cards = [
+ get_number_cards_doc("Employee", "Total Employees", filters_json = json.dumps([
+ ["Employee","status","=","Active"]
+ ])
+ )
+ ]
+
+ number_cards.append(
+ get_number_cards_doc("Employee", "New Joinees (Last year)", filters_json = json.dumps([
+ ["Employee","date_of_joining","Previous","1 year"],
+ ["Employee","status","=","Active"]
+ ])
+ )
+ )
+
+ number_cards.append(
+ get_number_cards_doc("Employee", "Employees Left (Last year)", filters_json = json.dumps([
+ ["Employee", "relieving_date", "Previous", "1 year"],
+ ["Employee", "status", "=", "Left"]
+ ])
+ )
+ )
+
+ number_cards.append(
+ get_number_cards_doc("Job Applicant", "Total Applicants (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "creation", "Previous", "1 month"]
+ ])
+ )
+ )
+
+ number_cards.append(
+ get_number_cards_doc("Job Opening", "Total Job Openings (Last month)", func = "Sum",
+ aggregate_function_based_on = "planned_vacancies",
+ filters_json = json.dumps([["Job Opening", "creation", "Previous", "1 month"]])
+ )
+ )
+ number_cards.append(
+ get_number_cards_doc("Job Applicant", "Shortlisted Candidates (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "status", "=", "Accepted"],
+ ["Job Applicant", "creation", "Previous", "1 month"]
+ ])
+ )
+ )
+ number_cards.append(
+ get_number_cards_doc("Job Applicant", "Rejected Candidates (Last month)", filters_json = json.dumps([
+ ["Job Applicant", "status", "=", "Rejected"],
+ ["Job Applicant", "creation", "Previous", "1 month"]
+ ])
+ )
+ )
+ number_cards.append(
+ get_number_cards_doc("Job Offer", "Total Job Offered (Last month)",
+ filters_json = json.dumps([["Job Offer", "creation", "Previous", "1 month"]])
+ )
+ )
+
+ return number_cards
+
+
+def get_number_cards_doc(document_type, label, **args):
+ args = frappe._dict(args)
+
+ return {
+ "doctype": "Number Card",
+ "document_type": document_type,
+ "function": args.func or "Count",
+ "is_public": args.is_public or 1,
+ "label": _(label),
+ "name": args.name or label,
+ "show_percentage_stats": args.show_percentage_stats or 1,
+ "stats_time_interval": args.stats_time_interval or 'Monthly',
+ "filters_json": args.filters_json or '[]',
+ "aggregate_function_based_on": args.aggregate_function_based_on or None
+ }
+
+def get_dashboards_chart_doc(name, chart_type, graph_type, **args):
+ args = frappe._dict(args)
+
+ return {
+ "name": name,
+ "chart_name": _(args.chart_name or name),
+ "chart_type": chart_type,
+ "document_type": args.document_type or None,
+ "report_name": args.report_name or None,
+ "is_custom": args.is_custom or 0,
+ "group_by_type": args.group_by_type or None,
+ "group_by_based_on": args.group_by_based_on or None,
+ "based_on": args.based_on or None,
+ "value_based_on": args.value_based_on or None,
+ "number_of_groups": args.number_of_groups or 0,
+ "is_public": args.is_public or 1,
+ "timespan": args.timespan or "Last Year",
+ "time_interval": args.time_interval or "Yearly",
+ "timeseries": args.timeseries or 0,
+ "filters_json": args.filters_json or '[]',
+ "type": graph_type,
+ "custom_options": args.custom_options or '',
+ "doctype": "Dashboard Chart",
+ "aggregate_function_based_on": args.aggregate_function_based_on or None
+ }
\ No newline at end of file
diff --git a/erpnext/hr/desk_page/hr/hr.json b/erpnext/hr/desk_page/hr/hr.json
index 22aa170..12548d4 100644
--- a/erpnext/hr/desk_page/hr/hr.json
+++ b/erpnext/hr/desk_page/hr/hr.json
@@ -18,7 +18,7 @@
{
"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 \"dependencies\": [\n \"Employee\"\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]"
+ "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,
@@ -77,26 +77,33 @@
}
],
"category": "Modules",
- "charts": [],
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "HR",
- "modified": "2020-04-29 20:29:22.114309",
+ "modified": "2020-06-16 19:20:50.976045",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
+ "onboarding": "Human Resource",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#cef6d1",
"format": "{} Active",
"label": "Employee",
"link_to": "Employee",
@@ -104,17 +111,17 @@
"type": "DocType"
},
{
- "format": "{} Unpaid",
- "label": "Expense Claim",
- "link_to": "Expense Claim",
- "stats_filter": "{\"approval_status\":\"Draft\"}",
+ "color": "#ffe8cd",
+ "format": "{} Open",
+ "label": "Leave Application",
+ "link_to": "Leave Application",
+ "stats_filter": "{\"status\":\"Open\"}",
"type": "DocType"
},
{
- "format": "{} Open",
- "label": "Job Applicant",
- "link_to": "Job Applicant",
- "stats_filter": "{\n \"status\": \"Open\"\n}",
+ "label": "Attendance",
+ "link_to": "Attendance",
+ "stats_filter": "",
"type": "DocType"
},
{
@@ -123,14 +130,16 @@
"type": "DocType"
},
{
- "label": "Leave Application",
- "link_to": "Leave Application",
- "type": "DocType"
+ "label": "Monthly Attendance Sheet",
+ "link_to": "Monthly Attendance Sheet",
+ "type": "Report"
},
{
- "label": "Salary Register",
- "link_to": "Salary Register",
- "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/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py
index bab6fb5..e369ba7 100644
--- a/erpnext/hr/doctype/additional_salary/additional_salary.py
+++ b/erpnext/hr/doctype/additional_salary/additional_salary.py
@@ -37,7 +37,7 @@
frappe.throw(_("Payroll date can not be less than employee's joining date."))
elif getdate(self.from_date) < getdate(date_of_joining):
frappe.throw(_("From date can not be less than employee's joining date."))
- elif getdate(self.to_date) > getdate(relieving_date):
+ elif relieving_date and getdate(self.to_date) > getdate(relieving_date):
frappe.throw(_("To date can not be greater than employee's relieving date."))
def get_amount(self, sal_start_date, sal_end_date):
diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template.py b/erpnext/hr/doctype/appraisal_template/appraisal_template.py
index e5d3c42..d0dfad4 100644
--- a/erpnext/hr/doctype/appraisal_template/appraisal_template.py
+++ b/erpnext/hr/doctype/appraisal_template/appraisal_template.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cint
+from frappe.utils import cint, flt
from frappe import _
from frappe.model.document import Document
@@ -11,11 +11,11 @@
class AppraisalTemplate(Document):
def validate(self):
self.check_total_points()
-
- def check_total_points(self):
+
+ def check_total_points(self):
total_points = 0
for d in self.get("goals"):
- total_points += int(d.per_weightage or 0)
+ total_points += flt(d.per_weightage)
if cint(total_points) != 100:
frappe.throw(_("Sum of points for all goals should be 100. It is {0}").format(total_points))
diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json
index 906f6f7..a656a7e 100644
--- a/erpnext/hr/doctype/attendance/attendance.json
+++ b/erpnext/hr/doctype/attendance/attendance.json
@@ -19,11 +19,15 @@
"attendance_date",
"company",
"department",
- "shift",
"attendance_request",
- "amended_from",
+ "details_section",
+ "shift",
+ "in_time",
+ "out_time",
+ "column_break_18",
"late_entry",
- "early_exit"
+ "early_exit",
+ "amended_from"
],
"fields": [
{
@@ -172,13 +176,36 @@
"fieldname": "early_exit",
"fieldtype": "Check",
"label": "Early Exit"
+ },
+ {
+ "fieldname": "details_section",
+ "fieldtype": "Section Break",
+ "label": "Details"
+ },
+ {
+ "depends_on": "shift",
+ "fieldname": "in_time",
+ "fieldtype": "Datetime",
+ "label": "In Time",
+ "read_only": 1
+ },
+ {
+ "depends_on": "shift",
+ "fieldname": "out_time",
+ "fieldtype": "Datetime",
+ "label": "Out Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-ok",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-11 11:40:14.319496",
+ "modified": "2020-05-29 13:51:37.177231",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 6c7ee00..45b7060 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -172,8 +172,8 @@
records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [
- ["attendance_date", ">", month_start],
- ["attendance_date", "<", month_end],
+ ["attendance_date", ">=", month_start],
+ ["attendance_date", "<=", month_end],
["employee", "=", employee],
["docstatus", "!=", 2]
])
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index df0f75a..d4c118f 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -19,7 +19,7 @@
approvers = []
department_details = {}
department_list = []
- employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
+ employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department:
@@ -33,10 +33,16 @@
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
+ if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
+ approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
+
+
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
+ field_name = "Leave Approver"
else:
parentfield = "expense_approvers"
+ field_name = "Expense Approver"
if department_list:
for d in department_list:
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
@@ -46,4 +52,12 @@
and approver.parentfield = %s
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"))
+
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 f575765..7dacacf1 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -62,6 +62,7 @@
"salary_mode",
"payroll_cost_center",
"column_break_52",
+ "expense_approver",
"bank_name",
"bank_ac_no",
"health_insurance_section",
@@ -205,7 +206,7 @@
"label": "Status",
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nActive\nLeft",
+ "options": "Active\nLeft",
"reqd": 1,
"search_index": 1
},
@@ -667,6 +668,7 @@
"oldfieldtype": "Date"
},
{
+ "depends_on": "eval:doc.status == \"Left\"",
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
@@ -797,13 +799,21 @@
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "expense_approver",
+ "fieldtype": "Link",
+ "label": "Expense Approver",
+ "options": "User",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
- "modified": "2020-05-05 18:51:03.152503",
+ "modified": "2020-06-18 18:01:27.223535",
"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 6cc49cf..cba8ee9 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -139,13 +139,13 @@
employee: function (frm) {
if (frm.doc.employee) {
return frappe.call({
- method: "erpnext.hr.doctype.employee_advance.employee_advance.get_due_advance_amount",
+ 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("due_advance_amount",r.message);
+ frm.set_value("pending_amount",r.message);
}
});
}
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 8c5ce42..0d90913 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -19,7 +19,7 @@
"column_break_11",
"advance_amount",
"paid_amount",
- "due_advance_amount",
+ "pending_amount",
"claimed_amount",
"return_amount",
"section_break_7",
@@ -103,14 +103,6 @@
"read_only": 1
},
{
- "depends_on": "eval:cur_frm.doc.employee",
- "fieldname": "due_advance_amount",
- "fieldtype": "Currency",
- "label": "Due Advance Amount",
- "options": "Company:company:default_currency",
- "read_only": 1
- },
- {
"fieldname": "claimed_amount",
"fieldtype": "Currency",
"label": "Claimed Amount",
@@ -177,11 +169,19 @@
"fieldname": "repay_unclaimed_amount_from_salary",
"fieldtype": "Check",
"label": "Repay unclaimed amount from salary"
+ },
+ {
+ "depends_on": "eval:cur_frm.doc.employee",
+ "fieldname": "pending_amount",
+ "fieldtype": "Currency",
+ "label": "Pending Amount",
+ "options": "Company:company:default_currency",
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-03-06 15:11:33.747535",
+ "modified": "2020-06-12 12:42:39.833818",
"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 23e4992..a49dfcf 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -95,7 +95,7 @@
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist()
-def get_due_advance_amount(employee, posting_date):
+def get_pending_amount(employee, posting_date):
employee_due_amount = frappe.get_all("Employee Advance", \
filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
fields = ["advance_amount", "paid_amount"])
@@ -146,7 +146,7 @@
return additional_salary
@frappe.whitelist()
-def make_return_entry(employee_name, company, employee_advance_name, return_amount, mode_of_payment, advance_account):
+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 = ''
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 8670512..15fbd4e 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -72,7 +72,7 @@
return doc
-def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
+def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
"""Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@@ -100,7 +100,9 @@
'company': employee_doc.company,
'shift': shift,
'late_entry': late_entry,
- 'early_exit': early_exit
+ 'early_exit': early_exit,
+ 'in_time': in_time,
+ 'out_time': out_time
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index fb23103..6bb9af9 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -243,7 +243,6 @@
},
update_employee_advance_claimed_amount: function(frm) {
- console.log("update_employee_advance_claimed_amount")
let amount_to_be_allocated = frm.doc.grand_total;
$.each(frm.doc.advances || [], function(i, advance){
if (amount_to_be_allocated >= advance.unclaimed_amount){
@@ -295,6 +294,16 @@
frm.events.get_advances(frm);
},
+ cost_center: function(frm) {
+ frm.events.set_child_cost_center(frm);
+ },
+ set_child_cost_center: function(frm){
+ (frm.doc.expenses || []).forEach(function(d) {
+ if (!d.cost_center){
+ d.cost_center = frm.doc.cost_center;
+ }
+ });
+ },
get_taxes: function(frm) {
if(frm.doc.taxes) {
frappe.call({
@@ -338,8 +347,7 @@
frappe.ui.form.on("Expense Claim Detail", {
expenses_add: function(frm, cdt, cdn) {
- var row = frappe.get_doc(cdt, cdn);
- frm.script_manager.copy_from_first_row("expenses", row, ["cost_center"]);
+ frm.events.set_child_cost_center(frm);
},
amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index 96baaab..fa28470 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -66,6 +66,7 @@
"fieldname": "employee",
"fieldtype": "Link",
"in_global_search": 1,
+ "in_standard_filter": 1,
"label": "From Employee",
"oldfieldname": "employee",
"oldfieldtype": "Link",
@@ -164,6 +165,7 @@
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
+ "in_standard_filter": 1,
"label": "Posting Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
@@ -236,6 +238,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
@@ -368,7 +371,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2019-12-14 23:52:05.388458",
+ "modified": "2020-06-15 12:43:04.099803",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 1f50e27..fb1f2c0 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -38,11 +38,15 @@
},
validate: function(frm) {
+ if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){
+ frm.doc.half_day_date = frm.doc.from_date;
+ }
frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
},
make_dashboard: function(frm) {
var leave_details;
+ let lwps;
if (frm.doc.employee) {
frappe.call({
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
@@ -58,6 +62,7 @@
if (!r.exc && r.message['leave_approver']) {
frm.set_value('leave_approver', r.message['leave_approver']);
}
+ lwps = r.message["lwps"];
}
});
$("div").remove(".form-dashboard-section.custom");
@@ -67,6 +72,18 @@
})
);
frm.dashboard.show();
+ let allowed_leave_types = Object.keys(leave_details);
+
+ // lwps should be allowed, lwps don't have any allocation
+ allowed_leave_types = allowed_leave_types.concat(lwps);
+
+ frm.set_query('leave_type', function(){
+ return {
+ filters : [
+ ['leave_type_name', 'in', allowed_leave_types]
+ ]
+ };
+ });
}
},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json
index 74707a2..7f50ace 100644
--- a/erpnext/hr/doctype/leave_application/leave_application.json
+++ b/erpnext/hr/doctype/leave_application/leave_application.json
@@ -174,7 +174,8 @@
"label": "Status",
"no_copy": 1,
"options": "Open\nApproved\nRejected\nCancelled",
- "permlevel": 1
+ "permlevel": 1,
+ "reqd": 1
},
{
"fieldname": "sb10",
@@ -189,14 +190,14 @@
"reqd": 1
},
{
+ "fetch_from": "employee.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1,
"remember_last_selected_value": 1,
- "reqd": 1,
- "fetch_from": "employee.company"
+ "reqd": 1
},
{
"allow_on_submit": 1,
@@ -249,7 +250,7 @@
"is_submittable": 1,
"links": [],
"max_attachments": 3,
- "modified": "2020-03-10 22:40:43.487721",
+ "modified": "2020-05-18 13:00:41.577327",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Application",
@@ -334,4 +335,4 @@
"sort_order": "DESC",
"timeline_field": "employee",
"title_field": "employee_name"
-}
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index d2620be..0423824 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -19,7 +19,6 @@
from frappe.model.document import Document
class LeaveApplication(Document):
-
def get_feed(self):
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
@@ -33,6 +32,7 @@
self.validate_block_days()
self.validate_salary_processed_days()
self.validate_attendance()
+ self.set_half_day_date()
if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
self.validate_optional_leave()
self.validate_applicable_after()
@@ -290,6 +290,10 @@
frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
day = add_days(day, 1)
+ 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
+
def notify_employee(self):
employee = frappe.get_doc("Employee", self.employee)
if not employee.user_id:
@@ -436,21 +440,36 @@
leave_allocation = {}
for d in allocation_records:
allocation = allocation_records.get(d, frappe._dict())
+
+ total_allocated_leaves = frappe.db.get_value('Leave Allocation', {
+ 'from_date': ('<=', date),
+ 'to_date': ('>=', date),
+ 'employee': employee,
+ 'leave_type': allocation.leave_type,
+ }, 'SUM(total_leaves_allocated)') or 0
+
remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
consider_all_leaves_in_the_allocation_period=True)
+
end_date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1
leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, end_date)
leave_allocation[d] = {
- "total_leaves": allocation.total_leaves_allocated,
+ "total_leaves": total_allocated_leaves,
+ "expired_leaves": total_allocated_leaves - (remaining_leaves + leaves_taken),
"leaves_taken": leaves_taken,
"pending_leaves": leaves_pending,
"remaining_leaves": remaining_leaves}
+ #is used in set query
+ lwps = frappe.get_list("Leave Type", filters = {"is_lwp": 1})
+ lwps = [lwp.name for lwp in lwps]
+
ret = {
'leave_allocation': leave_allocation,
- 'leave_approver': get_leave_approver(employee)
+ 'leave_approver': get_leave_approver(employee),
+ 'lwps': lwps
}
return ret
@@ -596,7 +615,7 @@
is_carry_forward, is_expired
FROM `tabLeave Ledger Entry`
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
- AND docstatus=1
+ AND docstatus=1
AND (leaves<0
OR is_expired=1)
AND (from_date between %(from_date)s AND %(to_date)s
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
index 2385b6a..d30e3b9 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html
@@ -1,22 +1,23 @@
-{% if data %}
+{% if not jQuery.isEmptyObject(data) %}
<h5 style="margin-top: 20px;"> {{ __("Allocated Leaves") }} </h5>
<table class="table table-bordered small">
<thead>
<tr>
- <th style="width: 20%">{{ __("Leave Type") }}</th>
- <th style="width: 20%" class="text-right">{{ __("Total Allocated Leaves") }}</th>
- <th style="width: 20%" class="text-right">{{ __("Used Leaves") }}</th>
- <th style="width: 20%" class="text-right">{{ __("Pending Leaves") }}</th>
- <th style="width: 20%" class="text-right">{{ __("Available Leaves") }}</th>
+ <th style="width: 16%">{{ __("Leave Type") }}</th>
+ <th style="width: 16%" class="text-right">{{ __("Total Allocated Leaves") }}</th>
+ <th style="width: 16%" class="text-right">{{ __("Expired Leaves") }}</th>
+ <th style="width: 16%" class="text-right">{{ __("Used Leaves") }}</th>
+ <th style="width: 16%" class="text-right">{{ __("Pending Leaves") }}</th>
+ <th style="width: 16%" class="text-right">{{ __("Available Leaves") }}</th>
</tr>
-
</thead>
<tbody>
{% for(const [key, value] of Object.entries(data)) { %}
<tr>
<td> {%= key %} </td>
<td class="text-right"> {%= value["total_leaves"] %} </td>
+ <td class="text-right"> {%= value["expired_leaves"] %} </td>
<td class="text-right"> {%= value["leaves_taken"] %} </td>
<td class="text-right"> {%= value["pending_leaves"] %} </td>
<td class="text-right"> {%= value["remaining_leaves"] %} </td>
@@ -24,6 +25,6 @@
{% } %}
</tbody>
</table>
-{% } else { %}
+{% else %}
<p style="margin-top: 30px;"> No Leaves have been allocated. </p>
-{% } %}
\ No newline at end of file
+{% endif %}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index d56080e..1973564 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -28,13 +28,14 @@
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
single_shift_logs = list(group)
- attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs)
- mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name)
+ attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
+ mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee)
def get_attendance(self, logs):
- """Return attendance_status, working_hours for a set of logs belonging to a single shift.
+ """Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
+ for a set of logs belonging to a single shift.
Assumtion:
1. These logs belongs to an single shift, single employee and is not in a holiday date.
2. Logs are in chronological order
@@ -48,10 +49,10 @@
early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
- return 'Absent', total_working_hours, late_entry, early_exit
+ return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
- return 'Half Day', total_working_hours, late_entry, early_exit
- return 'Present', total_working_hours, late_entry, early_exit
+ return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
+ return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index 61faea1..edf05e8 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -22,6 +22,9 @@
args = frappe.local.form_dict
+ if getdate(args.from_date) > getdate(args.to_date):
+ frappe.throw(_("To Date should be greater than From Date"))
+
w = UnicodeWriter()
w = add_header(w)
diff --git a/erpnext/hr/module_onboarding/human_resource/human_resource.json b/erpnext/hr/module_onboarding/human_resource/human_resource.json
new file mode 100644
index 0000000..e64582b
--- /dev/null
+++ b/erpnext/hr/module_onboarding/human_resource/human_resource.json
@@ -0,0 +1,51 @@
+{
+ "allow_roles": [
+ {
+ "role": "HR Manager"
+ },
+ {
+ "role": "HR User"
+ }
+ ],
+ "creation": "2020-05-14 11:51:45.050242",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-20 11:20:07.992597",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Human Resource",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Create Department"
+ },
+ {
+ "step": "Create Designation"
+ },
+ {
+ "step": "Create Holiday list"
+ },
+ {
+ "step": "Create Employee"
+ },
+ {
+ "step": "Create Leave Type"
+ },
+ {
+ "step": "Create Leave Allocation"
+ },
+ {
+ "step": "Create Leave Application"
+ },
+ {
+ "step": "HR Settings"
+ }
+ ],
+ "subtitle": "Employee, Leaves and more.",
+ "success_message": "The HR Module is all set up!",
+ "title": "Let's Setup the Human Resource Module. ",
+ "user_can_dismiss": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_department/create_department.json b/erpnext/hr/onboarding_step/create_department/create_department.json
new file mode 100644
index 0000000..66a54cf
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_department/create_department.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 11:44:34.682115",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 12:22:26.448420",
+ "modified_by": "Administrator",
+ "name": "Create Department",
+ "owner": "Administrator",
+ "reference_document": "Department",
+ "show_full_form": 0,
+ "title": "Create Department",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_designation/create_designation.json b/erpnext/hr/onboarding_step/create_designation/create_designation.json
new file mode 100644
index 0000000..c4e9cc7
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_designation/create_designation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 11:45:07.514193",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 12:22:41.500795",
+ "modified_by": "Administrator",
+ "name": "Create Designation",
+ "owner": "Administrator",
+ "reference_document": "Designation",
+ "show_full_form": 0,
+ "title": "Create Designation",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_employee/create_employee.json b/erpnext/hr/onboarding_step/create_employee/create_employee.json
new file mode 100644
index 0000000..3aa33c6
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_employee/create_employee.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 11:43:25.561152",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 12:26:28.629074",
+ "modified_by": "Administrator",
+ "name": "Create Employee",
+ "owner": "Administrator",
+ "reference_document": "Employee",
+ "show_full_form": 0,
+ "title": "Create Employee",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
new file mode 100644
index 0000000..32472b4
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-28 11:47:34.700174",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 12:25:38.068582",
+ "modified_by": "Administrator",
+ "name": "Create Holiday list",
+ "owner": "Administrator",
+ "reference_document": "Holiday List",
+ "show_full_form": 1,
+ "title": "Create Holiday List",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json
new file mode 100644
index 0000000..fa9941e
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 11:48:56.123718",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 11:48:56.123718",
+ "modified_by": "Administrator",
+ "name": "Create Leave Allocation",
+ "owner": "Administrator",
+ "reference_document": "Leave Allocation",
+ "show_full_form": 0,
+ "title": "Create Leave Allocation",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json
new file mode 100644
index 0000000..1ed074e
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 11:49:45.400764",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 11:49:45.400764",
+ "modified_by": "Administrator",
+ "name": "Create Leave Application",
+ "owner": "Administrator",
+ "reference_document": "Leave Application",
+ "show_full_form": 0,
+ "title": "Create Leave Application",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json
new file mode 100644
index 0000000..8cbfc5c
--- /dev/null
+++ b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-27 11:17:31.119312",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-20 11:17:31.119312",
+ "modified_by": "Administrator",
+ "name": "Create Leave Type",
+ "owner": "Administrator",
+ "reference_document": "Leave Type",
+ "show_full_form": 1,
+ "title": "Create Leave Type",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
new file mode 100644
index 0000000..0a1d0ba
--- /dev/null
+++ b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-28 13:13:52.427711",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-20 11:16:42.430974",
+ "modified_by": "Administrator",
+ "name": "HR Settings",
+ "owner": "Administrator",
+ "reference_document": "HR Settings",
+ "show_full_form": 0,
+ "title": "HR Settings",
+ "validate_action": 0
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/department_analytics/department_analytics.js b/erpnext/hr/report/department_analytics/department_analytics.js
deleted file mode 100644
index 29fedcd..0000000
--- a/erpnext/hr/report/department_analytics/department_analytics.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.query_reports["Department Analytics"] = {
- "filters": [
- {
- "fieldname":"company",
- "label": __("Company"),
- "fieldtype": "Link",
- "options": "Company",
- "default": frappe.defaults.get_user_default("Company"),
- "reqd": 1
- },
- ]
-};
\ No newline at end of file
diff --git a/erpnext/hr/report/department_analytics/department_analytics.json b/erpnext/hr/report/department_analytics/department_analytics.json
deleted file mode 100644
index 1e26b33..0000000
--- a/erpnext/hr/report/department_analytics/department_analytics.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "add_total_row": 0,
- "creation": "2018-05-15 15:37:20.883263",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2018-05-15 17:19:32.934321",
- "modified_by": "Administrator",
- "module": "HR",
- "name": "Department Analytics",
- "owner": "Administrator",
- "ref_doctype": "Employee",
- "report_name": "Department Analytics",
- "report_type": "Script Report",
- "roles": [
- {
- "role": "Employee"
- },
- {
- "role": "HR User"
- },
- {
- "role": "HR Manager"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/hr/report/department_analytics/department_analytics.py b/erpnext/hr/report/department_analytics/department_analytics.py
deleted file mode 100644
index b28eac4..0000000
--- a/erpnext/hr/report/department_analytics/department_analytics.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-
-def execute(filters=None):
- if not filters: filters = {}
-
- if not filters["company"]:
- frappe.throw(_('{0} is mandatory').format(_('Company')))
-
- columns = get_columns()
- employees = get_employees(filters)
- departments_result = get_department(filters)
- departments = []
- if departments_result:
- for department in departments_result:
- departments.append(department)
- chart = get_chart_data(departments,employees)
- return columns, employees, None, chart
-
-def get_columns():
- return [
- _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
- _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Gender") + "::60", _("Company") + ":Link/Company:120"
- ]
-
-def get_conditions(filters):
- conditions = ""
- if filters.get("department"): conditions += " and department = '%s'" % \
- filters["department"].replace("'", "\\'")
-
- if filters.get("company"): conditions += " and company = '%s'" % \
- filters["company"].replace("'", "\\'")
- return conditions
-
-def get_employees(filters):
- conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee_name, date_of_birth,
- branch, department, designation,
- gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1)
-
-def get_department(filters):
- return frappe.db.sql("""select name from `tabDepartment` where company = %s""", (filters["company"]), as_list=1)
-
-def get_chart_data(departments,employees):
- if not departments:
- departments = []
- datasets = []
- for department in departments:
- if department:
- total_employee = frappe.db.sql("""select count(*) from \
- `tabEmployee` where \
- department = %s""" ,(department[0]), as_list=1)
- datasets.append(total_employee[0][0])
- chart = {
- "data": {
- 'labels': departments,
- 'datasets': [{'name': 'Employees','values': datasets}]
- }
- }
- chart["type"] = "bar"
- return chart
-
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/hr/report/employee_analytics/__init__.py
similarity index 100%
rename from erpnext/hr/report/department_analytics/__init__.py
rename to erpnext/hr/report/employee_analytics/__init__.py
diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.js b/erpnext/hr/report/employee_analytics/employee_analytics.js
new file mode 100644
index 0000000..8620a65
--- /dev/null
+++ b/erpnext/hr/report/employee_analytics/employee_analytics.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Employee Analytics"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname":"parameter",
+ "label": __("Parameter"),
+ "fieldtype": "Select",
+ "options": ["Branch","Grade","Department","Designation", "Employment Type"],
+ "reqd": 1
+ }
+ ]
+};
diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.json b/erpnext/hr/report/employee_analytics/employee_analytics.json
new file mode 100644
index 0000000..5a7ab9a
--- /dev/null
+++ b/erpnext/hr/report/employee_analytics/employee_analytics.json
@@ -0,0 +1,30 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-05-12 13:52:50.631086",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-12 13:52:50.631086",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Employee",
+ "report_name": "Employee Analytics",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Employee"
+ },
+ {
+ "role": "HR User"
+ },
+ {
+ "role": "HR Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.py b/erpnext/hr/report/employee_analytics/employee_analytics.py
new file mode 100644
index 0000000..8f39388
--- /dev/null
+++ b/erpnext/hr/report/employee_analytics/employee_analytics.py
@@ -0,0 +1,84 @@
+# 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):
+ if not filters: filters = {}
+
+ if not filters["company"]:
+ frappe.throw(_('{0} is mandatory').format(_('Company')))
+
+ columns = get_columns()
+ employees = get_employees(filters)
+ parameters_result = get_parameters(filters)
+ parameters = []
+ if parameters_result:
+ for department in parameters_result:
+ parameters.append(department)
+
+ chart = get_chart_data(parameters,employees, filters)
+ return columns, employees, None, chart
+
+def get_columns():
+ return [
+ _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
+ _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120", _("Gender") + "::100", _("Company") + ":Link/Company:120"
+ ]
+
+def get_conditions(filters):
+ conditions = " and "+filters.get("parameter").lower().replace(" ","_")+" IS NOT NULL "
+
+ if filters.get("company"): conditions += " and company = '%s'" % \
+ filters["company"].replace("'", "\\'")
+ return conditions
+
+def get_employees(filters):
+ conditions = get_conditions(filters)
+ return frappe.db.sql("""select name, employee_name, date_of_birth,
+ branch, department, designation,
+ gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1)
+
+def get_parameters(filters):
+ if filters.get("parameter") == "Grade":
+ parameter = "Employee Grade"
+ else:
+ parameter = filters.get("parameter")
+
+ return frappe.db.sql("""select name from `tab"""+ parameter +"""` """, as_list=1)
+
+def get_chart_data(parameters,employees, filters):
+ if not parameters:
+ parameters = []
+ datasets = []
+ parameter_field_name = filters.get("parameter").lower().replace(" ","_")
+ label = []
+ for parameter in parameters:
+ if parameter:
+ total_employee = frappe.db.sql("""select count(*) from
+ `tabEmployee` where """+
+ parameter_field_name + """ = %s and company = %s""" ,( parameter[0], filters.get("company")), as_list=1)
+ if total_employee[0][0]:
+ label.append(parameter)
+ datasets.append(total_employee[0][0])
+
+ values = [ value for value in datasets if value !=0]
+
+ total_employee = frappe.db.count('Employee', {'status':'Active'})
+ others = total_employee - sum(values)
+
+ label.append(["Not Set"])
+ values.append(others)
+
+ chart = {
+ "data": {
+ 'labels': label,
+ 'datasets': [{'name': 'Employees','values': values}]
+ }
+ }
+ chart["type"] = "donut"
+ return chart
+
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 97be5cd..db1d191 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -3,13 +3,13 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import flt
+from frappe.utils import flt, add_days
from frappe import _
-from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on, get_leave_allocation_records
+from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
def execute(filters=None):
if filters.to_date <= filters.from_date:
- frappe.throw(_('From date can not be greater than than To date'))
+ frappe.throw(_('"From date" can not be greater than or equal to "To date"'))
columns = get_columns()
data = get_data(filters)
@@ -104,14 +104,17 @@
new_allocation, expired_leaves = get_allocated_and_expired_leaves(filters.from_date, filters.to_date, employee.name, leave_type)
- opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
- closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
+ opening = get_leave_balance_on(employee.name, leave_type, add_days(filters.from_date, -1)) #allocation boundary condition
row.leaves_allocated = new_allocation
row.leaves_expired = expired_leaves - leaves_taken if expired_leaves - leaves_taken > 0 else 0
row.opening_balance = opening
row.leaves_taken = leaves_taken
- row.closing_balance = closing
+
+ # not be shown on the basis of days left it create in user mind for carry_forward leave
+ row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
+
+
row.indent = 1
data.append(row)
new_leaves_allocated = 0
@@ -177,7 +180,7 @@
}, as_dict=1)
for record in records:
- if record.to_date <= getdate(to_date):
+ if record.to_date < getdate(to_date):
expired_leaves += record.leaves
if record.from_date >= getdate(from_date):
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index a5cdecf..92715d3 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -6,7 +6,7 @@
from frappe import _
from frappe.utils import flt
from erpnext.hr.doctype.leave_application.leave_application \
- import get_leave_balance_on, get_leaves_for_period
+ import get_leave_details
from erpnext.hr.report.employee_leave_balance.employee_leave_balance \
import get_department_leave_approver_map
@@ -61,14 +61,14 @@
if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
row = [employee.name, employee.employee_name, employee.department]
-
+ available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:
-
+ remaining = 0
+ if leave_type in available_leave["leave_allocation"]:
# opening balance
- opening = get_leave_balance_on(employee.name, leave_type, filters.date)
+ remaining = available_leave["leave_allocation"][leave_type]['remaining_leaves']
-
- row += [opening]
+ row += [remaining]
data.append(row)
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index 82ed277..47daab1 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -30,8 +30,11 @@
def execute(filters=None):
if not filters: filters = {}
+ if filters.hide_year_field == 1:
+ filters.year = 2020
+
conditions, filters = get_conditions(filters)
- columns = get_columns(filters)
+ columns, days = get_columns(filters)
att_map = get_attendance_list(conditions, filters)
if filters.group_by:
@@ -60,20 +63,67 @@
columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"])
if filters.group_by:
+ emp_att_map = {}
for parameter in group_by_parameters:
data.append([ "<b>"+ parameter + "</b>"])
- record = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, leave_list=leave_list)
+ record, aaa = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
+ emp_att_map.update(aaa)
data += record
else:
- record = add_data(emp_map, att_map, filters, holiday_map, conditions, leave_list=leave_list)
+ record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=leave_list)
data += record
- return columns, data
+ chart_data = get_chart_data(emp_att_map, days)
+
+ return columns, data, None, chart_data
+
+def get_chart_data(emp_att_map, days):
+ labels = []
+ datasets = [
+ {"name": "Absent", "values": []},
+ {"name": "Present", "values": []},
+ {"name": "Leave", "values": []},
+ ]
+ for idx, day in enumerate(days, start=0):
+ p = day.replace("::65", "")
+ labels.append(day.replace("::65", ""))
+ total_absent_on_day = 0
+ total_leave_on_day = 0
+ total_present_on_day = 0
+ total_holiday = 0
+ for emp in emp_att_map.keys():
+ if emp_att_map[emp][idx]:
+ if emp_att_map[emp][idx] == "A":
+ total_absent_on_day += 1
+ if emp_att_map[emp][idx] in ["P", "WFH"]:
+ total_present_on_day += 1
+ if emp_att_map[emp][idx] == "HD":
+ total_present_on_day += 0.5
+ total_leave_on_day += 0.5
+ if emp_att_map[emp][idx] == "L":
+ total_leave_on_day += 1
-def add_data(employee_map, att_map, filters, holiday_map, conditions, leave_list=None):
+ datasets[0]["values"].append(total_absent_on_day)
+ datasets[1]["values"].append(total_present_on_day)
+ datasets[2]["values"].append(total_leave_on_day)
+
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ }
+ }
+
+ chart["type"] = "line"
+
+ return chart
+
+def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_list=None):
record = []
+ emp_att_map = {}
for emp in employee_map:
emp_det = employee_map.get(emp)
if not emp_det or emp not in att_map:
@@ -85,6 +135,7 @@
row += [emp, emp_det.employee_name]
total_p = total_a = total_l = total_h = total_um= 0.0
+ emp_status_map = []
for day in range(filters["total_days_in_month"]):
status = None
status = att_map.get(emp).get(day + 1)
@@ -101,19 +152,11 @@
status = "Holiday"
total_h += 1
+ abbr = status_map.get(status, "")
+ emp_status_map.append(abbr)
- # if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list][0]:
- # if holiday_map[emp_holiday_list][1]:
- # status= "Weekly Off"
- # else:
- # status = "Holiday"
-
- # += 1
-
- if not filters.summarized_view:
- row.append(status_map.get(status, ""))
- else:
- if status == "Present":
+ if filters.summarized_view:
+ if status == "Present" or status == "Work From Home":
total_p += 1
elif status == "Absent":
total_a += 1
@@ -126,6 +169,9 @@
elif not status:
total_um += 1
+ if not filters.summarized_view:
+ row += emp_status_map
+
if filters.summarized_view:
row += [total_p, total_l, total_a, total_h, total_um]
@@ -159,10 +205,10 @@
row.append("0.0")
row.extend([time_default_counts[0][0],time_default_counts[0][1]])
+ emp_att_map[emp] = emp_status_map
record.append(row)
-
- return record
+ return record, emp_att_map
def get_columns(filters):
@@ -172,17 +218,19 @@
columns = [_(filters.group_by)+ ":Link/Branch:120"]
columns += [
- _("Employee") + ":Link/Employee:120", _("Employee Name") + ":Link/Employee:120"
+ _("Employee") + ":Link/Employee:120", _("Employee Name") + ":Data/:120"
]
-
+ days = []
+ for day in range(filters["total_days_in_month"]):
+ date = str(filters.year) + "-" + str(filters.month)+ "-" + str(day+1)
+ day_name = day_abbr[getdate(date).weekday()]
+ days.append(cstr(day+1)+ " " +day_name +"::65")
if not filters.summarized_view:
- for day in range(filters["total_days_in_month"]):
- date = str(filters.year) + "-" + str(filters.month)+ "-" + str(day+1)
- day_name = day_abbr[getdate(date).weekday()]
- columns.append(cstr(day+1)+ " " +day_name +"::65")
- else:
+ columns += days
+
+ if filters.summarized_view:
columns += [_("Total Present") + ":Float:120", _("Total Leaves") + ":Float:120", _("Total Absent") + ":Float:120", _("Total Holidays") + ":Float:120", _("Unmarked Days")+ ":Float:120"]
- return columns
+ return columns, days
def get_attendance_list(conditions, filters):
attendance_list = frappe.db.sql("""select employee, day(attendance_date) as day_of_month,
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index cd12510..8d95924 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -73,11 +73,11 @@
def assign_task_to_users(self, task, users):
for user in users:
args = {
- 'assign_to' : user,
- 'doctype' : task.doctype,
- 'name' : task.name,
- 'description' : task.description or task.subject,
- 'notify': self.notify_users_by_email
+ 'assign_to': [user],
+ 'doctype': task.doctype,
+ 'name': task.name,
+ 'description': task.description or task.subject,
+ 'notify': self.notify_users_by_email
}
assign_to.add(args)
diff --git a/erpnext/loan_management/desk_page/loan_management/loan_management.json b/erpnext/loan_management/desk_page/loan/loan.json
similarity index 74%
rename from erpnext/loan_management/desk_page/loan_management/loan_management.json
rename to erpnext/loan_management/desk_page/loan/loan.json
index f9ea978..48193b0 100644
--- a/erpnext/loan_management/desk_page/loan_management/loan_management.json
+++ b/erpnext/loan_management/desk_page/loan/loan.json
@@ -23,7 +23,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Loan Repayment\"\n ],\n \"doctype\": \"Loan Repayment\",\n \"incomplete_dependencies\": [\n \"Loan Repayment\"\n ],\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 \"dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"doctype\": \"Loan Security Pledge\",\n \"incomplete_dependencies\": [\n \"Loan Security Pledge\"\n ],\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]"
+ "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",
@@ -36,18 +36,21 @@
"extends_another_page": 0,
"idx": 0,
"is_standard": 1,
- "label": "Loan Management",
- "modified": "2020-04-02 11:28:51.380509",
+ "label": "Loan",
+ "modified": "2020-06-07 19:42:14.947902",
"modified_by": "Administrator",
"module": "Loan Management",
- "name": "Loan Management",
+ "name": "Loan",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#ffe8cd",
+ "format": "{} Open",
"label": "Loan Application",
"link_to": "Loan Application",
+ "stats_filter": "{ \"status\": \"Open\" }",
"type": "DocType"
},
{
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index c9e36a8..d44088b 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -27,6 +27,7 @@
def on_cancel(self):
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self):
if not self.disbursement_date:
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 094b9c6..e6ceb55 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -31,6 +31,7 @@
self.update_is_accrued()
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def update_is_accrued(self):
frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
@@ -176,21 +177,23 @@
return term_loans
def make_loan_interest_accrual_entry(args):
- loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
- loan_interest_accrual.loan = args.loan
- loan_interest_accrual.applicant_type = args.applicant_type
- loan_interest_accrual.applicant = args.applicant
- loan_interest_accrual.interest_income_account = args.interest_income_account
- loan_interest_accrual.loan_account = args.loan_account
- loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2)
- loan_interest_accrual.interest_amount = flt(args.interest_amount, 2)
- loan_interest_accrual.posting_date = args.posting_date or nowdate()
- loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
- loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
- loan_interest_accrual.payable_principal_amount = args.payable_principal
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
- loan_interest_accrual.save()
- loan_interest_accrual.submit()
+ loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
+ loan_interest_accrual.loan = args.loan
+ loan_interest_accrual.applicant_type = args.applicant_type
+ loan_interest_accrual.applicant = args.applicant
+ loan_interest_accrual.interest_income_account = args.interest_income_account
+ loan_interest_accrual.loan_account = args.loan_account
+ loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
+ loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
+ loan_interest_accrual.posting_date = args.posting_date or nowdate()
+ loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
+ loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name
+ loan_interest_accrual.payable_principal_amount = args.payable_principal
+
+ loan_interest_accrual.save()
+ loan_interest_accrual.submit()
def get_no_of_days_for_interest_accural(loan, posting_date):
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 2ab668a..c28994e 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -6,7 +6,7 @@
import frappe, erpnext
import json
from frappe import _
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
from six import iteritems
from frappe.model.document import Document
from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime
@@ -29,8 +29,11 @@
def on_cancel(self):
self.mark_as_unpaid()
self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self, amounts):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
if not self.posting_date:
self.posting_date = get_datetime()
@@ -38,24 +41,26 @@
self.cost_center = erpnext.get_default_cost_center(self.company)
if not self.interest_payable:
- self.interest_payable = flt(amounts['interest_amount'], 2)
+ self.interest_payable = flt(amounts['interest_amount'], precision)
if not self.penalty_amount:
- self.penalty_amount = flt(amounts['penalty_amount'], 2)
+ self.penalty_amount = flt(amounts['penalty_amount'], precision)
if not self.pending_principal_amount:
- self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
+ self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
if not self.payable_principal_amount and self.is_term_loan:
- self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
+ self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
if not self.payable_amount:
- self.payable_amount = flt(amounts['payable_amount'], 2)
+ self.payable_amount = flt(amounts['payable_amount'], precision)
if amounts.get('due_date'):
self.due_date = amounts.get('due_date')
def validate_amount(self):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
if not self.amount_paid:
frappe.throw(_("Amount paid cannot be zero"))
@@ -63,11 +68,13 @@
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
frappe.throw(msg)
- if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2):
+ if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision):
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
frappe.throw(msg)
def update_paid_amount(self):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
+
loan = frappe.get_doc("Loan", self.against_loan)
for payment in self.repayment_details:
@@ -75,9 +82,9 @@
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
- (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
+ (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
- if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
+ if flt(loan.total_principal_paid + self.principal_amount_paid, precision) >= flt(loan.total_payment, precision):
if loan.is_secured_loan:
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
else:
@@ -253,6 +260,7 @@
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
def get_amounts(amounts, against_loan, posting_date, payment_type):
+ precision = cint(frappe.db.get_default("currency_precision")) or 2
against_loan_doc = frappe.get_doc("Loan", against_loan)
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
@@ -282,8 +290,8 @@
payable_principal_amount += entry.payable_principal_amount
pending_accrual_entries.setdefault(entry.name, {
- 'interest_amount': flt(entry.interest_amount),
- 'payable_principal_amount': flt(entry.payable_principal_amount)
+ 'interest_amount': flt(entry.interest_amount, precision),
+ 'payable_principal_amount': flt(entry.payable_principal_amount, precision)
})
if not final_due_date:
@@ -301,11 +309,11 @@
per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365
total_pending_interest += (pending_days * per_day_interest)
- amounts["pending_principal_amount"] = pending_principal_amount
- amounts["payable_principal_amount"] = payable_principal_amount
- amounts["interest_amount"] = total_pending_interest
- amounts["penalty_amount"] = penalty_amount
- amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
+ amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
+ amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
+ amounts["interest_amount"] = flt(total_pending_interest, precision)
+ amounts["penalty_amount"] = flt(penalty_amount, precision)
+ amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
amounts["pending_accrual_entries"] = pending_accrual_entries
if final_due_date:
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 51c5cb9..1dd3710 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -41,6 +41,7 @@
"options": "Company:company:default_currency"
},
{
+ "default": "0",
"fieldname": "rate_of_interest",
"fieldtype": "Percent",
"label": "Rate of Interest (%) Yearly",
@@ -143,7 +144,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2020-04-15 00:24:43.259963",
+ "modified": "2020-06-07 18:55:59.346292",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Type",
diff --git a/erpnext/loan_management/report/loan_security_status/loan_security_status.py b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
index ea6a2ee6..1951855 100644
--- a/erpnext/loan_management/report/loan_security_status/loan_security_status.py
+++ b/erpnext/loan_management/report/loan_security_status/loan_security_status.py
@@ -76,7 +76,8 @@
"fieldtype": "Link",
"fieldname": "currency",
"options": "Currency",
- "width": 50
+ "width": 50,
+ "hidden": 1
}
]
@@ -84,17 +85,13 @@
def get_data(filters):
- loan_security_price_map = frappe._dict(frappe.get_all("Loan Security",
- fields=["name", "loan_security_price"], as_list=1
- ))
-
data = []
conditions = get_conditions(filters)
loan_security_pledges = frappe.db.sql("""
SELECT
p.name, p.applicant, p.loan, p.status, p.pledge_time,
- c.loan_security, c.qty
+ c.loan_security, c.qty, c.loan_security_price, c.amount
FROM
`tabLoan Security Pledge` p, `tabPledge` c
WHERE
@@ -115,8 +112,8 @@
row["pledge_time"] = pledge.pledge_time
row["loan_security"] = pledge.loan_security
row["qty"] = pledge.qty
- row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security)
- row["loan_security_value"] = row["loan_security_price"] * pledge.qty
+ row["loan_security_price"] = pledge.loan_security_price
+ row["loan_security_value"] = pledge.amount
row["currency"] = default_currency
data.append(row)
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
new file mode 100644
index 0000000..64e4bc6
--- /dev/null
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -0,0 +1,241 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe, erpnext, json
+from frappe import _
+from frappe.utils import nowdate, get_first_day, get_last_day, add_months
+
+def get_data():
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ "number_cards": get_number_cards(),
+ })
+
+def get_dashboards():
+ return [{
+ "name": "Manufacturing",
+ "dashboard_name": "Manufacturing",
+ "charts": [
+ { "chart": "Produced Quantity", "width": "Half" },
+ { "chart": "Completed Operation", "width": "Half" },
+ { "chart": "Work Order Analysis", "width": "Half" },
+ { "chart": "Quality Inspection Analysis", "width": "Half" },
+ { "chart": "Pending Work Order", "width": "Half" },
+ { "chart": "Last Month Downtime Analysis", "width": "Half" },
+ { "chart": "Work Order Qty Analysis", "width": "Full" },
+ { "chart": "Job Card Analysis", "width": "Full" }
+ ],
+ "cards": [
+ { "card": "Monthly Total Work Order" },
+ { "card": "Monthly Completed Work Order" },
+ { "card": "Ongoing Job Card" },
+ { "card": "Monthly Quality Inspection"}
+ ]
+ }]
+
+def get_charts():
+ company = erpnext.get_default_company()
+
+ if not company:
+ company = frappe.db.get_value("Company", {"is_group": 0}, "name")
+
+ return [{
+ "doctype": "Dashboard Chart",
+ "based_on": "modified",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Produced Quantity"),
+ "name": "Produced Quantity",
+ "document_type": "Work Order",
+ "filters_json": json.dumps([['Work Order', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Monthly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "produced_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "based_on": "creation",
+ "time_interval": "Yearly",
+ "chart_type": "Sum",
+ "chart_name": _("Completed Operation"),
+ "name": "Completed Operation",
+ "document_type": "Work Order Operation",
+ "filters_json": json.dumps([['Work Order Operation', 'docstatus', '=', 1, False]]),
+ "group_by_type": "Count",
+ "time_interval": "Quarterly",
+ "timespan": "Last Year",
+ "owner": "Administrator",
+ "type": "Line",
+ "value_based_on": "completed_qty",
+ "is_public": 1,
+ "timeseries": 1
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Analysis"),
+ "name": "Work Order Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Status"}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Quality Inspection Analysis"),
+ "name": "Quality Inspection Analysis",
+ "timespan": "Last Year",
+ "report_name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "filters_json": json.dumps({}),
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Pending Work Order"),
+ "name": "Pending Work Order",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Age"}),
+ "owner": "Administrator",
+ "type": "Donut",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "axisOptions": {
+ "shortenYAxisNumbers": 1
+ },
+ "height": 300
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Last Month Downtime Analysis"),
+ "name": "Last Month Downtime Analysis",
+ "timespan": "Last Year",
+ "filters_json": json.dumps({}),
+ "report_name": "Downtime Analysis",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "type": "Bar"
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Work Order Qty Analysis"),
+ "name": "Work Order Qty Analysis",
+ "timespan": "Last Year",
+ "report_name": "Work Order Summary",
+ "filters_json": json.dumps({"company": company, "charts_based_on": "Quantity"}),
+ "owner": "Administrator",
+ "type": "Bar",
+ "is_public": 1,
+ "is_custom": 1,
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ }, {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Yearly",
+ "chart_type": "Report",
+ "chart_name": _("Job Card Analysis"),
+ "name": "Job Card Analysis",
+ "timespan": "Last Year",
+ "report_name": "Job Card Summary",
+ "owner": "Administrator",
+ "is_public": 1,
+ "is_custom": 1,
+ "filters_json": json.dumps({"company": company, "docstatus": 1, "range":"Monthly"}),
+ "custom_options": json.dumps({
+ "barOptions": { "stacked": 1 }
+ }),
+ "type": "Bar"
+ }]
+
+def get_number_cards():
+ start_date = add_months(nowdate(), -1)
+ end_date = nowdate()
+
+ return [{
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Total Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [start_date, end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Total Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Work Order",
+ "name": "Monthly Completed Work Order",
+ "filters_json": json.dumps([
+ ['Work Order', 'status', '=', 'Completed'],
+ ['Work Order', 'docstatus', '=', 1],
+ ['Work Order', 'creation', 'between', [start_date, end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Completed Work Order"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Job Card",
+ "name": "Ongoing Job Card",
+ "filters_json": json.dumps([
+ ['Job Card', 'status','!=','Completed'],
+ ['Job Card', 'docstatus', '=', 1]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Ongoing Job Card"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ },
+ {
+ "doctype": "Number Card",
+ "document_type": "Quality Inspection",
+ "name": "Monthly Quality Inspection",
+ "filters_json": json.dumps([
+ ['Quality Inspection', 'docstatus', '=', 1],
+ ['Quality Inspection', 'creation', 'between', [start_date, end_date]]
+ ]),
+ "function": "Count",
+ "is_public": 1,
+ "label": _("Monthly Quality Inspection"),
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Weekly"
+ }]
\ No newline at end of file
diff --git a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
index 18604e2..763f533 100644
--- a/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/desk_page/manufacturing/manufacturing.json
@@ -3,7 +3,7 @@
{
"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 \"dependencies\": [\n \"Activity Type\"\n ],\n \"description\": \"Time Sheet for manufacturing.\",\n \"label\": \"Timesheet\",\n \"name\": \"Timesheet\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Job Card\",\n \"name\": \"Job Card\",\n \"type\": \"doctype\"\n }\n]"
+ "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,
@@ -13,7 +13,7 @@
{
"hidden": 0,
"label": "Reports",
- "links": "[\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Open Work Orders\",\n \"name\": \"Open Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Work Orders in Progress\",\n \"name\": \"Work Orders in Progress\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Issued Items Against Work Order\",\n \"name\": \"Issued Items Against Work Order\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Completed Work Orders\",\n \"name\": \"Completed Work Orders\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Work Order\"\n ],\n \"doctype\": \"Work Order\",\n \"is_query_report\": true,\n \"label\": \"Production Analytics\",\n \"name\": \"Production Analytics\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Search\",\n \"name\": \"BOM Search\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"BOM\"\n ],\n \"doctype\": \"BOM\",\n \"is_query_report\": true,\n \"label\": \"BOM Stock Report\",\n \"name\": \"BOM Stock Report\",\n \"type\": \"report\"\n }\n]"
+ "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,
@@ -32,23 +32,93 @@
}
],
"category": "Domains",
- "charts": [],
+ "charts": [
+ {
+ "chart_name": "Produced Quantity"
+ }
+ ],
"creation": "2020-03-02 17:11:37.032604",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Manufacturing",
- "modified": "2020-04-01 11:28:50.979358",
+ "modified": "2020-05-28 13:54:02.048419",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
+ "onboarding": "Manufacturing",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"restrict_to_domain": "Manufacturing",
- "shortcuts": []
+ "shortcuts": [
+ {
+ "color": "#cef6d1",
+ "format": "{} Active",
+ "label": "Item",
+ "link_to": "Item",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"disabled\": 0\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#cef6d1",
+ "format": "{} Active",
+ "label": "BOM",
+ "link_to": "BOM",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{\n \"is_active\": 1\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} Open",
+ "label": "Work Order",
+ "link_to": "Work Order",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"in\", \n [\"Draft\", \"Not Started\", \"In Process\"]\n ]\n}",
+ "type": "DocType"
+ },
+ {
+ "color": "#ffe8cd",
+ "format": "{} Open",
+ "label": "Production Plan",
+ "link_to": "Production Plan",
+ "restrict_to_domain": "Manufacturing",
+ "stats_filter": "{ \n \"status\": [\"not in\", [\"Completed\"]]\n}",
+ "type": "DocType"
+ },
+ {
+ "label": "Dashboard",
+ "link_to": "Manufacturing",
+ "restrict_to_domain": "Manufacturing",
+ "type": "Dashboard"
+ },
+ {
+ "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"
+ }
+ ]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 44da9ca..47b4207 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -29,10 +29,7 @@
frm.set_query("item", function() {
return {
- query: "erpnext.controllers.queries.item_query",
- filters: {
- "doctype": "BOM"
- }
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query"
};
});
@@ -44,9 +41,12 @@
};
});
- frm.set_query("item_code", "items", function() {
+ frm.set_query("item_code", "items", function(doc) {
return {
- query: "erpnext.controllers.queries.item_query"
+ query: "erpnext.manufacturing.doctype.bom.bom.item_query",
+ filters: {
+ "item_code": doc.item
+ }
};
});
@@ -96,6 +96,12 @@
frm.trigger("make_work_order");
}, __("Create"));
+ if (frm.doc.has_variants) {
+ frm.add_custom_button(__("Variant BOM"), function() {
+ frm.trigger("make_variant_bom");
+ }, __("Create"));
+ }
+
if (frm.doc.inspection_required) {
frm.add_custom_button(__("Quality Inspection"), function() {
frm.trigger("make_quality_inspection");
@@ -124,7 +130,7 @@
}
- if (frm.doc.__onload && frm.doc.__onload["has_variants"]) {
+ if (frm.doc.has_variants) {
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>`,
@@ -138,9 +144,52 @@
},
make_work_order: function(frm) {
+ frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
+ args: {
+ bom_no: frm.doc.name,
+ item: item,
+ qty: data.qty || 0.0,
+ project: frm.doc.project,
+ variant_items: variant_items
+ },
+ freeze: true,
+ callback: function(r) {
+ if(r.message) {
+ let doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ });
+ },
+
+ make_variant_bom: function(frm) {
+ frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => {
+ frappe.call({
+ method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom",
+ args: {
+ source_name: frm.doc.name,
+ bom_no: frm.doc.name,
+ item: item,
+ variant_items: variant_items
+ },
+ freeze: true,
+ callback: function(r) {
+ if(r.message) {
+ let doc = frappe.model.sync(r.message)[0];
+ frappe.set_route("Form", doc.doctype, doc.name);
+ }
+ }
+ });
+ }, true);
+ },
+
+ setup_variant_prompt: function(frm, title, callback, skip_qty_field) {
const fields = [];
- if (frm.doc.__onload && frm.doc.__onload["has_variants"]) {
+ if (frm.doc.has_variants) {
fields.push({
fieldtype: 'Link',
label: __('Variant Item'),
@@ -158,34 +207,106 @@
});
}
- fields.push({
- fieldtype: 'Float',
- label: __('Qty To Manufacture'),
- fieldname: 'qty',
- reqd: 1,
- default: 1
+ if (!skip_qty_field) {
+ fields.push({
+ fieldtype: 'Float',
+ label: __('Qty To Manufacture'),
+ fieldname: 'qty',
+ reqd: 1,
+ default: 1
+ });
+ }
+
+ var has_template_rm = frm.doc.items.filter(d => d.has_variants === 1) || [];
+ if (has_template_rm && has_template_rm.length > 0) {
+ fields.push({
+ fieldname: "items",
+ fieldtype: "Table",
+ label: __("Raw Materials"),
+ fields: [
+ {
+ fieldname: "item_code",
+ options: "Item",
+ label: __("Template Item"),
+ fieldtype: "Link",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ fieldname: "varint_item_code",
+ options: "Item",
+ label: __("Variant Item"),
+ fieldtype: "Link",
+ in_list_view: 1,
+ reqd: 1,
+ get_query: function(data) {
+ if (!data.item_code) {
+ frappe.throw(__("Select template item"));
+ }
+
+ return {
+ query: "erpnext.controllers.queries.item_query",
+ filters: {
+ "variant_of": data.item_code
+ }
+ };
+ }
+ },
+ {
+ fieldname: "qty",
+ label: __("Quantity"),
+ fieldtype: "Float",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ fieldname: "source_warehouse",
+ label: __("Source Warehouse"),
+ fieldtype: "Link",
+ options: "Warehouse"
+ },
+ {
+ fieldname: "operation",
+ label: __("Operation"),
+ fieldtype: "Data",
+ hidden: 1,
+ }
+ ],
+ in_place_edit: true,
+ data: [],
+ get_data: function () {
+ return [];
+ },
+ });
+ }
+
+ let dialog = frappe.prompt(fields, data => {
+ let item = data.item || frm.doc.item;
+ let variant_items = data.items || [];
+
+ variant_items.forEach(d => {
+ if (!d.varint_item_code) {
+ frappe.throw(__("Select variant item code for the template item {0}", [d.item_code]));
+ }
+ })
+
+ callback(frm, item, data, variant_items);
+
+ }, __(title), __("Create"));
+
+ has_template_rm.forEach(d => {
+ dialog.fields_dict.items.df.data.push({
+ "item_code": d.item_code,
+ "varint_item_code": "",
+ "qty": d.qty,
+ "source_warehouse": d.source_warehouse,
+ "operation": d.operation
+ });
});
- frappe.prompt(fields, data => {
- let item = data.item || frm.doc.item;
-
- frappe.call({
- method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
- args: {
- bom_no: frm.doc.name,
- item: item,
- qty: data.qty || 0.0,
- project: frm.doc.project
- },
- freeze: true,
- callback: function(r) {
- if(r.message) {
- var doc = frappe.model.sync(r.message)[0];
- frappe.set_route("Form", doc.doctype, doc.name);
- }
- }
- });
- }, __("Enter Value"), __("Create"));
+ if (has_template_rm) {
+ dialog.fields_dict.items.grid.refresh();
+ }
},
make_quality_inspection: function(frm) {
@@ -265,7 +386,7 @@
plc_conversion_rate: function(doc) {
if (!this.in_apply_price_list) {
- this.apply_price_list();
+ this.apply_price_list(null, true);
}
},
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 4ce0ecf..f551b91 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -53,6 +53,7 @@
"section_break_25",
"description",
"column_break_27",
+ "has_variants",
"section_break0",
"exploded_items",
"website_section",
@@ -498,6 +499,17 @@
"options": "Currency",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fetch_from": "item.has_variants",
+ "fieldname": "has_variants",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Has Variants",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-sitemap",
@@ -505,7 +517,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-05-05 14:29:32.634952",
+ "modified": "2020-05-21 12:29:32.634952",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6ac653e..7d31a1c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -3,13 +3,16 @@
from __future__ import unicode_literals
import frappe, erpnext
-from frappe.utils import cint, cstr, flt
+from frappe.utils import cint, cstr, flt, today
from frappe import _
from erpnext.setup.utils import get_exchange_rate
from frappe.website.website_generator import WebsiteGenerator
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.get_item_details import get_price_list_rate
from frappe.core.doctype.version.version import get_diff
+from erpnext.controllers.queries import get_match_cond
+from erpnext.stock.doctype.item.item import get_item_details
+from frappe.model.mapper import get_mapped_doc
import functools
@@ -59,11 +62,6 @@
self.name = name
- def onload(self):
- super(BOM, self).onload()
- if self.get("item") and cint(frappe.db.get_value("Item", self.item, "has_variants")):
- self.set_onload("has_variants", True)
-
def validate(self):
self.route = frappe.scrub(self.name).replace('_', '-')
self.clear_operations()
@@ -103,9 +101,7 @@
self.manage_default_bom()
def get_item_det(self, item_code):
- item = frappe.db.sql("""select name, item_name, docstatus, description, image,
- is_sub_contracted_item, stock_uom, default_bom, last_purchase_rate, include_item_in_manufacturing
- from `tabItem` where name=%s""", item_code, as_dict = 1)
+ item = get_item_details(item_code)
if not item:
frappe.throw(_("Item: {0} does not exist in the system").format(item_code))
@@ -116,8 +112,16 @@
if self.routing:
self.set("operations", [])
for d in frappe.get_all("BOM Operation", fields = ["*"],
- filters = {'parenttype': 'Routing', 'parent': self.routing}):
- child = self.append('operations', d)
+ filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="idx"):
+ child = self.append('operations', {
+ "operation": d.operation,
+ "workstation": d.workstation,
+ "description": d.description,
+ "time_in_mins": d.time_in_mins,
+ "batch_size": d.batch_size,
+ "operating_cost": d.operating_cost,
+ "idx": d.idx
+ })
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
def set_bom_material_details(self):
@@ -150,10 +154,10 @@
item = self.get_item_det(args['item_code'])
- args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or ''
+ args['bom_no'] = args['bom_no'] or item and cstr(item['default_bom']) or ''
args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or
- item and item[0].include_item_in_manufacturing or 0)
- args.update(item[0])
+ item and item.include_item_in_manufacturing or 0)
+ args.update(item)
rate = self.get_rm_rate(args)
ret_item = {
@@ -185,40 +189,14 @@
self.rm_cost_as_per = "Valuation Rate"
if arg.get('scrap_items'):
- rate = self.get_valuation_rate(arg)
+ rate = get_valuation_rate(arg)
elif arg:
#Customer Provided parts will have zero rate
if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'):
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
rate = flt(self.get_bom_unitcost(arg['bom_no'])) * (arg.get("conversion_factor") or 1)
else:
- if self.rm_cost_as_per == 'Valuation Rate':
- rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
- elif self.rm_cost_as_per == 'Last Purchase Rate':
- rate = flt(arg.get('last_purchase_rate') \
- or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
- * (arg.get("conversion_factor") or 1)
- elif self.rm_cost_as_per == "Price List":
- if not self.buying_price_list:
- frappe.throw(_("Please select Price List"))
- args = frappe._dict({
- "doctype": "BOM",
- "price_list": self.buying_price_list,
- "qty": arg.get("qty") or 1,
- "uom": arg.get("uom") or arg.get("stock_uom"),
- "stock_uom": arg.get("stock_uom"),
- "transaction_type": "buying",
- "company": self.company,
- "currency": self.currency,
- "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
- "conversion_factor": arg.get("conversion_factor") or 1,
- "plc_conversion_rate": 1,
- "ignore_party": True
- })
- item_doc = frappe.get_doc("Item", arg.get("item_code"))
- out = frappe._dict()
- get_price_list_rate(args, item_doc, out)
- rate = out.price_list_rate
+ rate = get_bom_item_rate(arg, self)
if not rate:
if self.rm_cost_as_per == "Price List":
@@ -286,31 +264,6 @@
where is_active = 1 and name = %s""", bom_no, as_dict=1)
return bom and bom[0]['unit_cost'] or 0
- def get_valuation_rate(self, args):
- """ Get weighted average of valuation rate from all warehouses """
-
- total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
- for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
- where item_code=%s""", args['item_code'], as_dict=1):
- total_qty += flt(d.actual_qty)
- total_value += flt(d.stock_value)
-
- if total_qty:
- valuation_rate = total_value / total_qty
-
- if valuation_rate <= 0:
- last_valuation_rate = frappe.db.sql("""select valuation_rate
- from `tabStock Ledger Entry`
- where item_code = %s and valuation_rate > 0
- order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
-
- valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
-
- if not valuation_rate:
- valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
-
- return flt(valuation_rate)
-
def manage_default_bom(self):
""" Uncheck others if current one is selected as default or
check the current one as default if it the only bom for the selected item,
@@ -624,6 +577,62 @@
if not d.batch_size or d.batch_size <= 0:
d.batch_size = 1
+def get_bom_item_rate(args, bom_doc):
+ if bom_doc.rm_cost_as_per == 'Valuation Rate':
+ rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == 'Last Purchase Rate':
+ rate = ( flt(args.get('last_purchase_rate')) \
+ or frappe.db.get_value("Item", args['item_code'], "last_purchase_rate")) \
+ * (args.get("conversion_factor") or 1)
+ elif bom_doc.rm_cost_as_per == "Price List":
+ if not bom_doc.buying_price_list:
+ frappe.throw(_("Please select Price List"))
+ bom_args = frappe._dict({
+ "doctype": "BOM",
+ "price_list": bom_doc.buying_price_list,
+ "qty": args.get("qty") or 1,
+ "uom": args.get("uom") or args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "transaction_type": "buying",
+ "company": bom_doc.company,
+ "currency": bom_doc.currency,
+ "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
+ "conversion_factor": args.get("conversion_factor") or 1,
+ "plc_conversion_rate": 1,
+ "ignore_party": True
+ })
+ item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
+ out = frappe._dict()
+ get_price_list_rate(bom_args, item_doc, out)
+ rate = out.price_list_rate
+
+ return rate
+
+def get_valuation_rate(args):
+ """ Get weighted average of valuation rate from all warehouses """
+
+ total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
+ for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
+ where item_code=%s""", args['item_code'], as_dict=1):
+ total_qty += flt(d.actual_qty)
+ total_value += flt(d.stock_value)
+
+ if total_qty:
+ valuation_rate = total_value / total_qty
+
+ if valuation_rate <= 0:
+ last_valuation_rate = frappe.db.sql("""select valuation_rate
+ from `tabStock Ledger Entry`
+ where item_code = %s and valuation_rate > 0
+ order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
+
+ valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
+
+ if not valuation_rate:
+ valuation_rate = frappe.db.get_value("Item", args['item_code'], "valuation_rate")
+
+ return flt(valuation_rate)
+
def get_list_context(context):
context.title = _("Bill of Materials")
# context.introduction = _('Boms')
@@ -639,6 +648,8 @@
sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty,
item.image,
bom.project,
+ bom_item.rate,
+ bom_item.amount,
item.stock_uom,
item.item_group,
item.allow_alternative_item,
@@ -655,6 +666,7 @@
where
bom_item.docstatus < 2
and bom.name = %(bom)s
+ and ifnull(item.has_variants, 0) = 0
and item.is_stock_item in (1, {is_stock_item})
{where_conditions}
group by item_code, stock_uom
@@ -897,3 +909,84 @@
out.removed.append([df.fieldname, d.as_dict()])
return out
+
+def item_query(doctype, txt, searchfield, start, page_len, filters):
+ meta = frappe.get_meta("Item", cached=True)
+ searchfields = meta.get_search_fields()
+
+ order_by = "idx desc, name, item_name"
+
+ fields = ["name", "item_group", "item_name", "description"]
+ fields.extend([field for field in searchfields
+ if not field in ["name", "item_group", "description"]])
+
+ searchfields = searchfields + [field for field in [searchfield or "name", "item_code", "item_group", "item_name"]
+ if not field in searchfields]
+
+ query_filters = {
+ "disabled": 0,
+ "ifnull(end_of_life, '5050-50-50')": (">", today())
+ }
+
+ or_cond_filters = {}
+ if txt:
+ for s_field in searchfields:
+ or_cond_filters[s_field] = ("like", "%{0}%".format(txt))
+
+ barcodes = frappe.get_all("Item Barcode",
+ fields=["distinct parent as item_code"],
+ filters = {"barcode": ("like", "%{0}%".format(txt))})
+
+ barcodes = [d.item_code for d in barcodes]
+ if barcodes:
+ or_cond_filters["name"] = ("in", barcodes)
+
+ for cond in get_match_cond(doctype, as_condition=False):
+ for key, value in cond.items():
+ if key == doctype:
+ key = "name"
+
+ query_filters[key] = ("in", value)
+
+ if filters and filters.get("item_code"):
+ has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
+ if not has_variants:
+ query_filters["has_variants"] = 0
+
+ return frappe.get_all("Item",
+ fields = fields, filters=query_filters,
+ or_filters = or_cond_filters, order_by=order_by,
+ limit_start=start, limit_page_length=page_len, as_list=1)
+
+@frappe.whitelist()
+def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
+ from erpnext.manufacturing.doctype.work_order.work_order import add_variant_item
+
+ def postprocess(source, doc):
+ doc.item = item
+ doc.quantity = 1
+
+ item_data = get_item_details(item)
+ doc.update({
+ "item_name": item_data.item_name,
+ "description": item_data.description,
+ "uom": item_data.stock_uom,
+ "allow_alternative_item": item_data.allow_alternative_item
+ })
+
+ add_variant_item(variant_items, doc, source_name)
+
+ doc = get_mapped_doc('BOM', source_name, {
+ 'BOM': {
+ 'doctype': 'BOM',
+ 'validation': {
+ 'docstatus': ['=', 1]
+ }
+ },
+ 'BOM Item': {
+ 'doctype': 'BOM Item',
+ 'condition': lambda doc: doc.has_variants == 0
+ },
+ }, target_doc, postprocess)
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom_list.js b/erpnext/manufacturing/doctype/bom/bom_list.js
index 2b06ed7..94cb466 100644
--- a/erpnext/manufacturing/doctype/bom/bom_list.js
+++ b/erpnext/manufacturing/doctype/bom/bom_list.js
@@ -1,7 +1,9 @@
frappe.listview_settings['BOM'] = {
- add_fields: ["is_active", "is_default", "total_cost"],
+ add_fields: ["is_active", "is_default", "total_cost", "has_variants"],
get_indicator: function(doc) {
- if(doc.is_default) {
+ if(doc.is_active && doc.has_variants) {
+ return [__("Template"), "orange", "has_variants,=,Yes"];
+ } else if(doc.is_default) {
return [__("Default"), "green", "is_default,=,Yes"];
} else if(doc.is_active) {
return [__("Active"), "blue", "is_active,=,Yes"];
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index f094be4..e34be61 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2013-02-22 01:27:49",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
@@ -33,6 +35,7 @@
"scrap",
"qty_consumed_per_unit",
"section_break_27",
+ "has_variants",
"include_item_in_manufacturing",
"original_item"
],
@@ -57,6 +60,7 @@
"label": "Item Name"
},
{
+ "depends_on": "eval:parent.with_operations == 1",
"fieldname": "operation",
"fieldtype": "Link",
"label": "Item operation",
@@ -258,11 +262,22 @@
"label": "Original Item",
"options": "Item",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fetch_from": "item_code.has_variants",
+ "fieldname": "has_variants",
+ "fieldtype": "Check",
+ "label": "Has Variants",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
- "modified": "2019-11-22 11:38:52.087303",
+ "links": [],
+ "modified": "2020-04-09 14:30:26.535546",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 3ca851d..0350e2c 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -78,6 +78,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_hour_rate",
"fieldtype": "Currency",
"label": "Base Hour Rate(Company Currency)",
@@ -87,6 +88,7 @@
},
{
"default": "5",
+ "depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
"label": "Operating Cost(Company Currency)",
@@ -108,12 +110,12 @@
],
"idx": 1,
"istable": 1,
- "modified": "2019-07-16 22:35:55.374037",
- "modified_by": "govindsmenokee@gmail.com",
+ "modified": "2020-06-16 17:01:11.128420",
+ "modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
-}
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/manufacturing/doctype/downtime_entry/__init__.py
similarity index 100%
copy from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
copy to erpnext/manufacturing/doctype/downtime_entry/__init__.py
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.js
new file mode 100644
index 0000000..3b7f5ba
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.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('Downtime Entry', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
new file mode 100644
index 0000000..b301a9e
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
@@ -0,0 +1,141 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "naming_series:",
+ "creation": "2020-04-18 04:50:46.187638",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "workstation",
+ "operator",
+ "column_break_4",
+ "from_time",
+ "to_time",
+ "downtime",
+ "downtime_reason_section",
+ "stop_reason",
+ "column_break_9",
+ "remarks"
+ ],
+ "fields": [
+ {
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "label": "Workstation / Machine",
+ "options": "Workstation",
+ "reqd": 1
+ },
+ {
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "From Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "To Time",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Operator",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fieldname": "downtime_reason_section",
+ "fieldtype": "Section Break",
+ "label": "Downtime Reason"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "label": "Downtime",
+ "read_only": 1
+ },
+ {
+ "fieldname": "stop_reason",
+ "fieldtype": "Select",
+ "label": "Stop Reason",
+ "options": "\nExcessive machine set up time\nUnplanned machine maintenance\nOn-machine press checks\nMachine operator errors\nMachine malfunction\nElectricity down\nOther",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "label": "Remarks"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "DT-",
+ "reqd": 1
+ }
+ ],
+ "links": [],
+ "modified": "2020-05-26 22:14:54.479831",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Entry",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing User",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Manufacturing Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "workstation",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
new file mode 100644
index 0000000..56ec435
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/downtime_entry.py
@@ -0,0 +1,13 @@
+# -*- 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.utils import time_diff_in_hours
+from frappe.model.document import Document
+
+class DowntimeEntry(Document):
+ def validate(self):
+ if self.from_time and self.to_time:
+ self.downtime = time_diff_in_hours(self.to_time, self.from_time) * 60
diff --git a/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py
new file mode 100644
index 0000000..8b2a8d3
--- /dev/null
+++ b/erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.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 TestDowntimeEntry(unittest.TestCase):
+ pass
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json
index 7661fff..fba670c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.json
+++ b/erpnext/manufacturing/doctype/job_card/job_card.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2018-07-09 17:23:29.518745",
"doctype": "DocType",
@@ -264,8 +265,10 @@
{
"fetch_from": "work_order.production_item",
"fieldname": "production_item",
- "fieldtype": "Read Only",
- "label": "Production Item"
+ "fieldtype": "Link",
+ "label": "Production Item",
+ "options": "Item",
+ "read_only": 1
},
{
"fieldname": "barcode",
@@ -274,7 +277,8 @@
"read_only": 1
},
{
- "fetch_from": "work_order.item_name",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Read Only",
"label": "Item Name"
@@ -290,7 +294,8 @@
}
],
"is_submittable": 1,
- "modified": "2020-03-27 13:36:35.417502",
+ "links": [],
+ "modified": "2020-04-20 15:14:00.273441",
"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 e43b98a..c29d4ba 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -102,8 +102,11 @@
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
if (not workstation_doc.working_hours or
cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))):
- row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time,
- row.planned_start_time)
+ if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
+ row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
+ row.remaining_time_in_mins = 0.0
+ else:
+ row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, row.planned_start_time)
self.update_time_logs(row)
return
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js
index ac144e2..668e981 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.js
@@ -3,3 +3,31 @@
frappe.ui.form.on('Manufacturing Settings', {
});
+
+frappe.tour["Manufacturing Settings"] = [
+ {
+ fieldname: "material_consumption",
+ title: __("Allow Multiple Material Consumption"),
+ description: __("If ticked, multiple materials can be used for a single Work Order. This is useful if one or more time consuming products are being manufactured.")
+ },
+ {
+ fieldname: "backflush_raw_materials_based_on",
+ title: __("Backflush Raw Materials"),
+ description: __("The Stock Entry of type 'Manufacture' is known as backflush. Raw materials being consumed to manufacture finished goods is known as backflushing. <br><br> When creating Manufacture Entry, raw-material items are backflushed based on BOM of production item. If you want raw-material items to be backflushed based on Material Transfer entry made against that Work Order instead, then you can set it under this field.")
+ },
+ {
+ fieldname: "default_wip_warehouse",
+ title: __("Work In Progress Warehouse"),
+ description: __("This Warehouse will be auto-updated in the Work In Progress Warehouse field of Work Orders.")
+ },
+ {
+ fieldname: "default_fg_warehouse",
+ title: __("Finished Goods Warehouse"),
+ description: __("This Warehouse will be auto-updated in the Target Warehouse field of Work Order.")
+ },
+ {
+ fieldname: "update_bom_costs_automatically",
+ title: __("Update BOM Cost Automatically"),
+ description: __("If ticked, the BOM cost will be automatically updated based on Valuation Rate / Price List Rate / last purchase rate of raw materials.")
+ }
+];
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 64c952b..1a64bc5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -201,9 +201,9 @@
title: title,
fields: [
{
- "fieldtype": "Table MultiSelect", "label": __("Source Warehouses"),
+ "fieldtype": "Table MultiSelect", "label": __("Source Warehouses (Optional)"),
"fieldname": "warehouses", "options": "Production Plan Material Request Warehouse",
- "description": "System will pickup the materials from the selected warehouses",
+ "description": __("System will pickup the materials from the selected warehouses. If not specified, system will create material request for purchase."),
get_query: function () {
return {
filters: {
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 6cfd0ba..d7589fa 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -44,7 +44,6 @@
name: d.workstation
},
callback: function (data) {
- frappe.model.set_value(d.doctype, d.name, "base_hour_rate", data.message.hour_rate);
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
frm.events.calculate_operating_cost(frm, d);
}
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index c125571..a244f58 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -449,6 +449,32 @@
}
});
}
+ },
+
+ item_code: function(frm, cdt, cdn) {
+ let row = locals[cdt][cdn];
+
+ if (row.item_code) {
+ frappe.call({
+ method: "erpnext.stock.doctype.item.item.get_item_details",
+ args: {
+ item_code: row.item_code,
+ company: frm.doc.company
+ },
+ callback: function(r) {
+ if (r.message) {
+ frappe.model.set_value(cdt, cdn, {
+ "required_qty": 1,
+ "item_name": r.message.item_name,
+ "description": r.message.description,
+ "source_warehouse": r.message.default_warehouse,
+ "allow_alternative_item": r.message.allow_alternative_item,
+ "include_item_in_manufacturing": r.message.include_item_in_manufacturing
+ });
+ }
+ }
+ });
+ }
}
});
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 00a67a0..585a09d 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -38,11 +38,12 @@
"required_items",
"time",
"planned_start_date",
- "actual_start_date",
- "column_break_13",
"planned_end_date",
- "actual_end_date",
"expected_delivery_date",
+ "column_break_13",
+ "actual_start_date",
+ "actual_end_date",
+ "lead_time",
"operations_section",
"transfer_material_against",
"operations",
@@ -108,6 +109,8 @@
},
{
"depends_on": "eval:doc.production_item",
+ "fetch_from": "production_item.item_name",
+ "fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
@@ -281,27 +284,30 @@
"reqd": 1
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_start_date",
"fieldtype": "Datetime",
"label": "Actual Start Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
+ "allow_on_submit": 1,
"fieldname": "planned_end_date",
"fieldtype": "Datetime",
"label": "Planned End Date",
"no_copy": 1,
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
+ "allow_on_submit": 1,
"fieldname": "actual_end_date",
"fieldtype": "Datetime",
"label": "Actual End Date",
- "read_only": 1
+ "read_only_depends_on": "eval:doc.operations && doc.operations.length > 0"
},
{
"allow_on_submit": 1,
@@ -476,6 +482,13 @@
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
+ },
+ {
+ "description": "In Mins",
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "label": "Lead Time",
+ "read_only": 1
}
],
"icon": "fa fa-cogs",
@@ -483,7 +496,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2020-04-24 19:32:43.323054",
+ "modified": "2020-05-05 19:32:43.323054",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 8301f30..e2233a3 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -6,11 +6,11 @@
import json
import math
from frappe import _
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate, get_link_to_form, time_diff_in_hours
from frappe.model.document import Document
-from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict
+from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict, get_bom_item_rate
from dateutil.relativedelta import relativedelta
-from erpnext.stock.doctype.item.item import validate_end_of_life
+from erpnext.stock.doctype.item.item import validate_end_of_life, get_item_defaults
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
from erpnext.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
@@ -279,7 +279,7 @@
if enable_capacity_planning and job_card_doc:
row.planned_start_time = job_card_doc.time_logs[-1].from_time
row.planned_end_time = job_card_doc.time_logs[-1].to_time
- print(row.planned_start_time, original_start_time, plan_days)
+
if date_diff(row.planned_start_time, original_start_time) > plan_days:
frappe.message_log.pop()
frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.")
@@ -437,8 +437,6 @@
frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'"))
def set_actual_dates(self):
- self.actual_start_date = None
- self.actual_end_date = None
if self.get("operations"):
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
if actual_start_dates:
@@ -447,6 +445,27 @@
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
if actual_end_dates:
self.actual_end_date = max(actual_end_dates)
+ else:
+ data = frappe.get_all("Stock Entry",
+ fields = ["timestamp(posting_date, posting_time) as posting_datetime"],
+ filters = {
+ "work_order": self.name,
+ "purpose": ("in", ["Material Transfer for Manufacture", "Manufacture"])
+ }
+ )
+
+ if data and len(data):
+ dates = [d.posting_datetime for d in data]
+ self.actual_start_date = min(dates)
+
+ if self.status == "Completed":
+ self.actual_end_date = max(dates)
+
+ self.set_lead_time()
+
+ def set_lead_time(self):
+ if self.actual_start_date and self.actual_end_date:
+ self.lead_time = flt(time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60)
def delete_job_card(self):
for d in frappe.get_all("Job Card", ["name"], {"work_order": self.name}):
@@ -522,6 +541,8 @@
# For instance in BOM Explosion Item child table, the items coming from sub assembly items
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
self.append('required_items', {
+ 'rate': item.rate,
+ 'amount': item.amount,
'operation': item.operation,
'item_code': item.item_code,
'item_name': item.item_name,
@@ -618,9 +639,10 @@
filters = filters, fields = ['operation'], as_list=1)
@frappe.whitelist()
-def get_item_details(item, project = None):
+def get_item_details(item, project = None, skip_bom_info=False):
res = frappe.db.sql("""
- select stock_uom, description
+ select stock_uom, description, item_name, allow_alternative_item,
+ include_item_in_manufacturing
from `tabItem`
where disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
@@ -631,6 +653,7 @@
return {}
res = res[0]
+ if skip_bom_info: return res
filters = {"item": item, "is_default": 1}
@@ -662,7 +685,7 @@
return res
@frappe.whitelist()
-def make_work_order(bom_no, item, qty=0, project=None):
+def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
@@ -677,8 +700,44 @@
wo_doc.qty = flt(qty)
wo_doc.get_items_and_operations_from_bom()
+ if variant_items:
+ add_variant_item(variant_items, wo_doc, bom_no, "required_items")
+
return wo_doc
+def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
+ if isinstance(variant_items, string_types):
+ variant_items = json.loads(variant_items)
+
+ for item in variant_items:
+ args = frappe._dict({
+ "item_code": item.get("varint_item_code"),
+ "required_qty": item.get("qty"),
+ "qty": item.get("qty"), # for bom
+ "source_warehouse": item.get("source_warehouse"),
+ "operation": item.get("operation")
+ })
+
+ bom_doc = frappe.get_cached_doc("BOM", bom_no)
+ item_data = get_item_details(args.item_code, skip_bom_info=True)
+ args.update(item_data)
+
+ args["rate"] = get_bom_item_rate({
+ "item_code": args.get("item_code"),
+ "qty": args.get("required_qty"),
+ "uom": args.get("stock_uom"),
+ "stock_uom": args.get("stock_uom"),
+ "conversion_factor": 1
+ }, bom_doc)
+
+ if not args.source_warehouse:
+ args["source_warehouse"] = get_item_defaults(item.get("varint_item_code"),
+ wo_doc.company).default_warehouse
+
+ args["amount"] = flt(args.get("required_qty")) * flt(args.get("rate"))
+ args["uom"] = item_data.stock_uom
+ wo_doc.append(table_name, args)
+
@frappe.whitelist()
def check_if_scrap_warehouse_mandatory(bom_no):
res = {"set_scrap_wh_mandatory": False }
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
index 4442162..3acf572 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
@@ -1,526 +1,144 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-04-18 07:38:26.314642",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-04-18 07:38:26.314642",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "operation",
+ "item_code",
+ "source_warehouse",
+ "column_break_3",
+ "item_name",
+ "description",
+ "allow_alternative_item",
+ "include_item_in_manufacturing",
+ "qty_section",
+ "required_qty",
+ "rate",
+ "amount",
+ "column_break_11",
+ "transferred_qty",
+ "consumed_qty",
+ "available_qty_at_source_warehouse",
+ "available_qty_at_wip_warehouse"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "operation",
- "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": "Operation",
- "length": 0,
- "no_copy": 0,
- "options": "Operation",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "label": "Operation",
+ "options": "Operation"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_code",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Item Code",
- "length": 0,
- "no_copy": 0,
- "options": "Item",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Item Code",
+ "options": "Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "source_warehouse",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 1,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Source Warehouse",
- "length": 0,
- "no_copy": 0,
- "options": "Warehouse",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "source_warehouse",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Source Warehouse",
+ "options": "Warehouse"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "item_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Item Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "label": "Item Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Description",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "label": "Description",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "qty_section",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "qty_section",
+ "fieldtype": "Section Break",
+ "label": "Qty"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "required_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Required Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Required Qty"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!parent.skip_transfer",
- "fieldname": "transferred_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Transferred Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!parent.skip_transfer",
+ "fieldname": "transferred_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Transferred Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "allow_alternative_item",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Allow Alternative Item",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "allow_alternative_item",
+ "fieldtype": "Check",
+ "label": "Allow Alternative Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "include_item_in_manufacturing",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Include Item In Manufacturing",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "include_item_in_manufacturing",
+ "fieldtype": "Check",
+ "label": "Include Item In Manufacturing"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!parent.skip_transfer",
- "fieldname": "consumed_qty",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Consumed Qty",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "depends_on": "eval:!parent.skip_transfer",
+ "fieldname": "consumed_qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Consumed Qty",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "available_qty_at_source_warehouse",
- "fieldtype": "Float",
- "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": "Available Qty at Source Warehouse",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "available_qty_at_source_warehouse",
+ "fieldtype": "Float",
+ "label": "Available Qty at Source Warehouse",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "available_qty_at_wip_warehouse",
- "fieldtype": "Float",
- "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": "Available Qty at WIP Warehouse",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "available_qty_at_wip_warehouse",
+ "fieldtype": "Float",
+ "label": "Available Qty at WIP Warehouse",
+ "read_only": 1
+ },
+ {
+ "fieldname": "rate",
+ "fieldtype": "Currency",
+ "label": "Rate",
+ "read_only": 1
+ },
+ {
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "label": "Amount",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-11-20 19:04:38.508839",
- "modified_by": "Administrator",
- "module": "Manufacturing",
- "name": "Work Order Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-04-13 18:46:32.966416",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..952d1f0
--- /dev/null
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,57 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 12:51:42.744570",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create Raw Materials"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ },
+ {
+ "step": "Explore Manufacturing Settings"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
new file mode 100644
index 0000000..50584e1
--- /dev/null
+++ b/erpnext/manufacturing/onboarding/manufacturing/manufacturing.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Item Manager"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-05 16:37:08.238935",
+ "docstatus": 0,
+ "doctype": "Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-12 16:22:07.050224",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Manufacturing",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Introduction to Manufacturing"
+ },
+ {
+ "step": "Warehouse"
+ },
+ {
+ "step": "Workstation"
+ },
+ {
+ "step": "Operation"
+ },
+ {
+ "step": "Create Product"
+ },
+ {
+ "step": "Create BOM"
+ },
+ {
+ "step": "Work Order"
+ }
+ ],
+ "subtitle": "Products, Raw Materials, BOM, Work Order and more.",
+ "success_message": "Manufacturing module is all setup!",
+ "title": "Let's Setup Manufacturing Module",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
new file mode 100644
index 0000000..84b4088
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:41:20.239696",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:31.315686",
+ "modified_by": "Administrator",
+ "name": "Create BOM",
+ "owner": "Administrator",
+ "reference_document": "BOM",
+ "show_full_form": 1,
+ "title": "Create a BOM (Bill of Material)",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
new file mode 100644
index 0000000..0ffa301
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-05 16:42:31.476275",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 1,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:59.010439",
+ "modified_by": "Administrator",
+ "name": "Create Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Finished Good",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
new file mode 100644
index 0000000..0764f2e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 11:53:17.295372",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 11:53:25.147837",
+ "modified_by": "Administrator",
+ "name": "Create Raw Materials",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create Raw Materials",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
new file mode 100644
index 0000000..7ef202e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
@@ -0,0 +1,20 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-19 11:55:11.378374",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-26 20:28:03.558199",
+ "modified_by": "Administrator",
+ "name": "Explore Manufacturing Settings",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Explore Manufacturing Settings",
+ "validate_action": 0,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
new file mode 100644
index 0000000..eb7ab3a
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/introduction_to_manufacturing/introduction_to_manufacturing.json
@@ -0,0 +1,20 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-05 16:40:23.676406",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 19:11:57.152883",
+ "modified_by": "Administrator",
+ "name": "Introduction to Manufacturing",
+ "owner": "Administrator",
+ "reference_document": "Manufacturing Settings",
+ "show_full_form": 0,
+ "title": "Manufacturing Settings",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json
new file mode 100644
index 0000000..b532e67
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/operation/operation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:31.706756",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:41.642754",
+ "modified_by": "Administrator",
+ "name": "Operation",
+ "owner": "Administrator",
+ "reference_document": "Operation",
+ "show_full_form": 0,
+ "title": "Create a Operation",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
new file mode 100644
index 0000000..e23bd33
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/warehouse/warehouse.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:13:34.014554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:13.766712",
+ "modified_by": "Administrator",
+ "name": "Warehouse",
+ "owner": "Administrator",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Create a Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
new file mode 100644
index 0000000..c63363e
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:15:56.084682",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:51:38.133150",
+ "modified_by": "Administrator",
+ "name": "Work Order",
+ "owner": "Administrator",
+ "reference_document": "Work Order",
+ "show_full_form": 1,
+ "title": "Create a Work Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
new file mode 100644
index 0000000..df244bb
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 16:14:14.930214",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 12:50:33.938176",
+ "modified_by": "Administrator",
+ "name": "Workstation",
+ "owner": "Administrator",
+ "reference_document": "Workstation",
+ "show_full_form": 0,
+ "title": "Create a Workstation / Machine",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/manufacturing/report/bom_operations_time/__init__.py
similarity index 100%
copy from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
copy to erpnext/manufacturing/report/bom_operations_time/__init__.py
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
similarity index 65%
rename from erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js
rename to erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
index 24c9592..7468e34 100644
--- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.js
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -1,8 +1,9 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
+/* eslint-disable */
-frappe.query_reports["Purchase Order Items To Be Billed"] = {
+frappe.query_reports["BOM Operations Time"] = {
"filters": [
]
-}
+};
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
new file mode 100644
index 0000000..665c5b9
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-03 01:41:20.862521",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-03 01:41:20.862521",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Operations Time",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "BOM",
+ "report_name": "BOM Operations Time",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
new file mode 100644
index 0000000..e7d9265
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ data = get_data(filters)
+ columns = get_columns(filters)
+ return columns, data
+
+def get_data(filters):
+ data = []
+
+ bom_data = []
+ for d in frappe.db.sql("""
+ SELECT
+ bom.name, bom.item, bom.item_name, bom.uom,
+ bomps.operation, bomps.workstation, bomps.time_in_mins
+ FROM `tabBOM` bom, `tabBOM Operation` bomps
+ WHERE
+ bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
+ """, as_dict=1):
+ row = get_args()
+ if d.name not in bom_data:
+ bom_data.append(d.name)
+ row.update(d)
+ else:
+ row.update({
+ "operation": d.operation,
+ "workstation": d.workstation,
+ "time_in_mins": d.time_in_mins
+ })
+
+ data.append(row)
+
+ used_as_subassembly_items = get_bom_count(bom_data)
+
+ for d in data:
+ d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+
+ return data
+
+def get_bom_count(bom_data):
+ data = frappe.get_all("BOM Item",
+ fields=["count(name) as count", "bom_no"],
+ filters= {"bom_no": ("in", bom_data)}, group_by = "bom_no")
+
+ bom_count = {}
+ for d in data:
+ bom_count.setdefault(d.bom_no, d.count)
+
+ return bom_count
+
+def get_args():
+ return frappe._dict({
+ "name": "",
+ "item": "",
+ "item_name": "",
+ "uom": ""
+ })
+
+def get_columns(filters):
+ return [{
+ "label": _("BOM ID"),
+ "options": "BOM",
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("BOM Item Code"),
+ "options": "Item",
+ "fieldname": "item",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 110
+ }, {
+ "label": _("UOM"),
+ "options": "UOM",
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "width": 140
+ }, {
+ "label": _("Operation"),
+ "options": "Operation",
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "width": 120
+ }, {
+ "label": _("Workstation"),
+ "options": "Workstation",
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Time (In Mins)"),
+ "fieldname": "time_in_mins",
+ "fieldtype": "Int",
+ "width": 140
+ }, {
+ "label": _("Sub-assembly BOM Count"),
+ "fieldname": "used_as_subassembly_items",
+ "fieldtype": "Int",
+ "width": 180
+ }]
+
+
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/manufacturing/report/downtime_analysis/__init__.py
similarity index 100%
copy from erpnext/hr/report/department_analytics/__init__.py
copy to erpnext/manufacturing/report/downtime_analysis/__init__.py
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
new file mode 100644
index 0000000..ff32dbe
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Downtime Analysis"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Datetime",
+ default: frappe.datetime.now_datetime(),
+ reqd: 1,
+ },
+ {
+ label: __("Machine"),
+ fieldname: "workstation",
+ fieldtype: "Link",
+ options: "Workstation"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
new file mode 100644
index 0000000..5edc778
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 1,
+ "creation": "2020-04-20 18:26:04.345289",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 18:26:04.345289",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Downtime Analysis",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Downtime Entry",
+ "report_name": "Downtime Analysis",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
new file mode 100644
index 0000000..093309a
--- /dev/null
+++ b/erpnext/manufacturing/report/downtime_analysis/downtime_analysis.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import flt
+from frappe import _
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {}
+
+ fields = ["name", "workstation", "operator", "from_time", "to_time", "downtime", "stop_reason", "remarks"]
+
+ query_filters["from_time"] = (">=", filters.get("from_date"))
+ query_filters["to_time"] = ("<=", filters.get("to_date"))
+
+ if filters.get("workstation"):
+ query_filters["workstation"] = filters.get("workstation")
+
+ data = frappe.get_all("Downtime Entry", fields= fields, filters=query_filters) or []
+ for d in data:
+ if d.downtime:
+ d.downtime = d.downtime / 60
+
+ return data
+
+def get_chart_data(data, columns):
+ labels = sorted(list(set([d.workstation for d in data])))
+
+ workstation_wise_data = {}
+ for d in data:
+ if d.workstation not in workstation_wise_data:
+ workstation_wise_data[d.workstation] = 0
+
+ workstation_wise_data[d.workstation] += flt(d.downtime, 2)
+
+ datasets = []
+ for label in labels:
+ datasets.append(workstation_wise_data.get(label, 0))
+
+ chart = {
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {"name": "Machine Downtime", "values": datasets}
+ ]
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def get_columns(filters):
+ return [
+ {
+ "label": _("ID"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Downtime Entry",
+ "width": 100
+ },
+ {
+ "label": _("Machine"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 100
+ },
+ {
+ "label": _("Operator"),
+ "fieldname": "operator",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 130
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 160
+ },
+ {
+ "label": _("Downtime (In Hours)"),
+ "fieldname": "downtime",
+ "fieldtype": "Float",
+ "width": 150
+ },
+ {
+ "label": _("Stop Reason"),
+ "fieldname": "stop_reason",
+ "fieldtype": "Data",
+ "width": 220
+ },
+ {
+ "label": _("Remarks"),
+ "fieldname": "remarks",
+ "fieldtype": "Text",
+ "width": 100
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/__init__.py
similarity index 100%
copy from erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py
copy to erpnext/manufacturing/report/exponential_smoothing_forecasting/__init__.py
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js
new file mode 100644
index 0000000..123a82a
--- /dev/null
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js
@@ -0,0 +1,97 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Exponential Smoothing Forecasting"] = {
+ "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",
+ "default": frappe.datetime.get_today(),
+ "reqd": 1
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.get_today(), 12),
+ "reqd": 1
+ },
+ {
+ "fieldname":"based_on_document",
+ "label": __("Based On Document"),
+ "fieldtype": "Select",
+ "options": ["Sales Order", "Delivery Note", "Quotation"],
+ "default": "Sales Order",
+ "reqd": 1
+ },
+ {
+ "fieldname":"based_on_field",
+ "label": __("Based On"),
+ "fieldtype": "Select",
+ "options": ["Qty", "Amount"],
+ "default": "Qty",
+ "reqd": 1
+ },
+ {
+ "fieldname":"no_of_years",
+ "label": __("Based On Data ( in years )"),
+ "fieldtype": "Select",
+ "options": [3, 6, 9],
+ "default": 3,
+ "reqd": 1
+ },
+ {
+ "fieldname": "periodicity",
+ "label": __("Periodicity"),
+ "fieldtype": "Select",
+ "options": [
+ { "value": "Monthly", "label": __("Monthly") },
+ { "value": "Quarterly", "label": __("Quarterly") },
+ { "value": "Half-Yearly", "label": __("Half-Yearly") },
+ { "value": "Yearly", "label": __("Yearly") }
+ ],
+ "default": "Yearly",
+ "reqd": 1
+ },
+ {
+ "fieldname":"smoothing_constant",
+ "label": __("Smoothing Constant"),
+ "fieldtype": "Select",
+ "options": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
+ "reqd": 1,
+ "default": 0.3
+ },
+ {
+ "fieldname":"item_code",
+ "label": __("Item Code"),
+ "fieldtype": "Link",
+ "options": "Item"
+ },
+ {
+ "fieldname":"warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ get_query: () => {
+ var company = frappe.query_report.get_filter_value('company');
+ if (company) {
+ return {
+ filters: {
+ 'company': company
+ }
+ };
+ }
+ }
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.json b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.json
new file mode 100644
index 0000000..5092ef4
--- /dev/null
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.json
@@ -0,0 +1,40 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-05-15 05:18:55.838030",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-05-15 05:18:55.838030",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Exponential Smoothing Forecasting",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Order",
+ "report_name": "Exponential Smoothing Forecasting",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Sales Manager"
+ },
+ {
+ "role": "Sales User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
new file mode 100644
index 0000000..2ca9f16
--- /dev/null
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -0,0 +1,242 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, erpnext
+from frappe import _
+from frappe.utils import flt, nowdate, add_years, cint, getdate
+from erpnext.accounts.report.financial_statements import get_period_list
+from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+def execute(filters=None):
+ return ForecastingReport(filters).execute_report()
+
+class ExponentialSmoothingForecast(object):
+ def forecast_future_data(self):
+ for key, value in self.period_wise_data.items():
+ forecast_data = []
+ for period in self.period_list:
+ forecast_key = "forecast_" + period.key
+
+ if value.get(period.key) and not forecast_data:
+ value[forecast_key] = flt(value.get("avg", 0)) or flt(value.get(period.key))
+
+ elif forecast_data:
+ previous_period_data = forecast_data[-1]
+ value[forecast_key] = (previous_period_data[1] +
+ flt(self.filters.smoothing_constant) * (
+ flt(previous_period_data[0]) - flt(previous_period_data[1])
+ )
+ )
+
+ if value.get(forecast_key):
+ # will be use to forecaset next period
+ forecast_data.append([value.get(period.key), value.get(forecast_key)])
+
+class ForecastingReport(ExponentialSmoothingForecast):
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.data = []
+ self.doctype = self.filters.based_on_document
+ self.child_doctype = self.doctype + " Item"
+ self.based_on_field = ("qty"
+ if self.filters.based_on_field == "Qty" else "amount")
+ self.fieldtype = "Float" if self.based_on_field == "qty" else "Currency"
+ self.company_currency = erpnext.get_company_currency(self.filters.company)
+
+ def execute_report(self):
+ self.prepare_periodical_data()
+ self.forecast_future_data()
+ self.prepare_final_data()
+ self.add_total()
+
+ columns = self.get_columns()
+ charts = self.get_chart_data()
+ summary_data = self.get_summary_data()
+
+ return columns, self.data, None, charts, summary_data
+
+ def prepare_periodical_data(self):
+ self.period_wise_data = {}
+
+ from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
+ self.period_list = get_period_list(from_date, self.filters.to_date,
+ from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True)
+
+ order_data = self.get_data_for_forecast() or []
+
+ for entry in order_data:
+ key = (entry.item_code, entry.warehouse)
+ if key not in self.period_wise_data:
+ self.period_wise_data[key] = entry
+
+ period_data = self.period_wise_data[key]
+ for period in self.period_list:
+ # check if posting date is within the period
+ if (entry.posting_date >= period.from_date and entry.posting_date <= period.to_date):
+ period_data[period.key] = period_data.get(period.key, 0.0) + flt(entry.get(self.based_on_field))
+
+ for key, value in self.period_wise_data.items():
+ list_of_period_value = [value.get(p.key, 0) for p in self.period_list]
+
+ if list_of_period_value:
+ total_qty = [1 for d in list_of_period_value if d]
+ if total_qty:
+ value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty))
+
+ def get_data_for_forecast(self):
+ cond = ""
+ if self.filters.item_code:
+ cond = " AND soi.item_code = %s" %(frappe.db.escape(self.filters.item_code))
+
+ warehouses = []
+ if self.filters.warehouse:
+ warehouses = get_child_warehouses(self.filters.warehouse)
+ cond += " AND soi.warehouse in ({})".format(','.join(['%s'] * len(warehouses)))
+
+ input_data = [self.filters.from_date, self.filters.company]
+ if warehouses:
+ input_data.extend(warehouses)
+
+ date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date"
+
+ return frappe.db.sql("""
+ SELECT
+ so.{date_field} as posting_date, soi.item_code, soi.warehouse,
+ soi.item_name, soi.stock_qty as qty, soi.base_amount as amount
+ FROM
+ `tab{doc}` so, `tab{child_doc}` soi
+ WHERE
+ so.docstatus = 1 AND so.name = soi.parent AND
+ so.{date_field} < %s AND so.company = %s {cond}
+ """.format(doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond),
+ tuple(input_data), as_dict=1)
+
+ def prepare_final_data(self):
+ self.data = []
+
+ if not self.period_wise_data: return
+
+ for key in self.period_wise_data:
+ self.data.append(self.period_wise_data.get(key))
+
+ def add_total(self):
+ if not self.data: return
+
+ total_row = {
+ "item_code": _(frappe.bold("Total Quantity"))
+ }
+
+ for value in self.data:
+ for period in self.period_list:
+ forecast_key = "forecast_" + period.key
+ if forecast_key not in total_row:
+ total_row.setdefault(forecast_key, 0.0)
+
+ if period.key not in total_row:
+ total_row.setdefault(period.key, 0.0)
+
+ total_row[forecast_key] += value.get(forecast_key, 0.0)
+ total_row[period.key] += value.get(period.key, 0.0)
+
+ self.data.append(total_row)
+
+ def get_columns(self):
+ columns = [{
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 130
+ }]
+
+ width = 180 if self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"] else 100
+ for period in self.period_list:
+ if (self.filters.periodicity in ['Yearly', "Half-Yearly", "Quarterly"]
+ or period.from_date >= getdate(self.filters.from_date)):
+
+ forecast_key = period.key
+ label = _(period.label)
+ if period.from_date >= getdate(self.filters.from_date):
+ forecast_key = 'forecast_' + period.key
+ label = _(period.label) + " " + _("(Forecast)")
+
+ columns.append({
+ "label": label,
+ "fieldname": forecast_key,
+ "fieldtype": self.fieldtype,
+ "width": width,
+ "default": 0.0
+ })
+
+ return columns
+
+ def get_chart_data(self):
+ if not self.data: return
+
+ labels = []
+ self.total_demand = []
+ self.total_forecast = []
+ self.total_history_forecast = []
+ self.total_future_forecast = []
+
+ for period in self.period_list:
+ forecast_key = "forecast_" + period.key
+
+ labels.append(_(period.label))
+
+ if period.from_date < getdate(self.filters.from_date):
+ self.total_demand.append(self.data[-1].get(period.key, 0))
+ self.total_history_forecast.append(self.data[-1].get(forecast_key, 0))
+ else:
+ self.total_future_forecast.append(self.data[-1].get(forecast_key, 0))
+
+ self.total_forecast.append(self.data[-1].get(forecast_key, 0))
+
+ return {
+ "data": {
+ "labels": labels,
+ "datasets": [
+ {
+ "name": "Demand",
+ "values": self.total_demand
+ },
+ {
+ "name": "Forecast",
+ "values": self.total_forecast
+ }
+ ]
+ },
+ "type": "line"
+ }
+
+ def get_summary_data(self):
+ if not self.data: return
+
+ return [
+ {
+ "value": sum(self.total_demand),
+ "label": _("Total Demand (Past Data)"),
+ "currency": self.company_currency,
+ "datatype": self.fieldtype
+ },
+ {
+ "value": sum(self.total_history_forecast),
+ "label": _("Total Forecast (Past Data)"),
+ "currency": self.company_currency,
+ "datatype": self.fieldtype
+ },
+ {
+ "value": sum(self.total_future_forecast),
+ "indicator": "Green",
+ "label": _("Total Forecast (Future Data)"),
+ "currency": self.company_currency,
+ "datatype": self.fieldtype
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/manufacturing/report/job_card_summary/__init__.py
similarity index 100%
copy from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
copy to erpnext/manufacturing/report/job_card_summary/__init__.py
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
new file mode 100644
index 0000000..bd68db1
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
@@ -0,0 +1,73 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Job Card Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ fieldname: "fiscal_year",
+ label: __("Fiscal Year"),
+ fieldtype: "Link",
+ options: "Fiscal Year",
+ default: frappe.defaults.get_user_default("fiscal_year"),
+ reqd: 1,
+ on_change: function(query_report) {
+ var fiscal_year = query_report.get_values().fiscal_year;
+ if (!fiscal_year) {
+ return;
+ }
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ from_date: fy.year_start_date,
+ to_date: fy.year_end_date
+ });
+ });
+ }
+ },
+ {
+ label: __("From Posting Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.defaults.get_user_default("year_start_date"),
+ reqd: 1
+ },
+ {
+ label: __("To Posting Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.defaults.get_user_default("year_end_date"),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Open", "Work In Progress", "Completed", "On Hold"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
new file mode 100644
index 0000000..9f08fc3
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
@@ -0,0 +1,34 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-20 12:00:21.436619",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-20 12:00:21.436619",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Job Card Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Job Card",
+ "report_name": "Job Card Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
new file mode 100644
index 0000000..b1bff35
--- /dev/null
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -0,0 +1,204 @@
+# 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 _
+from frappe.utils import getdate, flt
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {
+ "docstatus": ("<", 2),
+ "posting_date": ("between", [filters.from_date, filters.to_date])
+ }
+
+ fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date",
+ "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"]
+
+ for field in ["work_order", "workstation", "operation", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ data = frappe.get_all("Job Card",
+ fields= fields, filters=query_filters)
+
+ if not data: return []
+
+ job_cards = [d.name for d in data]
+
+ job_card_time_filter = {
+ "docstatus": ("<", 2),
+ "parent": ("in", job_cards),
+ }
+
+ job_card_time_details = {}
+ for job_card_data in frappe.get_all("Job Card Time Log",
+ fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"],
+ filters=job_card_time_filter, group_by="parent", debug=1):
+ job_card_time_details[job_card_data.parent] = job_card_data
+
+ res = []
+ for d in data:
+ if d.status != "Completed":
+ d.status = "Open"
+
+ if job_card_time_details.get(d.name):
+ d.from_time = job_card_time_details.get(d.name).from_time
+ d.to_time = job_card_time_details.get(d.name).to_time
+
+ res.append(d)
+
+ return res
+
+def get_chart_data(job_card_details, filters):
+ labels, periodic_data = prepare_chart_data(job_card_details, filters)
+
+ open_job_cards, completed = [], []
+ datasets = []
+
+ for d in labels:
+ open_job_cards.append(periodic_data.get("Open").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Open", "values": open_job_cards})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar"
+ }
+
+ return chart
+
+def prepare_chart_data(job_card_details, filters):
+ labels = []
+
+ periodic_data = {
+ "Open": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ for d in job_card_details:
+ if getdate(d.posting_date) > from_date and getdate(d.posting_date) <= end_date:
+ status = "Completed" if d.status == "Completed" else "Open"
+
+ if periodic_data.get(status).get(period):
+ periodic_data[status][period] += 1
+ else:
+ periodic_data[status][period] = 1
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Job Card",
+ "width": 100
+ },
+ {
+ "label": _("Posting Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Work Order"),
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 110
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Workstation"),
+ "fieldname": "workstation",
+ "fieldtype": "Link",
+ "options": "Workstation",
+ "width": 110
+ },
+ {
+ "label": _("Operation"),
+ "fieldname": "operation",
+ "fieldtype": "Link",
+ "options": "Operation",
+ "width": 110
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Total Completed Qty"),
+ "fieldname": "total_completed_qty",
+ "fieldtype": "Float",
+ "width": 120
+ },
+ {
+ "label": _("From Time"),
+ "fieldname": "from_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("To Time"),
+ "fieldname": "to_time",
+ "fieldtype": "Datetime",
+ "width": 120
+ },
+ {
+ "label": _("Time Required (In Mins)"),
+ "fieldname": "total_time_in_mins",
+ "fieldtype": "Float",
+ "width": 100
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_analytics/production_analytics.py b/erpnext/manufacturing/report/production_analytics/production_analytics.py
index 7447a1f..79af8a1 100644
--- a/erpnext/manufacturing/report/production_analytics/production_analytics.py
+++ b/erpnext/manufacturing/report/production_analytics/production_analytics.py
@@ -55,32 +55,27 @@
if d.status == 'Completed':
if getdate(d.actual_end_date) < getdate(from_date) or getdate(d.modified) < getdate(from_date):
periodic_data = update_periodic_data(periodic_data, "Completed", period)
-
elif getdate(d.actual_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Pending", period)
-
elif getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
elif d.status == 'In Process':
if getdate(d.actual_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Pending", period)
-
elif getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
elif d.status == 'Not Started':
if getdate(d.planned_start_date) < getdate(from_date) :
periodic_data = update_periodic_data(periodic_data, "Overdue", period)
-
else:
periodic_data = update_periodic_data(periodic_data, "Not Started", period)
+
return periodic_data
def update_periodic_data(periodic_data, status, period):
@@ -148,4 +143,3 @@
-
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/manufacturing/report/production_planning_report/__init__.py
similarity index 100%
copy from erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py
copy to erpnext/manufacturing/report/production_planning_report/__init__.py
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
new file mode 100644
index 0000000..675b8a1
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js
@@ -0,0 +1,111 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Planning Report"] = {
+ "filters": [
+ {
+ "fieldname":"company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_user_default("Company")
+ },
+ {
+ "fieldname":"based_on",
+ "label": __("Based On"),
+ "fieldtype": "Select",
+ "options": ["Sales Order", "Material Request", "Work Order"],
+ "default": "Sales Order",
+ "reqd": 1,
+ on_change: function() {
+ let filters = frappe.query_report.filters;
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ let options = {
+ "Sales Order": ["Delivery Date", "Total Amount"],
+ "Material Request": ["Required Date"],
+ "Work Order": ["Planned Start Date"]
+ }
+
+ filters.forEach(d => {
+ if (d.fieldname == "order_by") {
+ d.df.options = options[based_on];
+ d.set_input(d.df.options)
+ }
+ });
+
+ frappe.query_report.refresh();
+ }
+ },
+ {
+ "fieldname":"docnames",
+ "label": __("Document Name"),
+ "fieldtype": "MultiSelectList",
+ "options": "Sales Order",
+ "get_data": function(txt) {
+ if (!frappe.query_report.filters) return;
+
+ let based_on = frappe.query_report.get_filter_value('based_on');
+ if (!based_on) return;
+
+ return frappe.db.get_link_options(based_on, txt);
+ },
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "docstatus": 1,
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"raw_material_warehouse",
+ "label": __("Raw Material Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "get_query": function() {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ "company": company
+ }
+ };
+ }
+ },
+ {
+ "fieldname":"order_by",
+ "label": __("Order By"),
+ "fieldtype": "Select",
+ "options": ["Delivery Date", "Total Amount"],
+ "default": "Delivery Date"
+ },
+ {
+ "fieldname":"include_subassembly_raw_materials",
+ "label": __("Include Sub-assembly Raw Materials"),
+ "fieldtype": "Check",
+ "depends_on": "eval: doc.based_on != 'Work Order'",
+ "default": 0
+ },
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "production_item_name" && data && data.qty_to_manufacture > data.available_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ if (column.fieldname == "production_item" && !data.name ) {
+ value = "";
+ }
+
+ if (column.fieldname == "raw_material_name" && data && data.required_qty > data.allotted_qty ) {
+ value = `<div style="color:red">${value}</div>`;
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.json b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
new file mode 100644
index 0000000..f37dad3
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-03-06 11:37:43.180095",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "",
+ "modified": "2020-03-06 11:38:05.789851",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Planning Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Production Planning Report",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
new file mode 100644
index 0000000..5ac3923
--- /dev/null
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -0,0 +1,374 @@
+# 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 _
+from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+# and bom_no is not null and bom_no !=''
+
+mapper = {
+ "Sales Order": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse,
+ `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """,
+ "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty
+ and `tabSales Order`.per_delivered < 100.0"""
+ },
+ "Material Request": {
+ "fields": """ item_code as production_item, item_name as production_item_name, stock_uom,
+ stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse,
+ `tabMaterial Request Item`.schedule_date """,
+ "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100
+ and `tabMaterial Request`.material_request_type = 'Manufacture' """
+ },
+ "Work Order": {
+ "fields": """ production_item, item_name as production_item_name, planned_start_date,
+ stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """,
+ "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')"
+ },
+}
+
+order_mapper = {
+ "Sales Order": {
+ "Delivery Date": "`tabSales Order Item`.delivery_date asc",
+ "Total Amount": "`tabSales Order`.base_grand_total desc"
+ },
+ "Material Request": {
+ "Required Date": "`tabMaterial Request Item`.schedule_date asc"
+ },
+ "Work Order": {
+ "Planned Start Date": "planned_start_date asc"
+ }
+}
+
+def execute(filters=None):
+ return ProductionPlanReport(filters).execute_report()
+
+class ProductionPlanReport(object):
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.raw_materials_dict = {}
+ self.data = []
+
+ def execute_report(self):
+ self.get_open_orders()
+ self.get_raw_materials()
+ self.get_item_details()
+ self.get_bin_details()
+ self.get_purchase_details()
+ self.prepare_data()
+ self.get_columns()
+
+ return self.columns, self.data
+
+ def get_open_orders(self):
+ doctype = ("`tabWork Order`" if self.filters.based_on == "Work Order"
+ else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on))
+
+ filters = mapper.get(self.filters.based_on)["filters"]
+ filters = self.prepare_other_conditions(filters, self.filters.based_on)
+ order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by])
+
+ self.orders = frappe.db.sql(""" SELECT {fields} from {doctype}
+ WHERE {filters} {order_by}""".format(
+ doctype = doctype,
+ filters = filters,
+ order_by = order_by,
+ fields = mapper.get(self.filters.based_on)["fields"]
+ ), tuple(self.filters.docnames), as_dict=1)
+
+ def prepare_other_conditions(self, filters, doctype):
+ if self.filters.docnames:
+ field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype)
+ filters += " and %s in (%s)" % (field, ','.join(['%s'] * len(self.filters.docnames)))
+
+ if doctype != "Work Order":
+ filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype)
+
+ if self.filters.company:
+ filters += " and `tab%s`.company = %s" %(doctype, frappe.db.escape(self.filters.company))
+
+ return filters
+
+ def get_raw_materials(self):
+ if not self.orders: return
+ self.warehouses = [d.warehouse for d in self.orders]
+ self.item_codes = [d.production_item for d in self.orders]
+
+ if self.filters.based_on == "Work Order":
+ work_orders = [d.name for d in self.orders]
+
+ raw_materials = frappe.get_all("Work Order Item",
+ fields=["parent", "item_code", "item_name as raw_material_name",
+ "source_warehouse as warehouse", "required_qty"],
+ filters = {"docstatus": 1, "parent": ("in", work_orders), "source_warehouse": ("!=", "")}) or []
+ self.warehouses.extend([d.source_warehouse for d in raw_materials])
+
+ else:
+ bom_nos = []
+
+ for d in self.orders:
+ bom_no = d.bom_no or frappe.get_cached_value("Item", d.production_item, "default_bom")
+
+ if not d.bom_no:
+ d.bom_no = bom_no
+
+ bom_nos.append(bom_no)
+
+ bom_doctype = ("BOM Explosion Item"
+ if self.filters.include_subassembly_raw_materials else "BOM Item")
+
+ qty_field = ("qty_consumed_per_unit"
+ 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
+ FROM
+ `tabBOM` as bom, `tab{1}` as bom_item
+ WHERE
+ bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1
+ """.format(qty_field, bom_doctype, ','.join(["%s"] * len(bom_nos))), tuple(bom_nos), as_dict=1)
+
+ if not raw_materials: return
+
+ self.item_codes.extend([d.item_code for d in raw_materials])
+
+ for d in raw_materials:
+ if d.parent not in self.raw_materials_dict:
+ self.raw_materials_dict.setdefault(d.parent, [])
+
+ rows = self.raw_materials_dict[d.parent]
+ rows.append(d)
+
+ def get_item_details(self):
+ if not (self.orders and self.item_codes): return
+
+ self.item_details = {}
+ for d in frappe.get_all("Item Default", fields = ["parent", "default_warehouse"],
+ filters = {"company": self.filters.company, "parent": ("in", self.item_codes)}):
+ self.item_details[d.parent] = d
+
+ def get_bin_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.bin_details = {}
+ self.mrp_warehouses = []
+ if self.filters.raw_material_warehouse:
+ self.mrp_warehouses.extend(get_child_warehouses(self.filters.raw_material_warehouse))
+ self.warehouses.extend(self.mrp_warehouses)
+
+ for d in frappe.get_all("Bin",
+ fields=["warehouse", "item_code", "actual_qty", "ordered_qty", "projected_qty"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)}):
+ key = (d.item_code, d.warehouse)
+ if key not in self.bin_details:
+ self.bin_details.setdefault(key, d)
+
+ def get_purchase_details(self):
+ if not (self.orders and self.raw_materials_dict): return
+
+ self.purchase_details = {}
+
+ for d in frappe.get_all("Purchase Order Item",
+ fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
+ filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
+ group_by = "item_code, warehouse"):
+ key = (d.item_code, d.warehouse)
+ if key not in self.purchase_details:
+ self.purchase_details.setdefault(key, d)
+
+ def prepare_data(self):
+ if not self.orders: return
+
+ for d in self.orders:
+ key = d.name if self.filters.based_on == "Work Order" else d.bom_no
+
+ if not self.raw_materials_dict.get(key): continue
+
+ bin_data = self.bin_details.get((d.production_item, d.warehouse)) or {}
+ d.update({
+ "for_warehouse": d.warehouse,
+ "available_qty": 0
+ })
+
+ if bin_data and bin_data.get("actual_qty") > 0 and d.qty_to_manufacture:
+ d.available_qty = (bin_data.get("actual_qty")
+ if (d.qty_to_manufacture > bin_data.get("actual_qty")) else d.qty_to_manufacture)
+
+ bin_data["actual_qty"] -= d.available_qty
+
+ self.update_raw_materials(d, key)
+
+ def update_raw_materials(self, data, key):
+ self.index = 0
+ self.raw_materials_dict.get(key)
+
+ 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
+
+ if not warehouses:
+ warehouses = [data.warehouse]
+
+ if self.filters.based_on == "Work Order" and d.warehouse:
+ warehouses = [d.warehouse]
+ else:
+ item_details = self.item_details.get(d.item_code)
+ if item_details:
+ warehouses = [item_details["default_warehouse"]]
+
+ if self.filters.raw_material_warehouse:
+ warehouses = get_child_warehouses(self.filters.raw_material_warehouse)
+
+ d.remaining_qty = d.required_qty
+ self.pick_materials_from_warehouses(d, data, warehouses)
+
+ if (d.remaining_qty and self.filters.raw_material_warehouse
+ and d.remaining_qty != d.required_qty):
+ row = self.get_args()
+ d.warehouse = self.filters.raw_material_warehouse
+ d.required_qty = d.remaining_qty
+ d.allotted_qty = 0
+ row.update(d)
+ self.data.append(row)
+
+ def pick_materials_from_warehouses(self, args, order_data, warehouses):
+ for index, warehouse in enumerate(warehouses):
+ if not args.remaining_qty: return
+
+ row = self.get_args()
+
+ key = (args.item_code, warehouse)
+ bin_data = self.bin_details.get(key)
+
+ if bin_data:
+ row.update(bin_data)
+
+ args.allotted_qty = 0
+ if bin_data and bin_data.get("actual_qty") > 0:
+ args.allotted_qty = (bin_data.get("actual_qty")
+ if (args.required_qty > bin_data.get("actual_qty")) else args.required_qty)
+
+ args.remaining_qty -= args.allotted_qty
+ bin_data["actual_qty"] -= args.allotted_qty
+
+ if ((self.mrp_warehouses and (args.allotted_qty or index == len(warehouses) - 1))
+ or not self.mrp_warehouses):
+ if not self.index:
+ row.update(order_data)
+ self.index += 1
+
+ args.warehouse = warehouse
+ row.update(args)
+ if self.purchase_details.get(key):
+ row.update(self.purchase_details.get(key))
+
+ self.data.append(row)
+
+ def get_args(self):
+ return frappe._dict({
+ "work_order": "",
+ "sales_order": "",
+ "production_item": "",
+ "production_item_name": "",
+ "qty_to_manufacture": "",
+ "produced_qty": ""
+ })
+
+ def get_columns(self):
+ based_on = self.filters.based_on
+
+ self.columns = [{
+ "label": _("ID"),
+ "options": based_on,
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Item Code"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Item Name"),
+ "fieldname": "production_item_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "for_warehouse",
+ "fieldtype": "Link",
+ "width": 100
+ }, {
+ "label": _("Order Qty"),
+ "fieldname": "qty_to_manufacture",
+ "fieldtype": "Float",
+ "width": 80
+ }, {
+ "label": _("Available"),
+ "fieldname": "available_qty",
+ "fieldtype": "Float",
+ "width": 80
+ }]
+
+ fieldname, fieldtype = "delivery_date", "Date"
+ if self.filters.based_on == "Sales Order" and self.filters.order_by == "Total Amount":
+ fieldname, fieldtype = "base_grand_total", "Currency"
+ elif self.filters.based_on == "Material Request":
+ fieldname = "schedule_date"
+ elif self.filters.based_on == "Work Order":
+ fieldname = "planned_start_date"
+
+ self.columns.append({
+ "label": _(self.filters.order_by),
+ "fieldname": fieldname,
+ "fieldtype": fieldtype,
+ "width": 100
+ })
+
+ self.columns.extend([{
+ "label": _("Raw Material Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 120
+ }, {
+ "label": _("Raw Material Name"),
+ "fieldname": "raw_material_name",
+ "fieldtype": "Data",
+ "width": 130
+ }, {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 110
+ }, {
+ "label": _("Required Qty"),
+ "fieldname": "required_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Allotted Qty"),
+ "fieldname": "allotted_qty",
+ "fieldtype": "Float",
+ "width": 100
+ }, {
+ "label": _("Expected Arrival Date"),
+ "fieldname": "arrival_date",
+ "fieldtype": "Date",
+ "width": 160
+ }, {
+ "label": _("Arrival Quantity"),
+ "fieldname": "arrival_qty",
+ "fieldtype": "Float",
+ "width": 140
+ }])
+
+def document_query(doctype, txt, searchfield, start, page_len, filters):
+ pass
\ No newline at end of file
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py b/erpnext/manufacturing/report/quality_inspection_summary/__init__.py
similarity index 100%
copy from erpnext/accounts/report/purchase_order_items_to_be_billed/__init__.py
copy to erpnext/manufacturing/report/quality_inspection_summary/__init__.py
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js
new file mode 100644
index 0000000..d4587aa
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.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["Quality Inspection Summary"] = {
+ "filters": [
+ {
+ label: __("From Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.datetime.add_months(frappe.datetime.get_today(), -12),
+ reqd: 1
+ },
+ {
+ label: __("To Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.datetime.get_today(),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Accepted", "Rejected"]
+ },
+ {
+ label: __("Item Code"),
+ fieldname: "item_code",
+ fieldtype: "Link",
+ options: "Item"
+ },
+ {
+ label: __("Inspected By"),
+ fieldname: "inspected_by",
+ fieldtype: "Link",
+ options: "User"
+ }
+ ]
+};
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
new file mode 100644
index 0000000..48226e6
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-26 18:23:53.475110",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-26 18:24:50.529940",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Quality Inspection Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Quality Inspection",
+ "report_name": "Quality Inspection Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Quality Manager"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
new file mode 100644
index 0000000..6192632
--- /dev/null
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -0,0 +1,132 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data , None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": ("<", 2)}
+
+ fields = ["name", "status", "report_date", "item_code", "item_name", "sample_size",
+ "inspection_type", "reference_type", "reference_name", "inspected_by"]
+
+ for field in ["status", "item_code", "status", "inspected_by"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["report_date"] = (">=", filters.get("from_date"))
+ query_filters["report_date"] = ("<=", filters.get("to_date"))
+
+ return frappe.get_all("Quality Inspection",
+ fields= fields, filters=query_filters, order_by="report_date asc")
+
+def get_chart_data(periodic_data, columns):
+ labels = ["Rejected", "Accepted"]
+
+ status_wise_data = {
+ "Accepted": 0,
+ "Rejected": 0
+ }
+
+ datasets = []
+
+ for d in periodic_data:
+ status_wise_data[d.status] += 1
+
+ datasets.append({'name':'Qty Wise Chart',
+ 'values': [status_wise_data.get("Rejected"), status_wise_data.get("Accepted")]})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ {
+ "label": _("Report Date"),
+ "fieldname": "report_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 130
+ },
+ {
+ "label": _("Sample Size"),
+ "fieldname": "sample_size",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Inspection Type"),
+ "fieldname": "inspection_type",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Document Type"),
+ "fieldname": "reference_type",
+ "fieldtype": "Data",
+ "width": 90
+ },
+ {
+ "label": _("Document Name"),
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "options": "reference_type",
+ "width": 150
+ },
+ {
+ "label": _("Inspected By"),
+ "fieldname": "inspected_by",
+ "fieldtype": "Link",
+ "options": "User",
+ "width": 150
+ }
+ ])
+
+ return columns
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_be_ordered/__init__.py b/erpnext/manufacturing/report/work_order_summary/__init__.py
similarity index 100%
copy from erpnext/buying/report/requested_items_to_be_ordered/__init__.py
copy to erpnext/manufacturing/report/work_order_summary/__init__.py
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
new file mode 100644
index 0000000..eb23f17
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js
@@ -0,0 +1,86 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Work Order Summary"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldname: "company",
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ fieldname: "fiscal_year",
+ label: __("Fiscal Year"),
+ fieldtype: "Link",
+ options: "Fiscal Year",
+ default: frappe.defaults.get_user_default("fiscal_year"),
+ reqd: 1,
+ on_change: function(query_report) {
+ var fiscal_year = query_report.get_values().fiscal_year;
+ if (!fiscal_year) {
+ return;
+ }
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ from_date: fy.year_start_date,
+ to_date: fy.year_end_date
+ });
+ });
+ }
+ },
+ {
+ label: __("From Posting Date"),
+ fieldname:"from_date",
+ fieldtype: "Date",
+ default: frappe.defaults.get_user_default("year_start_date"),
+ reqd: 1
+ },
+ {
+ label: __("To Posting Date"),
+ fieldname:"to_date",
+ fieldtype: "Date",
+ default: frappe.defaults.get_user_default("year_end_date"),
+ reqd: 1,
+ },
+ {
+ label: __("Status"),
+ fieldname: "status",
+ fieldtype: "Select",
+ options: ["", "Not Started", "In Process", "Completed", "Stopped"]
+ },
+ {
+ label: __("Sales Orders"),
+ fieldname: "sales_order",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Sales Order', txt);
+ }
+ },
+ {
+ label: __("Production Item"),
+ fieldname: "production_item",
+ fieldtype: "MultiSelectList",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Item', txt);
+ }
+ },
+ {
+ label: __("Age"),
+ fieldname:"age",
+ fieldtype: "Int",
+ default: "0"
+ },
+ {
+ label: __("Charts Based On"),
+ fieldname:"charts_based_on",
+ fieldtype: "Select",
+ options: ["Status", "Age", "Quantity"],
+ default: "Status"
+ },
+ ]
+};
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.json b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
new file mode 100644
index 0000000..0d093e2
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.json
@@ -0,0 +1,31 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-04-17 17:07:56.830358",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Gadgets International",
+ "modified": "2020-04-19 16:59:47.979278",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Work Order Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Work Order Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Manufacturing Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
new file mode 100644
index 0000000..fb047b2
--- /dev/null
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -0,0 +1,267 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import date_diff, today, getdate, flt
+from frappe import _
+from erpnext.stock.report.stock_analytics.stock_analytics import (get_period_date_ranges, get_period)
+
+def execute(filters=None):
+ columns, data = [], []
+
+ if not filters.get("age"):
+ filters["age"] = 0
+
+ data = get_data(filters)
+ columns = get_columns(filters)
+ chart_data = get_chart_data(data, filters)
+ return columns, data, None, chart_data
+
+def get_data(filters):
+ query_filters = {"docstatus": 1}
+
+ fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
+ "planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
+
+ for field in ["sales_order", "production_item", "status", "company"]:
+ if filters.get(field):
+ query_filters[field] = ("in", filters.get(field))
+
+ query_filters["planned_start_date"] = (">=", filters.get("from_date"))
+ query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
+
+ data = frappe.get_all("Work Order",
+ fields= fields, filters=query_filters, order_by="planned_start_date asc")
+
+ res = []
+ for d in data:
+ start_date = d.actual_start_date or d.planned_start_date
+ d.age = 0
+
+ if d.status != 'Completed':
+ d.age = date_diff(today(), start_date)
+
+ if filters.get("age") <= d.age:
+ res.append(d)
+
+ return res
+
+def get_chart_data(data, filters):
+ if filters.get("charts_based_on") == "Status":
+ return get_chart_based_on_status(data)
+ elif filters.get("charts_based_on") == "Age":
+ return get_chart_based_on_age(data)
+ else:
+ return get_chart_based_on_qty(data, filters)
+
+def get_chart_based_on_status(data):
+ labels = ["Completed", "In Process", "Stopped", "Not Started"]
+
+ status_wise_data = {
+ "Not Started": 0,
+ "In Process": 0,
+ "Stopped": 0,
+ "Completed": 0
+ }
+
+ for d in data:
+ status_wise_data[d.status] += 1
+
+ values = [status_wise_data["Completed"], status_wise_data["In Process"],
+ status_wise_data["Stopped"], status_wise_data["Not Started"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_age(data):
+ labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
+
+ age_wise_data = {
+ "0-30 Days": 0,
+ "30-60 Days": 0,
+ "60-90 Days": 0,
+ "90 Above": 0
+ }
+
+ for d in data:
+ if d.age > 0 and d.age <= 30:
+ age_wise_data["0-30 Days"] += 1
+ elif d.age > 30 and d.age <= 60:
+ age_wise_data["30-60 Days"] += 1
+ elif d.age > 60 and d.age <= 90:
+ age_wise_data["60-90 Days"] += 1
+ else:
+ age_wise_data["90 Above"] += 1
+
+ values = [age_wise_data["0-30 Days"], age_wise_data["30-60 Days"],
+ age_wise_data["60-90 Days"], age_wise_data["90 Above"]]
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': [{'name':'Qty Wise Chart', 'values': values}]
+ },
+ "type": "donut",
+ "height": 300
+ }
+
+ return chart
+
+def get_chart_based_on_qty(data, filters):
+ labels, periodic_data = prepare_chart_data(data, filters)
+
+ pending, completed = [], []
+ datasets = []
+
+ for d in labels:
+ pending.append(periodic_data.get("Pending").get(d))
+ completed.append(periodic_data.get("Completed").get(d))
+
+ datasets.append({"name": "Pending", "values": pending})
+ datasets.append({"name": "Completed", "values": completed})
+
+ chart = {
+ "data": {
+ 'labels': labels,
+ 'datasets': datasets
+ },
+ "type": "bar",
+ "barOptions": {
+ "stacked": 1
+ }
+ }
+
+ return chart
+
+def prepare_chart_data(data, filters):
+ labels = []
+
+ periodic_data = {
+ "Pending": {},
+ "Completed": {}
+ }
+
+ filters.range = "Monthly"
+
+ ranges = get_period_date_ranges(filters)
+ for from_date, end_date in ranges:
+ period = get_period(end_date, filters)
+ if period not in labels:
+ labels.append(period)
+
+ if period not in periodic_data["Pending"]:
+ periodic_data["Pending"][period] = 0
+
+ if period not in periodic_data["Completed"]:
+ periodic_data["Completed"][period] = 0
+
+ for d in data:
+ if getdate(d.planned_start_date) >= from_date and getdate(d.planned_start_date) <= end_date:
+ periodic_data["Pending"][period] += (flt(d.qty) - flt(d.produced_qty))
+ periodic_data["Completed"][period] += flt(d.produced_qty)
+
+ return labels, periodic_data
+
+def get_columns(filters):
+ columns = [
+ {
+ "label": _("Id"),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": "Work Order",
+ "width": 100
+ },
+ ]
+
+ if not filters.get("status"):
+ columns.append(
+ {
+ "label": _("Status"),
+ "fieldname": "status",
+ "width": 100
+ },
+ )
+
+ columns.extend([
+ {
+ "label": _("Production Item"),
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 130
+ },
+ {
+ "label": _("Produce Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Produced Qty"),
+ "fieldname": "produced_qty",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 90
+ },
+ {
+ "label": _("Planned Start Date"),
+ "fieldname": "planned_start_date",
+ "fieldtype": "Date",
+ "width": 150
+ },
+ {
+ "label": _("Planned End Date"),
+ "fieldname": "planned_end_date",
+ "fieldtype": "Date",
+ "width": 150
+ }
+ ])
+
+ if filters.get("status") != 'Not Started':
+ columns.extend([
+ {
+ "label": _("Actual Start Date"),
+ "fieldname": "actual_start_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Actual End Date"),
+ "fieldname": "actual_end_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Age"),
+ "fieldname": "age",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ if filters.get("status") == 'Completed':
+ columns.extend([
+ {
+ "label": _("Lead Time (in mins)"),
+ "fieldname": "lead_time",
+ "fieldtype": "Float",
+ "width": 110
+ },
+ ])
+
+ return columns
\ 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 571f87a..d1294cc 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -77,6 +77,7 @@
customer = frappe.new_doc("Customer")
customer.customer_name = user_details.fullname
customer.customer_type = "Individual"
+ customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True)
try:
@@ -91,7 +92,11 @@
"link_name": customer.name
})
- contact.insert()
+ contact.save()
+
+ except frappe.DuplicateEntryError:
+ return customer.name
+
except Exception as e:
frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
pass
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 5a69cdb..7a0caed 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -62,10 +62,27 @@
'subscription_id': subscription_id,
'email_id': email
}, order_by="creation desc")
- return frappe.get_doc("Member", members[0]['name'])
+ try:
+ return frappe.get_doc("Member", members[0]['name'])
+ except:
+ return None
+
+def verify_signature(data):
+ signature = frappe.request.headers.get('X-Razorpay-Signature')
+
+ settings = frappe.get_doc("Membership Settings")
+ key = settings.get_webhook_secret()
+
+ controller = frappe.get_doc("Razorpay Settings")
+
+ controller.verify_signature(data, signature, key)
+
@frappe.whitelist(allow_guest=True)
-def trigger_razorpay_subscription(data):
+def trigger_razorpay_subscription(*args, **kwargs):
+ data = frappe.request.get_data(as_text=True)
+ verify_signature(data)
+
if isinstance(data, six.string_types):
data = json.loads(data)
data = frappe._dict(data)
@@ -82,7 +99,10 @@
except Exception as e:
error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed"))
notify_failure(error_log)
- raise e
+ return False
+
+ if not member:
+ return False
if data.event == "subscription.activated":
member.customer_id = payment.customer_id
@@ -111,7 +131,6 @@
return True
-
def notify_failure(log):
try:
content = """Dear System Manager,
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index c01a0b2..8c0e3a4 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -1,8 +1,30 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Membership Settings', {
+frappe.ui.form.on("Membership Settings", {
refresh: function(frm) {
+ if (frm.doc.webhook_secret) {
+ frm.add_custom_button(__("Revoke <Key></Key>"), () => {
+ frm.call("revoke_key").then(() => {
+ frm.refresh();
+ })
+ });
+ }
+ frm.trigger("add_generate_button");
+ },
- }
+ add_generate_button: function(frm) {
+ let label;
+
+ if (frm.doc.webhook_secret) {
+ label = __("Regenerate Webhook Secret");
+ } else {
+ label = __("Generate Webhook Secret");
+ }
+ frm.add_custom_button(label, () => {
+ frm.call("generate_webhook_key").then(() => {
+ frm.refresh();
+ });
+ });
+ },
});
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 56b8eac..52b9d01 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -8,7 +8,8 @@
"enable_razorpay",
"razorpay_settings_section",
"billing_cycle",
- "billing_frequency"
+ "billing_frequency",
+ "webhook_secret"
],
"fields": [
{
@@ -34,11 +35,17 @@
"fieldname": "billing_frequency",
"fieldtype": "Int",
"label": "Billing Frequency"
+ },
+ {
+ "fieldname": "webhook_secret",
+ "fieldtype": "Password",
+ "label": "Webhook Secret",
+ "read_only": 1
}
],
"issingle": 1,
"links": [],
- "modified": "2020-04-07 18:42:51.496807",
+ "modified": "2020-05-22 12:38:27.103759",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
index 2b8e37f..f3b2eee 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.py
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py
@@ -4,11 +4,27 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.integrations.utils import get_payment_gateway_controller
from frappe.model.document import Document
class MembershipSettings(Document):
- pass
+ def generate_webhook_key(self):
+ key = frappe.generate_hash(length=20)
+ self.webhook_secret = key
+ self.save()
+
+ frappe.msgprint(
+ _("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
+ _("Webhook Secret")
+ );
+
+ def revoke_key(self):
+ self.webhook_secret = None;
+ self.save()
+
+ def get_webhook_secret(self):
+ return self.get_password(fieldname="webhook_secret", raise_exception=False)
@frappe.whitelist()
def get_plans_for_membership(*args, **kwargs):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 28cd215..e897260 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -678,11 +678,26 @@
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
-erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries
+erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
+execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
+erpnext.patches.v12_0.move_due_advance_amount_to_pending_amount
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
-erpnext.patches.v12_0.set_serial_no_status
+erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
+execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
+erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
+erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
+erpnext.patches.v12_0.update_bom_in_so_mr
+execute:frappe.delete_doc("Report", "Department Analytics")
+execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
+erpnext.patches.v12_0.update_uom_conversion_factor
+erpnext.patches.v13_0.delete_old_purchase_reports
+erpnext.patches.v12_0.set_italian_import_supplier_invoice_permissions
+erpnext.patches.v13_0.update_sla_enhancements
+erpnext.patches.v12_0.update_address_template_for_india
+erpnext.patches.v12_0.set_multi_uom_in_rfq
+erpnext.patches.v13_0.update_issue_metrics
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index 3c9758e..1ddbae6 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -4,7 +4,7 @@
def execute():
frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
- if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account'):
+ if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
frappe.db.sql("""
UPDATE `tabBank` b, `tabBank Account` ba
SET b.swift_number = ba.swift_number, b.branch_code = ba.branch_code
@@ -12,4 +12,4 @@
""")
frappe.reload_doc('accounts', 'doctype', 'bank_account')
- frappe.reload_doc('accounts', 'doctype', 'payment_request')
\ No newline at end of file
+ frappe.reload_doc('accounts', 'doctype', 'payment_request')
diff --git a/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
new file mode 100644
index 0000000..6013eaa
--- /dev/null
+++ b/erpnext/patches/v12_0/move_due_advance_amount_to_pending_amount.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ ''' Move from due_advance_amount to pending_amount '''
+
+ if frappe.db.has_column("Employee Advance", "due_advance_amount"):
+ frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')
diff --git a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
index 98a2fcf..24286dc 100644
--- a/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
+++ b/erpnext/patches/v12_0/remove_duplicate_leave_ledger_entries.py
@@ -6,6 +6,7 @@
def execute():
"""Delete duplicate leave ledger entries of type allocation created."""
+ frappe.reload_doc('hr', 'doctype', 'leave_ledger_entry')
if not frappe.db.a_row_exists("Leave Ledger Entry"):
return
@@ -14,31 +15,32 @@
def get_duplicate_records():
"""Fetch all but one duplicate records from the list of expired leave allocation."""
- return frappe.db.sql_list("""
- WITH duplicate_records AS
- (SELECT
- name, transaction_name, is_carry_forward,
- ROW_NUMBER() over(partition by transaction_name order by creation)as row
- FROM `tabLeave Ledger Entry` l
- WHERE (EXISTS
- (SELECT name
- FROM `tabLeave Ledger Entry`
- WHERE
- transaction_name = l.transaction_name
- AND transaction_type = 'Leave Allocation'
- AND name <> l.name
- AND employee = l.employee
- AND docstatus = 1
- AND leave_type = l.leave_type
- AND is_carry_forward=l.is_carry_forward
- AND to_date = l.to_date
- AND from_date = l.from_date
- AND is_expired = 1
- )))
- SELECT name FROM duplicate_records WHERE row > 1
+ return frappe.db.sql("""
+ SELECT name, employee, transaction_name, leave_type, is_carry_forward, from_date, to_date
+ FROM `tabLeave Ledger Entry`
+ WHERE
+ transaction_type = 'Leave Allocation'
+ AND docstatus = 1
+ AND is_expired = 1
+ GROUP BY
+ employee, transaction_name, leave_type, is_carry_forward, from_date, to_date
+ HAVING
+ count(name) > 1
+ ORDER BY
+ creation
""")
def delete_duplicate_ledger_entries(duplicate_records_list):
"""Delete duplicate leave ledger entries."""
- if duplicate_records_list:
- frappe.db.sql(''' DELETE FROM `tabLeave Ledger Entry` WHERE name in {0}'''.format(tuple(duplicate_records_list))) #nosec
\ No newline at end of file
+ if not duplicate_records_list: return
+ for d in duplicate_records_list:
+ frappe.db.sql('''
+ DELETE FROM `tabLeave Ledger Entry`
+ WHERE name != %s
+ AND employee = %s
+ AND transaction_name = %s
+ AND leave_type = %s
+ AND is_carry_forward = %s
+ AND from_date = %s
+ AND to_date = %s
+ ''', tuple(d))
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/rename_bank_reconciliation.py b/erpnext/patches/v12_0/rename_bank_reconciliation.py
index eda47a9..2efa854 100644
--- a/erpnext/patches/v12_0/rename_bank_reconciliation.py
+++ b/erpnext/patches/v12_0/rename_bank_reconciliation.py
@@ -8,9 +8,6 @@
if frappe.db.table_exists("Bank Reconciliation"):
frappe.rename_doc('DocType', 'Bank Reconciliation', 'Bank Clearance', force=True)
frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance')
-
+
frappe.rename_doc('DocType', 'Bank Reconciliation Detail', 'Bank Clearance Detail', force=True)
frappe.reload_doc('Accounts', 'doctype', 'Bank Clearance Detail')
-
- frappe.delete_doc("DocType", "Bank Reconciliation")
- frappe.delete_doc("DocType", "Bank Reconciliation Detail")
diff --git a/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py
new file mode 100644
index 0000000..a6011c4
--- /dev/null
+++ b/erpnext/patches/v12_0/set_italian_import_supplier_invoice_permissions.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.italy.setup import add_permissions
+
+def execute():
+ countries = frappe.get_all("Company", fields="country")
+ countries = [country["country"] for country in countries]
+ if "Italy" in countries:
+ add_permissions()
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_multi_uom_in_rfq.py b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
new file mode 100644
index 0000000..70ca6b2
--- /dev/null
+++ b/erpnext/patches/v12_0/set_multi_uom_in_rfq.py
@@ -0,0 +1,16 @@
+# 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 flt
+from erpnext.stock.get_item_details import get_conversion_factor
+
+def execute():
+ frappe.reload_doc('buying', 'doctype', 'request_for_quotation_item')
+
+ frappe.db.sql("""UPDATE `tabRequest for Quotation Item`
+ SET
+ stock_uom = uom,
+ conversion_factor = 1,
+ stock_qty = qty""")
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
index 6f843cd..52c9a2d 100644
--- a/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
+++ b/erpnext/patches/v12_0/set_purchase_receipt_delivery_note_detail.py
@@ -64,11 +64,13 @@
return_document_map = make_return_document_map(doctype, return_document_map)
+ count = 0
+
#iterate through original documents and its return documents
for docname in return_document_map:
- doc_items = frappe.get_doc(doctype, docname).get("items")
+ doc_items = frappe.get_cached_doc(doctype, docname).get("items")
for return_doc in return_document_map[docname]:
- return_doc_items = frappe.get_doc(doctype, return_doc).get("items")
+ return_doc_items = frappe.get_cached_doc(doctype, return_doc).get("items")
#iterate through return document items and original document items for mapping
for return_item in return_doc_items:
@@ -80,9 +82,11 @@
else:
continue
+ # commit after every 100 sql updates
+ count += 1
+ if count%100 == 0:
+ frappe.db.commit()
+
set_document_detail_in_return_document("Purchase Receipt")
set_document_detail_in_return_document("Delivery Note")
frappe.db.commit()
-
-
-
diff --git a/erpnext/patches/v12_0/set_serial_no_status.py b/erpnext/patches/v12_0/set_serial_no_status.py
index 4ec84ef..3b5f5ef 100644
--- a/erpnext/patches/v12_0/set_serial_no_status.py
+++ b/erpnext/patches/v12_0/set_serial_no_status.py
@@ -5,13 +5,22 @@
def execute():
frappe.reload_doc('stock', 'doctype', 'serial_no')
- for serial_no in frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date from `tabSerial No`
- where (status is NULL OR status='')""", as_dict = 1):
+ serial_no_list = frappe.db.sql("""select name, delivery_document_type, warranty_expiry_date, warehouse from `tabSerial No`
+ where (status is NULL OR status='')""", as_dict = 1)
+ if len(serial_no_list) > 20000:
+ frappe.db.auto_commit_on_many_writes = True
+
+ for serial_no in serial_no_list:
if serial_no.get("delivery_document_type"):
status = "Delivered"
elif serial_no.get("warranty_expiry_date") and getdate(serial_no.get("warranty_expiry_date")) <= getdate(nowdate()):
status = "Expired"
+ elif not serial_no.get("warehouse"):
+ status = "Inactive"
else:
status = "Active"
- frappe.db.set_value("Serial No", serial_no.get("name"), "status", status)
\ No newline at end of file
+ frappe.db.set_value("Serial No", serial_no.get("name"), "status", status)
+
+ if frappe.db.auto_commit_on_many_writes:
+ frappe.db.auto_commit_on_many_writes = False
diff --git a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
index 60aec05..b8efb21 100644
--- a/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
+++ b/erpnext/patches/v12_0/unset_customer_supplier_based_on_type_of_item_price.py
@@ -1,15 +1,29 @@
from __future__ import unicode_literals
import frappe
+
def execute():
- invalid_selling_item_price = frappe.db.sql(
- """SELECT name FROM `tabItem Price` WHERE selling = 1 and buying = 0 and (supplier IS NOT NULL or supplier = '')"""
- )
- invalid_buying_item_price = frappe.db.sql(
- """SELECT name FROM `tabItem Price` WHERE selling = 0 and buying = 1 and (customer IS NOT NULL or customer = '')"""
- )
- docs_to_modify = invalid_buying_item_price + invalid_selling_item_price
- for d in docs_to_modify:
- # saving the doc will auto reset invalid customer/supplier field
- doc = frappe.get_doc("Item Price", d[0])
- doc.save()
\ No newline at end of file
+ """
+ set proper customer and supplier details for item price
+ based on selling and buying values
+ """
+
+ # update for selling
+ frappe.db.sql(
+ """UPDATE `tabItem Price` ip, `tabPrice List` pl
+ SET ip.`reference` = ip.`customer`, ip.`supplier` = NULL
+ WHERE ip.`selling` = 1
+ AND ip.`buying` = 0
+ AND (ip.`supplier` IS NOT NULL OR ip.`supplier` = '')
+ AND ip.`price_list` = pl.`name`
+ AND pl.`enabled` = 1""")
+
+ # update for buying
+ frappe.db.sql(
+ """UPDATE `tabItem Price` ip, `tabPrice List` pl
+ SET ip.`reference` = ip.`supplier`, ip.`customer` = NULL
+ WHERE ip.`selling` = 0
+ AND ip.`buying` = 1
+ AND (ip.`customer` IS NOT NULL OR ip.`customer` = '')
+ AND ip.`price_list` = pl.`name`
+ AND pl.`enabled` = 1""")
diff --git a/erpnext/patches/v12_0/update_address_template_for_india.py b/erpnext/patches/v12_0/update_address_template_for_india.py
new file mode 100644
index 0000000..0d582da
--- /dev/null
+++ b/erpnext/patches/v12_0/update_address_template_for_india.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.address_template.setup import set_up_address_templates
+
+def execute():
+ if frappe.db.get_value('Company', {'country': 'India'}, 'name'):
+ address_template = frappe.db.get_value('Address Template', 'India', 'template')
+ if not address_template or "gstin" not in address_template:
+ set_up_address_templates(default_country='India')
diff --git a/erpnext/patches/v12_0/update_bom_in_so_mr.py b/erpnext/patches/v12_0/update_bom_in_so_mr.py
new file mode 100644
index 0000000..309ae4c
--- /dev/null
+++ b/erpnext/patches/v12_0/update_bom_in_so_mr.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("stock", "doctype", "material_request_item")
+ frappe.reload_doc("selling", "doctype", "sales_order_item")
+
+ for doctype in ["Sales Order", "Material Request"]:
+ condition = " and child_doc.stock_qty > child_doc.produced_qty"
+ if doctype == "Material Request":
+ condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
+
+ frappe.db.sql(""" UPDATE `tab{doc}` as doc, `tab{doc} Item` as child_doc, tabItem as item
+ SET
+ child_doc.bom_no = item.default_bom
+ WHERE
+ child_doc.item_code = item.name and child_doc.docstatus < 2
+ and item.default_bom is not null and item.default_bom != '' {cond}
+ """.format(doc = doctype, cond = condition))
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/update_uom_conversion_factor.py b/erpnext/patches/v12_0/update_uom_conversion_factor.py
new file mode 100644
index 0000000..b5a20aa
--- /dev/null
+++ b/erpnext/patches/v12_0/update_uom_conversion_factor.py
@@ -0,0 +1,11 @@
+from __future__ import unicode_literals
+import frappe, json
+
+def execute():
+ from erpnext.setup.setup_wizard.operations.install_fixtures import add_uom_data
+
+ frappe.reload_doc("setup", "doctype", "UOM Conversion Factor")
+ frappe.reload_doc("setup", "doctype", "UOM")
+ frappe.reload_doc("stock", "doctype", "UOM Category")
+
+ add_uom_data()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/__init__.py b/erpnext/patches/v13_0/__init__.py
index e69de29..baffc48 100644
--- a/erpnext/patches/v13_0/__init__.py
+++ b/erpnext/patches/v13_0/__init__.py
@@ -0,0 +1 @@
+from __future__ import unicode_literals
diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py
new file mode 100644
index 0000000..8bdc07e
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ reports_to_delete = ["Requested Items To Be Ordered",
+ "Purchase Order Items To Be Received or Billed","Purchase Order Items To Be Received",
+ "Purchase Order Items To Be Billed"]
+
+ for report in reports_to_delete:
+ if frappe.db.exists("Report", report):
+ delete_auto_email_reports(report)
+
+ frappe.delete_doc("Report", report)
+
+def delete_auto_email_reports(report):
+ """ Check for one or multiple Auto Email Reports and delete """
+ auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
+ for auto_email_report in auto_email_reports:
+ frappe.delete_doc("Auto Email Report", auto_email_report[0])
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
index ec94cd0..5ade8ca 100644
--- a/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
+++ b/erpnext/patches/v13_0/move_tax_slabs_from_payroll_period_to_income_tax_slab.py
@@ -14,15 +14,21 @@
frappe.reload_doc("hr", "doctype", doctype)
+ standard_tax_exemption_amount_exists = frappe.db.has_column("Payroll Period", "standard_tax_exemption_amount")
+
+ select_fields = "name, start_date, end_date"
+ if standard_tax_exemption_amount_exists:
+ select_fields = "name, start_date, end_date, standard_tax_exemption_amount"
+
for company in frappe.get_all("Company"):
payroll_periods = frappe.db.sql("""
SELECT
- name, start_date, end_date, standard_tax_exemption_amount
+ {0}
FROM
`tabPayroll Period`
WHERE company=%s
ORDER BY start_date DESC
- """, company.name, as_dict = 1)
+ """.format(select_fields), company.name, as_dict = 1)
for i, period in enumerate(payroll_periods):
income_tax_slab = frappe.new_doc("Income Tax Slab")
@@ -36,7 +42,8 @@
income_tax_slab.effective_from = period.start_date
income_tax_slab.company = company.name
income_tax_slab.allow_tax_exemption = 1
- income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount
+ if standard_tax_exemption_amount_exists:
+ income_tax_slab.standard_tax_exemption_amount = period.standard_tax_exemption_amount
income_tax_slab.flags.ignore_mandatory = True
income_tax_slab.submit()
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
new file mode 100644
index 0000000..be5e30f
--- /dev/null
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -0,0 +1,10 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ company = frappe.db.get_single_value('Global Defaults', 'default_company')
+ doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
+ for entry in doctypes:
+ if frappe.db.exists('DocType', entry):
+ frappe.reload_doc('Healthcare', 'doctype', entry)
+ frappe.db.sql("update `tab{dt}` set company = {company} where ifnull(company, '') = ''".format(dt=entry, company=frappe.db.escape(company)))
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
new file mode 100644
index 0000000..331c559
--- /dev/null
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import add_to_date
+from frappe.utils.dashboard import get_config, make_records
+
+def execute():
+ frappe.reload_doc("manufacturing", "doctype", "work_order")
+ frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+ frappe.reload_doc("manufacturing", "doctype", "job_card")
+
+ data = frappe.get_all("Work Order",
+ filters = {
+ "docstatus": 1,
+ "status": ("in", ["In Process", "Completed"])
+ })
+
+ for d in data:
+ doc = frappe.get_doc("Work Order", d.name)
+ doc.set_actual_dates()
+ doc.db_set("actual_start_date", doc.actual_start_date, update_modified=False)
+
+ if doc.status == "Completed":
+ frappe.db.set_value("Work Order", d.name, {
+ "actual_end_date": doc.actual_end_date,
+ "lead_time": doc.lead_time
+ }, update_modified=False)
+
+ if not doc.planned_end_date:
+ planned_end_date = add_to_date(doc.planned_start_date, minutes=doc.lead_time)
+ doc.db_set("planned_end_date", doc.actual_start_date, update_modified=False)
+
+ frappe.db.sql(""" UPDATE `tabJob Card` as jc, `tabWork Order` as wo
+ SET
+ jc.production_item = wo.production_item, jc.item_name = wo.item_name
+ WHERE
+ jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
+ """)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_issue_metrics.py b/erpnext/patches/v13_0/update_issue_metrics.py
new file mode 100644
index 0000000..6d76235
--- /dev/null
+++ b/erpnext/patches/v13_0/update_issue_metrics.py
@@ -0,0 +1,33 @@
+from __future__ import unicode_literals
+import frappe
+
+from frappe.core.doctype.communication.communication import set_avg_response_time
+from erpnext.support.doctype.issue.issue import set_resolution_time, set_user_resolution_time
+
+def execute():
+ if frappe.db.exists('DocType', 'Issue'):
+ frappe.reload_doctype('Issue')
+
+ count = 0
+ for parent in frappe.get_all('Issue', order_by='creation desc'):
+ parent_doc = frappe.get_doc('Issue', parent.name)
+
+ communication = frappe.get_all('Communication', filters={
+ 'reference_doctype': 'Issue',
+ 'reference_name': parent.name,
+ 'communication_medium': 'Email',
+ 'sent_or_received': 'Sent'
+ }, order_by = 'creation asc', limit=1)
+
+ if communication:
+ communication_doc = frappe.get_doc('Communication', communication[0].name)
+ set_avg_response_time(parent_doc, communication_doc)
+
+ if parent_doc.status in ['Closed', 'Resolved']:
+ set_resolution_time(parent_doc)
+ set_user_resolution_time(parent_doc)
+
+ # commit after every 100 records
+ count += 1
+ if count % 100 == 0:
+ frappe.db.commit()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_sla_enhancements.py b/erpnext/patches/v13_0/update_sla_enhancements.py
new file mode 100644
index 0000000..c156ba9
--- /dev/null
+++ b/erpnext/patches/v13_0/update_sla_enhancements.py
@@ -0,0 +1,93 @@
+# Copyright (c) 2018, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ # add holiday list and employee group fields in SLA
+ # change response and resolution time in priorities child table
+ if frappe.db.exists('DocType', 'Service Level Agreement'):
+ sla_details = frappe.db.get_all('Service Level Agreement', fields=['name', 'service_level'])
+ priorities = frappe.db.get_all('Service Level Priority', fields=['*'], filters={
+ 'parenttype': ('in', ['Service Level Agreement', 'Service Level'])
+ })
+
+ frappe.reload_doc('support', 'doctype', 'service_level_agreement')
+ frappe.reload_doc('support', 'doctype', 'pause_sla_on_status')
+ frappe.reload_doc('support', 'doctype', 'service_level_priority')
+ frappe.reload_doc('support', 'doctype', 'service_day')
+
+ for entry in sla_details:
+ values = frappe.db.get_value('Service Level', entry.service_level, ['holiday_list', 'employee_group'])
+ if values:
+ holiday_list = values[0]
+ employee_group = values[1]
+ frappe.db.set_value('Service Level Agreement', entry.name, {
+ 'holiday_list': holiday_list,
+ 'employee_group': employee_group
+ })
+
+ priority_dict = {}
+
+ for priority in priorities:
+ if priority.parenttype == 'Service Level Agreement':
+ response_time = convert_to_seconds(priority.response_time, priority.response_time_period)
+ resolution_time = convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
+ frappe.db.set_value('Service Level Priority', priority.name, {
+ 'response_time': response_time,
+ 'resolution_time': resolution_time
+ })
+ if priority.parenttype == 'Service Level':
+ if not priority.parent in priority_dict:
+ priority_dict[priority.parent] = []
+ priority_dict[priority.parent].append(priority)
+
+
+ # copy Service Levels to Service Level Agreements
+ sl = [entry.service_level for entry in sla_details]
+ if frappe.db.exists('DocType', 'Service Level'):
+ service_levels = frappe.db.get_all('Service Level', filters={'service_level': ('not in', sl)}, fields=['*'])
+ for entry in service_levels:
+ sla = frappe.new_doc('Service Level Agreement')
+ sla.service_level = entry.service_level
+ sla.holiday_list = entry.holiday_list
+ sla.employee_group = entry.employee_group
+ sla.flags.ignore_validate = True
+ sla = sla.insert(ignore_mandatory=True)
+
+ frappe.db.sql("""
+ UPDATE
+ `tabService Day`
+ SET
+ parent = %(new_parent)s , parentfield = 'support_and_resolution', parenttype = 'Service Level Agreement'
+ WHERE
+ parent = %(old_parent)s
+ """, {'new_parent': sla.name, 'old_parent': entry.name}, as_dict = 1)
+
+ priority_list = priority_dict.get(entry.name)
+ if priority_list:
+ sla = frappe.get_doc('Service Level Agreement', sla.name)
+ for priority in priority_list:
+ row = sla.append('priorities', {
+ 'priority': priority.priority,
+ 'default_priority': priority.default_priority,
+ 'response_time': convert_to_seconds(priority.response_time, priority.response_time_period),
+ 'resolution_time': convert_to_seconds(priority.resolution_time, priority.resolution_time_period)
+ })
+ row.db_update()
+ sla.db_update()
+
+ frappe.delete_doc_if_exists('DocType', 'Service Level')
+
+
+def convert_to_seconds(value, unit):
+ seconds = 0
+ if unit == "Hour":
+ seconds = value * 3600
+ if unit == "Day":
+ seconds = value * 3600 * 24
+ if unit == "Week":
+ seconds = value * 3600 * 24 * 7
+ return seconds
diff --git a/erpnext/patches/v7_0/update_mins_to_first_response.py b/erpnext/patches/v7_0/update_mins_to_first_response.py
index 1df4b42..1668135 100644
--- a/erpnext/patches/v7_0/update_mins_to_first_response.py
+++ b/erpnext/patches/v7_0/update_mins_to_first_response.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.core.doctype.communication.email import update_mins_to_first_communication
+from frappe.core.doctype.communication.communication import update_mins_to_first_communication
def execute():
frappe.reload_doctype('Issue')
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 0993e69..6b6b8c5 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -1,4 +1,5 @@
import frappe
+from frappe.utils import cint
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
def get_field_filter_data():
@@ -243,6 +244,8 @@
else:
product_info = None
+ product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
+
return {
'next_attribute': next_attribute,
'valid_options_for_attributes': valid_options_for_attributes,
diff --git a/erpnext/projects/dashboard_fixtures.py b/erpnext/projects/dashboard_fixtures.py
new file mode 100644
index 0000000..d89ffe9
--- /dev/null
+++ b/erpnext/projects/dashboard_fixtures.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import json
+from frappe import _
+
+def get_company_for_dashboards():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company_list = frappe.get_list("Company")
+ if company_list:
+ return company_list[0].name
+ return None
+
+def get_data():
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(),
+ })
+
+def get_dashboards():
+ return [{
+ "doctype": "Dashboard",
+ "name": "Project",
+ "dashboard_name": "Project",
+ "charts": [
+ { "chart": "Project Summary", "width": "Full" }
+ ]
+ }]
+
+def get_charts():
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+
+ return [
+ {
+ 'doctype': 'Dashboard Chart',
+ 'name': 'Project Summary',
+ 'chart_name': _('Project Summary'),
+ 'chart_type': 'Report',
+ 'report_name': 'Project Summary',
+ 'is_public': 1,
+ 'is_custom': 1,
+ 'filters_json': json.dumps({"company": company.name, "status": "Open"}),
+ 'type': 'Bar',
+ 'custom_options': '{"type": "bar", "colors": ["#fc4f51", "#78d6ff", "#7575ff"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}',
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/projects/desk_page/projects/projects.json b/erpnext/projects/desk_page/projects/projects.json
index a07cdff..d91fe53 100644
--- a/erpnext/projects/desk_page/projects/projects.json
+++ b/erpnext/projects/desk_page/projects/projects.json
@@ -17,18 +17,23 @@
}
],
"category": "Modules",
- "charts": [],
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Projects",
- "modified": "2020-04-01 11:28:51.245756",
+ "modified": "2020-05-28 13:38:19.934937",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
@@ -37,6 +42,7 @@
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#cef6d1",
"format": "{} Assigned",
"label": "Task",
"link_to": "Task",
@@ -44,8 +50,11 @@
"type": "DocType"
},
{
+ "color": "#ffe8cd",
+ "format": "{} Open",
"label": "Project",
"link_to": "Project",
+ "stats_filter": "{\n \"status\": \"Open\"\n}",
"type": "DocType"
},
{
@@ -57,6 +66,11 @@
"label": "Project Billing Summary",
"link_to": "Project Billing Summary",
"type": "Report"
+ },
+ {
+ "label": "Project Dashboard",
+ "link_to": "Project",
+ "type": "Dashboard"
}
]
}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 5862963..3570a0f 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,7 +18,7 @@
};
},
onload: function (frm) {
- var so = frm.get_docfield("Project", "sales_order");
+ var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function (field) {
if (frm.is_new()) return;
return {
@@ -135,4 +135,4 @@
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
-}
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index bd33694..47a28fd 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -64,7 +64,7 @@
def assign():
from frappe.desk.form import assign_to
assign_to.add({
- "assign_to": "test@example.com",
+ "assign_to": ["test@example.com"],
"doctype": task.doctype,
"name": task.name,
"description": "Close this task"
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 32f0428..cbc624c 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -13,7 +13,7 @@
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.hr.doctype.salary_structure.test_salary_structure \
import make_salary_structure, create_salary_structure_assignment
-
+from erpnext.hr.doctype.employee.test_employee import make_employee
class TestTimesheet(unittest.TestCase):
def setUp(self):
@@ -25,8 +25,10 @@
def test_timesheet_billing_amount(self):
- make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate=True, billable=1)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 2)
@@ -35,8 +37,10 @@
self.assertEqual(timesheet.total_billable_amount, 100)
def test_timesheet_billing_amount_not_billable(self):
- make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=0)
+ emp = make_employee("test_employee_6@salary.com")
+
+ make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate=True, billable=0)
self.assertEqual(timesheet.total_hours, 2)
self.assertEqual(timesheet.total_billable_hours, 0)
@@ -45,8 +49,10 @@
self.assertEqual(timesheet.total_billable_amount, 0)
def test_salary_slip_from_timesheet(self):
- salary_structure = make_salary_structure_for_timesheet("_T-Employee-00001")
- timesheet = make_timesheet("_T-Employee-00001", simulate = True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ salary_structure = make_salary_structure_for_timesheet(emp)
+ timesheet = make_timesheet(emp, simulate = True, billable=1)
salary_slip = make_salary_slip(timesheet.name)
salary_slip.submit()
@@ -65,7 +71,9 @@
self.assertEqual(timesheet.status, 'Submitted')
def test_sales_invoice_from_timesheet(self):
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1)
+ emp = make_employee("test_employee_6@salary.com")
+
+ timesheet = make_timesheet(emp, simulate=True, billable=1)
sales_invoice = make_sales_invoice(timesheet.name, '_Test Item', '_Test Customer')
sales_invoice.due_date = nowdate()
sales_invoice.submit()
@@ -80,7 +88,9 @@
self.assertEqual(item.rate, 50.00)
def test_timesheet_billing_based_on_project(self):
- timesheet = make_timesheet("_T-Employee-00001", simulate=True, billable=1, project = '_Test Project', company='_Test Company')
+ emp = make_employee("test_employee_6@salary.com")
+
+ timesheet = make_timesheet(emp, simulate=True, billable=1, project = '_Test Project', company='_Test Company')
sales_invoice = create_sales_invoice(do_not_save=True)
sales_invoice.project = '_Test Project'
sales_invoice.submit()
@@ -90,6 +100,8 @@
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
def test_timesheet_time_overlap(self):
+ emp = make_employee("test_employee_6@salary.com")
+
settings = frappe.get_single('Projects Settings')
initial_setting = settings.ignore_employee_time_overlap
settings.ignore_employee_time_overlap = 0
@@ -97,7 +109,7 @@
update_activity_type("_Test Activity Type")
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.append(
'time_logs',
{
@@ -129,12 +141,14 @@
settings.save()
def test_timesheet_std_working_hours(self):
+ emp = make_employee("test_employee_6@salary.com")
+
company = frappe.get_doc('Company', "_Test Company")
company.standard_working_hours = 8
company.save()
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
@@ -156,7 +170,7 @@
company.save()
timesheet = frappe.new_doc("Timesheet")
- timesheet.employee = "_T-Employee-00001"
+ timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 3eea390..defc18b 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -258,7 +258,12 @@
var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
- if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
+ if(!child.billable) {
+ frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
+ } else {
+ // bill all hours by default
+ frappe.model.set_value(cdt, cdn, "billing_hours", child.hours);
+ }
};
var update_time_rates = function(frm, cdt, cdn){
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/projects/report/project_summary/__init__.py
similarity index 100%
copy from erpnext/hr/report/department_analytics/__init__.py
copy to erpnext/projects/report/project_summary/__init__.py
diff --git a/erpnext/projects/report/project_summary/project_summary.js b/erpnext/projects/report/project_summary/project_summary.js
new file mode 100644
index 0000000..414b7b2
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Project Summary"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
+ },
+ {
+ "fieldname": "is_active",
+ "label": __("Is Active"),
+ "fieldtype": "Select",
+ "options": "\nYes\nNo",
+ "default": "Yes",
+ },
+ {
+ "fieldname": "status",
+ "label": __("Status"),
+ "fieldtype": "Select",
+ "options": "\nOpen\nCompleted\nCancelled",
+ "default": "Open"
+ },
+ {
+ "fieldname": "project_type",
+ "label": __("Project Type"),
+ "fieldtype": "Link",
+ "options": "Project Type"
+ },
+ {
+ "fieldname": "priority",
+ "label": __("Priority"),
+ "fieldtype": "Select",
+ "options": "\nLow\nMedium\nHigh"
+ }
+ ]
+};
diff --git a/erpnext/projects/report/project_summary/project_summary.json b/erpnext/projects/report/project_summary/project_summary.json
new file mode 100644
index 0000000..0b18b3e
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-05-04 19:31:54.575765",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-05-04 19:32:53.177213",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Project Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Project",
+ "report_name": "Project Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Projects User"
+ },
+ {
+ "role": "Projects Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
new file mode 100644
index 0000000..ea7f1ab
--- /dev/null
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -0,0 +1,155 @@
+# 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 = []
+
+ data = frappe.db.get_all("Project", filters=filters, fields=["name", 'status', "percent_complete", "expected_start_date", "expected_end_date", "project_type"], order_by="expected_end_date")
+
+ for project in data:
+ project["total_tasks"] = frappe.db.count("Task", filters={"project": project.name})
+ project["completed_tasks"] = frappe.db.count("Task", filters={"project": project.name, "status": "Completed"})
+ project["overdue_tasks"] = frappe.db.count("Task", filters={"project": project.name, "status": "Overdue"})
+
+ chart = get_chart_data(data)
+ report_summary = get_report_summary(data)
+
+ return columns, data, None, chart, report_summary
+
+def get_columns():
+ return [
+ {
+ "fieldname": "name",
+ "label": _("Project"),
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 200
+ },
+ {
+ "fieldname": "project_type",
+ "label": _("Type"),
+ "fieldtype": "Link",
+ "options": "Project Type",
+ "width": 120
+ },
+ {
+ "fieldname": "status",
+ "label": _("Status"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "total_tasks",
+ "label": _("Total Tasks"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "completed_tasks",
+ "label": _("Tasks Completed"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "overdue_tasks",
+ "label": _("Tasks Overdue"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "percent_complete",
+ "label": _("Completion"),
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "expected_start_date",
+ "label": _("Start Date"),
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "expected_end_date",
+ "label": _("End Date"),
+ "fieldtype": "Date",
+ "width": 120
+ },
+ ]
+
+def get_chart_data(data):
+ labels = []
+ total = []
+ completed = []
+ overdue = []
+
+ for project in data:
+ labels.append(project.name)
+ total.append(project.total_tasks)
+ completed.append(project.completed_tasks)
+ overdue.append(project.overdue_tasks)
+
+ return {
+ "data": {
+ 'labels': labels[:30],
+ 'datasets': [
+ {
+ "name": "Overdue",
+ "values": overdue[:30]
+ },
+ {
+ "name": "Completed",
+ "values": completed[:30]
+ },
+ {
+ "name": "Total Tasks",
+ "values": total[:30]
+ },
+ ]
+ },
+ "type": "bar",
+ "colors": ["#fc4f51", "#78d6ff", "#7575ff"],
+ "barOptions": {
+ "stacked": True
+ }
+ }
+
+def get_report_summary(data):
+ if not data:
+ return None
+
+ avg_completion = sum([project.percent_complete for project in data]) / len(data)
+ total = sum([project.total_tasks for project in data])
+ total_overdue = sum([project.overdue_tasks for project in data])
+ completed = sum([project.completed_tasks for project in data])
+
+ return [
+ {
+ "value": avg_completion,
+ "indicator": "Green" if avg_completion > 50 else "Red",
+ "label": "Average Completion",
+ "datatype": "Percent",
+ },
+ {
+ "value": total,
+ "indicator": "Blue",
+ "label": "Total Tasks",
+ "datatype": "Int",
+ },
+ {
+ "value": completed,
+ "indicator": "Green",
+ "label": "Completed Tasks",
+ "datatype": "Int",
+ },
+ {
+ "value": total_overdue,
+ "indicator": "Green" if total_overdue == 0 else "Red",
+ "label": "Overdue Tasks",
+ "datatype": "Int",
+ }
+ ]
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index e94d1ff..2695502 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -23,8 +23,6 @@
"public/js/queries.js",
"public/js/sms_manager.js",
"public/js/utils/party.js",
- "public/js/templates/address_list.html",
- "public/js/templates/contact_list.html",
"public/js/controllers/stock_controller.js",
"public/js/payment/payments.js",
"public/js/controllers/taxes_and_totals.js",
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 9c56189..a4cc68b 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -73,6 +73,8 @@
me.frm.set_query('contact_person', erpnext.queries.contact_query);
me.frm.set_query('supplier_address', erpnext.queries.address_query);
+ me.frm.set_query('billing_address', erpnext.queries.company_address_query);
+
if(this.frm.fields_dict.supplier) {
this.frm.set_query("supplier", function() {
return{ query: "erpnext.controllers.queries.supplier_query" }});
@@ -283,6 +285,11 @@
"shipping_address_display", true);
},
+ billing_address: function() {
+ erpnext.utils.get_address_display(this.frm, "billing_address",
+ "billing_address_display", true);
+ },
+
tc_name: function() {
this.get_terms();
},
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 28c2102..ca897dd 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -552,7 +552,8 @@
if (show_batch_dialog)
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
.then((r) => {
- if(r.message.has_batch_no || r.message.has_serial_no) {
+ if (r.message &&
+ (r.message.has_batch_no || r.message.has_serial_no)) {
frappe.flags.hide_serial_batch_dialog = false;
}
});
@@ -917,7 +918,7 @@
shipping_rule: function() {
var me = this;
- if(this.frm.doc.shipping_rule && this.frm.doc.shipping_address) {
+ if(this.frm.doc.shipping_rule) {
return this.frm.call({
doc: this.frm.doc,
method: "apply_shipping_rule",
@@ -1652,8 +1653,10 @@
if(!r.exc) {
$.each(me.frm.doc.items || [], function(i, item) {
if(item.item_code && r.message.hasOwnProperty(item.item_code)) {
- item.item_tax_template = r.message[item.item_code].item_tax_template;
- item.item_tax_rate = r.message[item.item_code].item_tax_rate;
+ if (!item.item_tax_template) {
+ item.item_tax_template = r.message[item.item_code].item_tax_template;
+ item.item_tax_rate = r.message[item.item_code].item_tax_rate;
+ }
me.add_taxes_from_item_tax_template(item.item_tax_rate);
} else {
item.item_tax_template = "";
@@ -1709,7 +1712,7 @@
},
set_gross_profit: function(item) {
- if (this.frm.doc.doctype == "Sales Order" && item.valuation_rate) {
+ if (["Sales Order", "Quotation"].includes(this.frm.doc.doctype) && item.valuation_rate) {
var rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1);
item.gross_profit = flt(((rate - item.valuation_rate) * item.stock_qty), precision("amount", item));
}
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 296c628..d89d471 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -47,6 +47,16 @@
// dropdown for links to other financial statements
erpnext.financial_statements.filters = get_filters()
+ let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
+
+ frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
+ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
+ frappe.query_report.set_filter_value({
+ period_start_date: fy.year_start_date,
+ period_end_date: fy.year_end_date
+ });
+ });
+
report.page.add_inner_button(__("Balance Sheet"), function() {
var filters = report.get_values();
frappe.set_route('query-report', 'Balance Sheet', {company: filters.company});
@@ -62,7 +72,7 @@
}
};
-function get_filters(){
+function get_filters() {
let filters = [
{
"fieldname":"company",
@@ -99,7 +109,6 @@
"fieldname":"period_start_date",
"label": __("Start Date"),
"fieldtype": "Date",
- "default": frappe.datetime.nowdate(),
"hidden": 1,
"reqd": 1
},
@@ -107,7 +116,6 @@
"fieldname":"period_end_date",
"label": __("End Date"),
"fieldtype": "Date",
- "default": frappe.datetime.add_months(frappe.datetime.nowdate(), 12),
"hidden": 1,
"reqd": 1
},
@@ -162,15 +170,6 @@
}
]
- erpnext.dimension_filters.forEach((dimension) => {
- filters.push({
- "fieldname": dimension["fieldname"],
- "label": __(dimension["label"]),
- "fieldtype": "Link",
- "options": dimension["document_type"]
- });
- });
-
return filters;
}
diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js
index cd767f5..c786a86 100644
--- a/erpnext/public/js/purchase_trends_filters.js
+++ b/erpnext/public/js/purchase_trends_filters.js
@@ -51,7 +51,10 @@
{ "value": "Supplier Group", "label": __("Supplier Group") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/public/js/sales_trends_filters.js b/erpnext/public/js/sales_trends_filters.js
index b272fdd..b9c4dca 100644
--- a/erpnext/public/js/sales_trends_filters.js
+++ b/erpnext/public/js/sales_trends_filters.js
@@ -27,7 +27,10 @@
{ "value": "Territory", "label": __("Territory") },
{ "value": "Project", "label": __("Project") }
],
- "default": "Item"
+ "default": "Item",
+ "dashboard_config": {
+ "read_only": 1,
+ }
},
{
"fieldname":"group_by",
diff --git a/erpnext/public/js/templates/address_list.html b/erpnext/public/js/templates/address_list.html
deleted file mode 100644
index 0f967b6..0000000
--- a/erpnext/public/js/templates/address_list.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<div class="clearfix"></div>
-{% for(var i=0, l=addr_list.length; i<l; i++) { %}
-<div class="address-box">
- <p class="h6">
- {%= i+1 %}. {%= addr_list[i].address_title %}{% if(addr_list[i].address_type!="Other") { %}
- <span class="text-muted">({%= __(addr_list[i].address_type) %})</span>{% } %}
- {% if(addr_list[i].is_primary_address) { %}
- <span class="text-muted">({%= __("Primary") %})</span>{% } %}
- {% if(addr_list[i].is_shipping_address) { %}
- <span class="text-muted">({%= __("Shipping") %})</span>{% } %}
-
- <a href="#Form/Address/{%= encodeURIComponent(addr_list[i].name) %}" class="btn btn-default btn-xs pull-right"
- style="margin-top:-3px; margin-right: -5px;">
- {%= __("Edit") %}</a>
- </p>
- <p>{%= addr_list[i].display %}</p>
-</div>
-{% } %}
-{% if(!addr_list.length) { %}
-<p class="text-muted small">{%= __("No address added yet.") %}</p>
-{% } %}
-<p><button class="btn btn-xs btn-default btn-address">{{ __("New Address") }}</button></p>
\ No newline at end of file
diff --git a/erpnext/public/js/templates/contact_list.html b/erpnext/public/js/templates/contact_list.html
deleted file mode 100644
index 7e69691..0000000
--- a/erpnext/public/js/templates/contact_list.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<div class="clearfix"></div>
-{% for(var i=0, l=contact_list.length; i<l; i++) { %}
- <div class="address-box">
- <p class="h6">
- {%= contact_list[i].first_name %} {%= contact_list[i].last_name %}
- {% if(contact_list[i].is_primary_contact) { %}
- <span class="text-muted">({%= __("Primary") %})</span>
- {% } %}
- {% if(contact_list[i].designation){ %}
- <span class="text-muted">– {%= contact_list[i].designation %}</span>
- {% } %}
- <a href="#Form/Contact/{%= encodeURIComponent(contact_list[i].name) %}"
- class="btn btn-xs btn-default pull-right"
- style="margin-top:-3px; margin-right: -5px;">
- {%= __("Edit") %}</a>
- </p>
- {% if (contact_list[i].phones || contact_list[i].email_ids) { %}
- <p>
- {% if(contact_list[i].phone) { %}
- {%= __("Phone") %}: {%= contact_list[i].phone %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
- {% endif %}
- {% if(contact_list[i].mobile_no) { %}
- {%= __("Mobile No") %}: {%= contact_list[i].mobile_no %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
- {% endif %}
- {% if(contact_list[i].phone_nos) { %}
- {% for(var j=0, k=contact_list[i].phone_nos.length; j<k; j++) { %}
- {%= __("Phone") %}: {%= contact_list[i].phone_nos[j].phone %}<br>
- {% } %}
- {% endif %}
- </p>
- <p>
- {% if(contact_list[i].email_id) { %}
- {%= __("Email") %}: {%= contact_list[i].email_id %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
- {% endif %}
- {% if(contact_list[i].email_ids) { %}
- {% for(var j=0, k=contact_list[i].email_ids.length; j<k; j++) { %}
- {%= __("Email") %}: {%= contact_list[i].email_ids[j].email_id %}<br>
- {% } %}
- {% endif %}
- </p>
- {% endif %}
- <p>
- {% if (contact_list[i].address) { %}
- {%= __("Address") %}: {%= contact_list[i].address %}<br>
- {% endif %}
- </p>
- </div>
-{% } %}
-{% if(!contact_list.length) { %}
-<p class="text-muted small">{%= __("No contacts added yet.") %}</p>
-{% } %}
-<p><button class="btn btn-xs btn-default btn-contact">
- {{ __("New Contact") }}</button>
-</p>
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 58969f2..bcab0d8 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -191,6 +191,23 @@
})
},
+ add_dimensions: function(report_name, index) {
+ let filters = frappe.query_reports[report_name].filters;
+
+ erpnext.dimension_filters.forEach((dimension) => {
+ let found = filters.some(el => el.fieldname === dimension['fieldname']);
+
+ if (!found) {
+ filters.splice(index, 0 ,{
+ "fieldname": dimension["fieldname"],
+ "label": __(dimension["label"]),
+ "fieldtype": "Link",
+ "options": dimension["document_type"]
+ });
+ }
+ });
+ },
+
make_subscription: function(doctype, docname) {
frappe.call({
method: "frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat",
@@ -470,7 +487,14 @@
fieldtype: 'Date',
fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date",
in_list_view: 1,
- label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date")
+ label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date"),
+ reqd: 1
+ })
+ fields.splice(3, 0, {
+ fieldtype: 'Float',
+ fieldname: "conversion_factor",
+ in_list_view: 1,
+ label: __("Conversion Factor")
})
}
@@ -519,6 +543,7 @@
"item_code": d.item_code,
"delivery_date": d.delivery_date,
"schedule_date": d.schedule_date,
+ "conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
});
diff --git a/erpnext/public/scss/website.scss b/erpnext/public/scss/website.scss
index 735b417..617e916 100644
--- a/erpnext/public/scss/website.scss
+++ b/erpnext/public/scss/website.scss
@@ -81,4 +81,10 @@
.place-order-container {
text-align: right;
+}
+
+.kb-card {
+ .card-body > .card-title {
+ line-height: 1.3;
+ }
}
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
index 0849fd7..7691fe3 100644
--- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
+++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
@@ -1,10 +1,12 @@
{
- "autoname": "format:MTNG-{date}",
+ "actions": [],
+ "autoname": "naming_series:",
"creation": "2018-10-15 16:25:41.548432",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
+ "naming_series",
"date",
"cb_00",
"status",
@@ -53,9 +55,16 @@
"fieldname": "sb_01",
"fieldtype": "Section Break",
"label": "Minutes"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "MTNG-.YYYY.-.MM.-.DD.-"
}
],
- "modified": "2019-07-13 19:57:40.500541",
+ "links": [],
+ "modified": "2020-05-19 13:18:59.821740",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Meeting",
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
index ded3a51..cf2644e 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
@@ -2,4 +2,13 @@
// For license information, please see license.txt
frappe.ui.form.on('Quality Procedure', {
+ refresh: function(frm) {
+ frm.set_query("procedure","processes", (frm) =>{
+ return {
+ filters: {
+ name: ["not in", [frm.parent_quality_procedure, frm.name]]
+ }
+ };
+ });
+ }
});
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
index 6df116c..b3c0d94 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
@@ -1,5 +1,6 @@
{
"actions": [],
+ "allow_rename": 1,
"autoname": "format:PRC-{quality_procedure_name}",
"creation": "2018-10-06 00:06:29.756804",
"doctype": "DocType",
@@ -72,7 +73,7 @@
],
"is_tree": 1,
"links": [],
- "modified": "2020-03-18 18:09:29.371627",
+ "modified": "2020-06-17 17:25:03.434953",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure",
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
index d29710d..1952e57 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
@@ -11,18 +11,18 @@
nsm_parent_field = 'parent_quality_procedure'
def before_save(self):
- for process in self.processes:
- if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- if doc.parent_quality_procedure:
- frappe.throw(_("{0} already has a Parent Procedure {1}.").format(process.procedure, doc.parent_quality_procedure))
- self.is_group = 1
+ self.check_for_incorrect_child()
def on_update(self):
self.set_parent()
def after_insert(self):
self.set_parent()
+ #if Child is Added through Tree View.
+ if self.parent_quality_procedure:
+ parent_quality_procedure = frappe.get_doc("Quality Procedure", self.parent_quality_procedure)
+ parent_quality_procedure.append("processes", {"procedure": self.name})
+ parent_quality_procedure.save()
def on_trash(self):
if self.parent_quality_procedure:
@@ -43,10 +43,20 @@
def set_parent(self):
for process in self.processes:
+ # Set parent for only those children who don't have a parent
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if not parent_quality_procedure and process.procedure:
+ frappe.db.set_value(self.doctype, process.procedure, "parent_quality_procedure", self.name)
+
+ def check_for_incorrect_child(self):
+ for process in self.processes:
if process.procedure:
- doc = frappe.get_doc("Quality Procedure", process.procedure)
- doc.parent_quality_procedure = self.name
- doc.save(ignore_permissions=True)
+ # Check if any child process belongs to another parent.
+ parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
+ if parent_quality_procedure and parent_quality_procedure != self.name:
+ frappe.throw(_("{0} already has a Parent Procedure {1}.".format(frappe.bold(process.procedure), frappe.bold(parent_quality_procedure))),
+ title=_("Invalid Child Procedure"))
+ self.is_group = 1
@frappe.whitelist()
def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False):
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
index 6df6f65..ef48ab6 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js
@@ -16,6 +16,7 @@
},
],
breadcrumb: "Setup",
+ disable_add_node: true,
root_label: "All Quality Procedures",
get_tree_root: false,
menu_items: [
diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
index 0a67fa5..3925dbb 100644
--- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
+++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
@@ -1,6 +1,8 @@
{
+ "actions": [],
"creation": "2019-05-26 00:10:00.248885",
"doctype": "DocType",
+ "editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"process_description",
@@ -23,7 +25,8 @@
}
],
"istable": 1,
- "modified": "2019-05-26 22:05:49.007189",
+ "links": [],
+ "modified": "2020-06-17 15:44:38.937915",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality Procedure Process",
diff --git a/erpnext/regional/address_template/templates/india.html b/erpnext/regional/address_template/templates/india.html
index ffb9d05..5d2329e 100644
--- a/erpnext/regional/address_template/templates/india.html
+++ b/erpnext/regional/address_template/templates/india.html
@@ -1,7 +1,7 @@
{{ address_line1 }}<br>{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}{{ city }}<br>
{% if gst_state %}{{ gst_state }}{% endif -%}
{% if gst_state_number %}, State Code: {{ gst_state_number }}<br>{% endif -%}
-{% if pincode %}PIN: {{ pincode }}<br>{% endif -%}
+{% if pincode %}Postal Code: {{ pincode }}<br>{% endif -%}
{{ country }}<br>
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 35f9cf6..888b2da 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -52,7 +52,7 @@
<td class="disabled"></td>
<td class="disabled"></td>
<tr>
- <td>(d) {{__("Inward Supplies(liable to reverse charge")}}</td>
+ <td>(d) {{__("Inward Supplies(liable to reverse charge)")}}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.txval, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.iamt, 2) }}</td>
<td class="right">{{ flt(data.sup_details.isup_rev.camt, 2) }}</td>
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 9e7a023..619734f 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -158,7 +158,7 @@
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
- self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y")
+ self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
self.set_itc_details(itc_details)
@@ -192,31 +192,27 @@
for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"])
- gst_category = "Registered Regular"
+ gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC':
reverse_charge = "Y"
+ itc_type = 'All Other ITC'
+ gst_category = ['Unregistered', 'Overseas']
else:
reverse_charge = "N"
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)
- d["iamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2)
- d["camt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2)
- d["samt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2)
- d["csamt"] += flt(itc_details.get((gst_category, itc_type, reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2)
-
- net_itc["iamt"] += flt(d["iamt"], 2)
- net_itc["camt"] += flt(d["camt"], 2)
- net_itc["samt"] += flt(d["samt"], 2)
- net_itc["csamt"] += flt(d["csamt"], 2)
+ for key in ['iamt', 'camt', 'samt', 'csamt']:
+ net_itc[key] += flt(d[key], 2)
for account_head in self.account_heads:
itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
- itc_inelg["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2)
- itc_inelg["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2)
- itc_inelg["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2)
- itc_inelg["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2)
+ for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
+ itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
@@ -274,17 +270,16 @@
""" #nosec
.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
- def get_itc_details(self, reverse_charge='N'):
-
+ 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
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
- where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
+ where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
and s.company_gstin = %s
group by t.account_head, s.gst_category, s.eligibility_for_itc
""",
- (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+ (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
itc_details = {}
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
index 59e955c..c1680c4 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
@@ -1,5 +1,4 @@
{
- "actions": [],
"creation": "2019-10-15 12:33:21.845329",
"doctype": "DocType",
"editable_grid": 1,
@@ -92,8 +91,7 @@
"label": "Upload XML Invoices"
}
],
- "links": [],
- "modified": "2019-12-10 16:37:26.793398",
+ "modified": "2020-05-25 21:32:49.064579",
"modified_by": "Administrator",
"module": "Regional",
"name": "Import Supplier Invoice",
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
index 6784ea8..31a7545 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
@@ -58,13 +58,14 @@
"naming_series": self.invoice_series,
"document_type": line.TipoDocumento.text,
"bill_date": get_datetime_str(line.Data.text),
- "invoice_no": line.Numero.text,
+ "bill_no": line.Numero.text,
"total_discount": 0,
"items": [],
"buying_price_list": self.default_buying_price_list
}
- if not invoices_args.get("invoice_no", ''): return
+ if not invoices_args.get("bill_no", ''):
+ frappe.throw(_("Numero has not set in the XML file"))
supp_dict = get_supplier_details(file_content)
invoices_args["destination_code"] = get_destination_code_from_file(file_content)
@@ -249,7 +250,7 @@
return existing_supplier_name
else:
-
+
new_supplier = frappe.new_doc("Supplier")
new_supplier.supplier_name = re.sub('&', '&', args.supplier)
new_supplier.supplier_group = supplier_group
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 732780a..9fe29eb 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -9,6 +9,8 @@
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.regional.india import number_state_mapping
from six import string_types
+from erpnext.accounts.general_ledger import make_gl_entries
+from erpnext.accounts.utils import get_account_currency
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -615,8 +617,9 @@
data.transDocDate = frappe.utils.formatdate(doc.lr_date, 'dd/mm/yyyy')
if doc.gst_transporter_id:
- validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID')
- data.transporterId = doc.gst_transporter_id
+ if doc.gst_transporter_id[0:2] != "88":
+ validate_gstin_check_digit(doc.gst_transporter_id, label='GST Transporter ID')
+ data.transporterId = doc.gst_transporter_id
return data
@@ -657,5 +660,53 @@
elif val:
gst_accounts[val] = acc
-
return gst_accounts
+
+def make_reverse_charge_entries(doc, method):
+ country = frappe.get_cached_value('Company', doc.company, 'country')
+
+ if country != 'India':
+ return
+
+ if doc.reverse_charge == 'Y':
+ gl_entries = []
+ gst_accounts = get_gst_accounts(doc.company)
+ gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
+
+ for tax in doc.get('taxes'):
+ if tax.category not in ("Total", "Valuation and Total"):
+ continue
+
+ if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
+ 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,
+ "credit": tax.base_tax_amount_after_discount_amount,
+ "credits_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)
+ )
+
+ gl_entries.append(doc.get_gl_dict(
+ {
+ "account": doc.credit_to if doc.doctype == 'Purchase Invoice' else doc.debit_to,
+ "cost_center": doc.cost_center,
+ "posting_date": doc.posting_date,
+ "party_type": 'Supplier',
+ "party": doc.supplier,
+ "against": tax.account_head,
+ "debit": tax.base_tax_amount_after_discount_amount,
+ "debit_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=doc)
+ )
+
+ make_gl_entries(gl_entries)
\ No newline at end of file
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index 2d0ad66..6ab7341 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -7,11 +7,13 @@
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.permissions import add_permission, update_permission_property
from erpnext.regional.italy import fiscal_regimes, tax_exemption_reasons, mode_of_payment_codes, vat_collectability_options
def setup(company=None, patch=True):
make_custom_fields()
setup_report()
+ add_permissions()
def make_custom_fields(update=True):
invoice_item_fields = [
@@ -200,3 +202,21 @@
dict(role='Accounts Manager')
]
)).insert()
+
+def add_permissions():
+ doctype = 'Import Supplier Invoice'
+ add_permission(doctype, 'All', 0)
+
+ for role in ('Accounts Manager', 'Accounts User','Purchase User', 'Auditor'):
+ add_permission(doctype, role, 0)
+ update_permission_property(doctype, role, 0, 'print', 1)
+ update_permission_property(doctype, role, 0, 'report', 1)
+
+ if role in ('Accounts Manager', 'Accounts User'):
+ update_permission_property(doctype, role, 0, 'write', 1)
+ update_permission_property(doctype, role, 0, 'create', 1)
+
+ update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1)
+ add_permission(doctype, 'Accounts Manager', 1)
+ update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1)
+ update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index fd1cc58..43b1ea8 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -117,12 +117,18 @@
else:
row.append(invoice_details.get(fieldname))
taxable_value = 0
+
+ if invoice in self.cgst_igst_invoices:
+ division_factor = 2
+ else:
+ division_factor = 1
+
for item_code, net_amount in self.invoice_items.get(invoice).items():
- if item_code in items:
- if self.item_tax_rate.get(invoice) and tax_rate in self.item_tax_rate.get(invoice, {}).get(item_code, []):
- taxable_value += abs(net_amount)
- elif not self.item_tax_rate.get(invoice):
- taxable_value += abs(net_amount)
+ if item_code in items:
+ if self.item_tax_rate.get(invoice) and tax_rate/division_factor in self.item_tax_rate.get(invoice, {}).get(item_code, []):
+ taxable_value += abs(net_amount)
+ elif not self.item_tax_rate.get(invoice):
+ taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]
@@ -196,7 +202,7 @@
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
sum(i.get('base_net_amount', 0) for i in items
- if i.item_code == d.item_code and i.parent == d.parent))
+ if i.item_code == d.item_code and i.parent == d.parent))
item_tax_rate = {}
@@ -221,6 +227,8 @@
self.items_based_on_tax_rate = {}
self.invoice_cess = frappe._dict()
+ self.cgst_igst_invoices = []
+
unidentified_gst_accounts = []
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
if account in self.gst_accounts.cess_account:
@@ -243,6 +251,8 @@
tax_rate = tax_amounts[0]
if cgst_or_sgst:
tax_rate *= 2
+ if parent not in self.cgst_igst_invoices:
+ self.cgst_igst_invoices.append(parent)
rate_based_dict = self.items_based_on_tax_rate\
.setdefault(parent, {}).setdefault(tax_rate, [])
diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py
index a01c6ce..772bbf5 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 erpnext.controllers.taxes_and_totals import get_itemised_tax
+from six import iteritems
def update_itemised_tax_data(doc):
if not doc.taxes: return
@@ -9,7 +11,14 @@
for row in doc.items:
tax_rate = 0.0
- if itemised_tax.get(row.item_code):
+ item_tax_rate = frappe.parse_json(row.item_tax_rate)
+
+ # First check if tax rate is present
+ # If not then look up in item_wise_tax_detail
+ if item_tax_rate:
+ for account, rate in iteritems(item_tax_rate):
+ tax_rate += rate
+ elif itemised_tax.get(row.item_code):
tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()])
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py
index 6d34402..cae28be 100644
--- a/erpnext/regional/united_states/setup.py
+++ b/erpnext/regional/united_states/setup.py
@@ -9,14 +9,14 @@
make_custom_fields()
add_print_formats()
-def make_custom_fields():
+def make_custom_fields(update=True):
custom_fields = {
'Supplier': [
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
label='Is IRS 1099 reporting required for supplier?')
]
}
- create_custom_fields(custom_fields)
+ create_custom_fields(custom_fields, update=update)
def add_print_formats():
frappe.reload_doc("regional", "print_format", "irs_1099_form")
diff --git a/erpnext/selling/desk_page/selling/selling.json b/erpnext/selling/desk_page/selling/selling.json
index a20806b..9ec6343 100644
--- a/erpnext/selling/desk_page/selling/selling.json
+++ b/erpnext/selling/desk_page/selling/selling.json
@@ -29,7 +29,7 @@
"category": "Modules",
"charts": [
{
- "chart_name": "Income",
+ "chart_name": "Incoming Bills (Purchase Invoice)",
"label": "Income"
}
],
@@ -39,11 +39,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 1,
"idx": 0,
"is_standard": 1,
"label": "Selling",
- "modified": "2020-04-01 11:28:51.047373",
+ "modified": "2020-06-03 13:23:24.861706",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",
@@ -52,18 +52,27 @@
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#ffe8cd",
+ "format": "{} Draft",
"label": "Sales Invoice",
"link_to": "Sales Invoice",
+ "stats_filter": "{ \"status\": \"Draft\" }",
"type": "DocType"
},
{
+ "color": "#ffe8cd",
+ "format": "{} To Deliver",
"label": "Sales Order",
"link_to": "Sales Order",
+ "stats_filter": "{\"Status\": \"To Deliver and Bill\"}",
"type": "DocType"
},
{
+ "color": "#cef6d1",
+ "format": "{} Open",
"label": "Quotation",
"link_to": "Quotation",
+ "stats_filter": "{ \"Status\": \"Open\" }",
"type": "DocType"
},
{
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index a6889e0..682dfed 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -3,16 +3,19 @@
from __future__ import unicode_literals
import frappe
+import json
from frappe.model.naming import set_name_by_naming_series
-from frappe import _, msgprint, throw
+from frappe import _, msgprint
import frappe.defaults
-from frappe.utils import flt, cint, cstr, today
+from frappe.utils import flt, cint, cstr, today, get_formatted_email
from frappe.desk.reportview import build_match_conditions, get_filters_cond
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
from frappe.model.rename_doc import update_linked_doctypes
from frappe.model.mapper import get_mapped_doc
+from frappe.utils.user import get_users_with_role
+
class Customer(TransactionBase):
def get_feed(self):
@@ -336,6 +339,7 @@
return lp_details
+@frappe.whitelist()
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
from erpnext.controllers.queries import get_fields
@@ -378,10 +382,45 @@
.format(customer, customer_outstanding, credit_limit))
# If not authorized person raise exception
- credit_controller = frappe.db.get_value('Accounts Settings', None, 'credit_controller')
- if not credit_controller or credit_controller not in frappe.get_roles():
- throw(_("Please contact to the user who have Sales Master Manager {0} role")
- .format(" / " + credit_controller if credit_controller else ""))
+ credit_controller_role = frappe.db.get_single_value('Accounts Settings', 'credit_controller')
+ if not credit_controller_role or credit_controller_role not in frappe.get_roles():
+ # form a list of emails for the credit controller users
+ credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
+
+ # form a list of emails and names to show to the user
+ credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})]
+ credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list]
+
+ if not credit_controller_users:
+ 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))
+
+ # if the current user does not have permissions to override credit limit,
+ # prompt them to send out an email to the controller users
+ frappe.msgprint(message,
+ title="Notify",
+ raise_exception=1,
+ primary_action={
+ 'label': 'Send Email',
+ 'server_action': 'erpnext.selling.doctype.customer.customer.send_emails',
+ 'args': {
+ 'customer': customer,
+ 'customer_outstanding': customer_outstanding,
+ 'credit_limit': credit_limit,
+ 'credit_controller_users_list': credit_controller_users_list
+ }
+ }
+ )
+
+@frappe.whitelist()
+def send_emails(args):
+ args = json.loads(args)
+ subject = (_("Credit limit reached for customer {0}").format(args.get('customer')))
+ message = (_("Credit limit has been crossed for customer {0} ({1}/{2})")
+ .format(args.get('customer'), args.get('customer_outstanding'), args.get('credit_limit')))
+ frappe.sendmail(recipients=[args.get('credit_controller_users_list')], subject=subject, message=message)
def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None):
# Outstanding based on GL Entries
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 7cfec5a..0e771c3 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -197,9 +197,9 @@
cond = "qo.docstatus = 1 and qo.status != 'Expired' and qo.valid_till < %s"
# check if those QUO have SO against it
so_against_quo = """
- SELECT
+ SELECT
so.name FROM `tabSales Order` so, `tabSales Order Item` so_item
- WHERE
+ WHERE
so_item.docstatus = 1 and so.docstatus = 1
and so_item.parent = so.name
and so_item.prevdoc_docname = qo.name"""
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index d50397c..59ae7b2 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -48,6 +48,10 @@
"base_net_amount",
"pricing_rules",
"is_free_item",
+ "section_break_43",
+ "valuation_rate",
+ "column_break_45",
+ "gross_profit",
"item_weight_details",
"weight_per_unit",
"total_weight",
@@ -602,12 +606,40 @@
"label": "Against Blanket Order",
"no_copy": 1,
"print_hide": 1
+ },
+ {
+ "fieldname": "section_break_43",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "valuation_rate",
+ "fieldtype": "Currency",
+ "label": "Valuation Rate",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
+ },
+ {
+ "fieldname": "column_break_45",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "gross_profit",
+ "fieldtype": "Currency",
+ "label": "Gross Profit",
+ "no_copy": 1,
+ "options": "Company:company:default_currency",
+ "print_hide": 1,
+ "read_only": 1,
+ "report_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-30 18:40:28.782720",
+ "modified": "2020-05-19 20:48:43.222229",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 45a43c5..705dcb8 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -34,6 +34,15 @@
}
};
})
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
refresh: function(frm) {
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 6462d3b..b57c4f3 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -282,6 +282,7 @@
"width": "100px"
},
{
+ "fetch_from": "customer.tax_id",
"fieldname": "tax_id",
"fieldtype": "Data",
"label": "Tax Id",
@@ -1196,7 +1197,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-17 12:50:39.640534",
+ "modified": "2020-05-19 21:39:19.486684",
"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 05e4aa8..ffb6635 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -868,7 +868,8 @@
],
"field_no_map": [
"rate",
- "price_list_rate"
+ "price_list_rate",
+ "item_tax_template"
],
"postprocess": update_item,
"condition": lambda doc: doc.ordered_qty < doc.qty and doc.supplier == supplier and doc.item_code in selected_items
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b8b0d40..74e742f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -400,6 +400,23 @@
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ def test_update_child_qty_rate_perm(self):
+ so = make_sales_order(item_code= "_Test Item", qty=4)
+
+ user = 'test@example.com'
+ test_user = frappe.get_doc('User', user)
+ test_user.add_roles("Accounts User")
+ frappe.set_user(user)
+
+ # update qty
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+
+ # add new item
+ trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
+ self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
+ frappe.set_user("Administrator")
+
def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 73f233c..eff17f8 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -72,6 +72,8 @@
"against_blanket_order",
"blanket_order",
"blanket_order_rate",
+ "manufacturing_section_section",
+ "bom_no",
"planning_section",
"projected_qty",
"actual_qty",
@@ -212,6 +214,7 @@
"fieldtype": "Link",
"label": "UOM",
"options": "UOM",
+ "print_hide": 0,
"reqd": 1
},
{
@@ -764,12 +767,25 @@
"fieldname": "against_blanket_order",
"fieldtype": "Check",
"label": "Against Blanket Order"
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "manufacturing_section_section",
+ "fieldtype": "Section Break",
+ "label": "Manufacturing Section"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-03-05 14:20:28.085117",
+ "modified": "2020-05-29 20:54:32.309460",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
index 88bd9c1..f15f63d 100644
--- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
+++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py
@@ -5,183 +5,183 @@
import calendar
import frappe
from frappe import _
-from frappe.utils import cint, cstr
+from frappe.utils import cint, cstr, getdate
def execute(filters=None):
- common_columns = [
- {
- 'label': _('New Customers'),
- 'fieldname': 'new_customers',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 125
- },
- {
- 'label': _('Repeat Customers'),
- 'fieldname': 'repeat_customers',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 125
- },
- {
- 'label': _('Total'),
- 'fieldname': 'total',
- 'fieldtype': 'Int',
- 'default': 0,
- 'width': 100
- },
- {
- 'label': _('New Customer Revenue'),
- 'fieldname': 'new_customer_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- },
- {
- 'label': _('Repeat Customer Revenue'),
- 'fieldname': 'repeat_customer_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- },
- {
- 'label': _('Total Revenue'),
- 'fieldname': 'total_revenue',
- 'fieldtype': 'Currency',
- 'default': 0.0,
- 'width': 175
- }
- ]
- if filters.get('view_type') == 'Monthly':
- return get_data_by_time(filters, common_columns)
- else:
- return get_data_by_territory(filters, common_columns)
+ common_columns = [
+ {
+ 'label': _('New Customers'),
+ 'fieldname': 'new_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Repeat Customers'),
+ 'fieldname': 'repeat_customers',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 125
+ },
+ {
+ 'label': _('Total'),
+ 'fieldname': 'total',
+ 'fieldtype': 'Int',
+ 'default': 0,
+ 'width': 100
+ },
+ {
+ 'label': _('New Customer Revenue'),
+ 'fieldname': 'new_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Repeat Customer Revenue'),
+ 'fieldname': 'repeat_customer_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ },
+ {
+ 'label': _('Total Revenue'),
+ 'fieldname': 'total_revenue',
+ 'fieldtype': 'Currency',
+ 'default': 0.0,
+ 'width': 175
+ }
+ ]
+ if filters.get('view_type') == 'Monthly':
+ return get_data_by_time(filters, common_columns)
+ else:
+ return get_data_by_territory(filters, common_columns)
def get_data_by_time(filters, common_columns):
- # key yyyy-mm
- columns = [
- {
- 'label': _('Year'),
- 'fieldname': 'year',
- 'fieldtype': 'Data',
- 'width': 100
- },
- {
- 'label': _('Month'),
- 'fieldname': 'month',
- 'fieldtype': 'Data',
- 'width': 100
- },
- ]
- columns += common_columns
+ # key yyyy-mm
+ columns = [
+ {
+ 'label': _('Year'),
+ 'fieldname': 'year',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ {
+ 'label': _('Month'),
+ 'fieldname': 'month',
+ 'fieldtype': 'Data',
+ 'width': 100
+ },
+ ]
+ columns += common_columns
- customers_in = get_customer_stats(filters)
+ customers_in = get_customer_stats(filters)
- # time series
- from_year, from_month, temp = filters.get('from_date').split('-')
- to_year, to_month, temp = filters.get('to_date').split('-')
+ # time series
+ from_year, from_month, temp = filters.get('from_date').split('-')
+ to_year, to_month, temp = filters.get('to_date').split('-')
- from_year, from_month, to_year, to_month = \
- cint(from_year), cint(from_month), cint(to_year), cint(to_month)
+ from_year, from_month, to_year, to_month = \
+ cint(from_year), cint(from_month), cint(to_year), cint(to_month)
- out = []
- for year in range(from_year, to_year+1):
- for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
- key = '{year}-{month:02d}'.format(year=year, month=month)
- data = customers_in.get(key)
- new = data['new'] if data else [0, 0.0]
- repeat = data['repeat'] if data else [0, 0.0]
- out.append({
- 'year': cstr(year),
- 'month': calendar.month_name[month],
- 'new_customers': new[0],
- 'repeat_customers': repeat[0],
- 'total': new[0] + repeat[0],
- 'new_customer_revenue': new[1],
- 'repeat_customer_revenue': repeat[1],
- 'total_revenue': new[1] + repeat[1]
- })
- return columns, out
+ out = []
+ for year in range(from_year, to_year+1):
+ for month in range(from_month if year==from_year else 1, (to_month+1) if year==to_year else 13):
+ key = '{year}-{month:02d}'.format(year=year, month=month)
+ data = customers_in.get(key)
+ new = data['new'] if data else [0, 0.0]
+ repeat = data['repeat'] if data else [0, 0.0]
+ out.append({
+ 'year': cstr(year),
+ 'month': calendar.month_name[month],
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1]
+ })
+ return columns, out
def get_data_by_territory(filters, common_columns):
- columns = [{
- 'label': 'Territory',
- 'fieldname': 'territory',
- 'fieldtype': 'Link',
- 'options': 'Territory',
- 'width': 150
- }]
- columns += common_columns
+ columns = [{
+ 'label': 'Territory',
+ 'fieldname': 'territory',
+ 'fieldtype': 'Link',
+ 'options': 'Territory',
+ 'width': 150
+ }]
+ columns += common_columns
- customers_in = get_customer_stats(filters, tree_view=True)
+ customers_in = get_customer_stats(filters, tree_view=True)
- territory_dict = {}
- for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
- territory_dict.update({
- t.name: {
- 'parent': t.parent_territory,
- 'is_group': t.is_group
- }
- })
+ territory_dict = {}
+ for t in frappe.db.sql('''SELECT name, lft, parent_territory, is_group FROM `tabTerritory` ORDER BY lft''', as_dict=1):
+ territory_dict.update({
+ t.name: {
+ 'parent': t.parent_territory,
+ 'is_group': t.is_group
+ }
+ })
- depth_map = frappe._dict()
- for name, info in territory_dict.items():
- default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
- depth_map.setdefault(name, default)
+ depth_map = frappe._dict()
+ for name, info in territory_dict.items():
+ default = depth_map.get(info['parent']) + 1 if info['parent'] else 0
+ depth_map.setdefault(name, default)
- data = []
- for name, indent in depth_map.items():
- condition = customers_in.get(name)
- new = customers_in[name]['new'] if condition else [0, 0.0]
- repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
- temp = {
- 'territory': name,
- 'parent_territory': territory_dict[name]['parent'],
- 'indent': indent,
- 'new_customers': new[0],
- 'repeat_customers': repeat[0],
- 'total': new[0] + repeat[0],
- 'new_customer_revenue': new[1],
- 'repeat_customer_revenue': repeat[1],
- 'total_revenue': new[1] + repeat[1],
- 'bold': 0 if indent else 1
- }
- data.append(temp)
+ data = []
+ for name, indent in depth_map.items():
+ condition = customers_in.get(name)
+ new = customers_in[name]['new'] if condition else [0, 0.0]
+ repeat = customers_in[name]['repeat'] if condition else [0, 0.0]
+ temp = {
+ 'territory': name,
+ 'parent_territory': territory_dict[name]['parent'],
+ 'indent': indent,
+ 'new_customers': new[0],
+ 'repeat_customers': repeat[0],
+ 'total': new[0] + repeat[0],
+ 'new_customer_revenue': new[1],
+ 'repeat_customer_revenue': repeat[1],
+ 'total_revenue': new[1] + repeat[1],
+ 'bold': 0 if indent else 1
+ }
+ data.append(temp)
- loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
+ loop_data = sorted(data, key=lambda k: k['indent'], reverse=True)
- for ld in loop_data:
- if ld['parent_territory']:
- parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
- for key in parent_data.keys():
- if key not in ['indent', 'territory', 'parent_territory', 'bold']:
- parent_data[key] += ld[key]
+ for ld in loop_data:
+ if ld['parent_territory']:
+ parent_data = [x for x in data if x['territory'] == ld['parent_territory']][0]
+ for key in parent_data.keys():
+ if key not in ['indent', 'territory', 'parent_territory', 'bold']:
+ parent_data[key] += ld[key]
- return columns, data, None, None, None, 1
+ return columns, data, None, None, None, 1
def get_customer_stats(filters, tree_view=False):
- """ Calculates number of new and repeated customers. """
- company_condition = ''
- if filters.get('company'):
- company_condition = ' and company=%(company)s'
+ """ Calculates number of new and repeated customers and revenue. """
+ company_condition = ''
+ if filters.get('company'):
+ company_condition = ' and company=%(company)s'
- customers = []
- customers_in = {}
+ customers = []
+ customers_in = {}
- for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
- where docstatus=1 and posting_date <= %(to_date)s and posting_date >= %(from_date)s
- {company_condition} order by posting_date'''.format(company_condition=company_condition),
- filters, as_dict=1):
+ for si in frappe.db.sql('''select territory, posting_date, customer, base_grand_total from `tabSales Invoice`
+ where docstatus=1 and posting_date <= %(to_date)s
+ {company_condition} order by posting_date'''.format(company_condition=company_condition),
+ filters, as_dict=1):
- key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
- customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
+ key = si.territory if tree_view else si.posting_date.strftime('%Y-%m')
+ new_or_repeat = 'new' if si.customer not in customers else 'repeat'
+ customers_in.setdefault(key, {'new': [0, 0.0], 'repeat': [0, 0.0]})
- if not si.customer in customers:
- customers_in[key]['new'][0] += 1
- customers_in[key]['new'][1] += si.base_grand_total
- customers.append(si.customer)
- else:
- customers_in[key]['repeat'][0] += 1
- customers_in[key]['repeat'][1] += si.base_grand_total
+ # if filters.from_date <= si.posting_date.strftime('%Y-%m-%d'):
+ if getdate(filters.from_date) <= getdate(si.posting_date):
+ customers_in[key][new_or_repeat][0] += 1
+ customers_in[key][new_or_repeat][1] += si.base_grand_total
+ if new_or_repeat == 'new':
+ customers.append(si.customer)
- return customers_in
+ return customers_in
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 0fbe49e..875904f 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -107,6 +107,9 @@
erpnext.company.set_chart_of_accounts_options(frm.doc);
+ if (!frappe.user.has_role('System Manager')) {
+ frm.get_field("delete_company_transactions").hide();
+ }
},
make_default_tax_template: function(frm) {
@@ -134,7 +137,7 @@
var d = frappe.prompt({
fieldtype:"Data",
fieldname: "company_name",
- label: __("Please re-type company name to confirm"),
+ label: __("Please enter the company name to confirm"),
reqd: 1,
description: __("Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.")
},
diff --git a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
index aba6a79..28d1d16 100644
--- a/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
+++ b/erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:title",
@@ -49,7 +50,7 @@
"fieldname": "terms_and_conditions_help",
"fieldtype": "HTML",
"label": "Terms and Conditions Help",
- "options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Langauge. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
+ "options": "<h4>Standard Terms and Conditions Example</h4>\n\n<pre>Delivery Terms for Order number {{ name }}\n\n-Order Date : {{ transaction_date }} \n-Expected Delivery Date : {{ delivery_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The fieldnames you can use in your email template are the fields in the document from which you are sending the email. You can find out the fields of any documents via Setup > Customize Form View and selecting the document type (e.g. Sales Invoice)</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>"
},
{
"fieldname": "applicable_modules_section",
@@ -81,7 +82,8 @@
],
"icon": "icon-legal",
"idx": 1,
- "modified": "2019-07-04 13:31:30.393425",
+ "links": [],
+ "modified": "2020-06-16 22:54:38.094844",
"modified_by": "Administrator",
"module": "Setup",
"name": "Terms and Conditions",
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index e666a41..74ff0ec 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -105,3 +105,4 @@
"ref_doctype": "Company"
})
settings.save()
+
diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py
new file mode 100644
index 0000000..9ce64eb
--- /dev/null
+++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py
@@ -0,0 +1,151 @@
+from __future__ import unicode_literals
+from frappe import _
+import frappe
+import json
+
+def get_company_for_dashboards():
+ company = frappe.defaults.get_defaults().company
+ if company:
+ return company
+ else:
+ company_list = frappe.get_list("Company")
+ if company_list:
+ return company_list[0].name
+ return None
+
+def get_default_dashboards():
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ income_account = company.default_income_account or get_account("Income Account", company.name)
+ expense_account = company.default_expense_account or get_account("Expense Account", company.name)
+ bank_account = company.default_bank_account or get_account("Bank", company.name)
+
+ return {
+ "Dashboards": [
+ {
+ "doctype": "Dashboard",
+ "dashboard_name": "Accounts",
+ "charts": [
+ { "chart": "Outgoing Bills (Sales Invoice)" },
+ { "chart": "Incoming Bills (Purchase Invoice)" },
+ { "chart": "Bank Balance" },
+ { "chart": "Income" },
+ { "chart": "Expenses" },
+ { "chart": "Patient Appointments" }
+ ]
+ },
+ {
+ "doctype": "Dashboard",
+ "dashboard_name": "Project",
+ "charts": [
+ { "chart": "Project Summary", "width": "Full" }
+ ]
+ },
+ ],
+ "Charts": [
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Income",
+ "timespan": "Last Year",
+ "color": None,
+ "filters_json": json.dumps({"company": company.name, "account": income_account}),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Expenses",
+ "timespan": "Last Year",
+ "color": None,
+ "filters_json": json.dumps({"company": company.name, "account": expense_account}),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Quarterly",
+ "chart_name": "Bank Balance",
+ "timespan": "Last Year",
+ "color": "#ffb868",
+ "filters_json": json.dumps({"company": company.name, "account": bank_account}),
+ "source": "Account Balance Timeline",
+ "chart_type": "Custom",
+ "timeseries": 1,
+ "owner": "Administrator",
+ "type": "Line",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Monthly",
+ "chart_name": "Incoming Bills (Purchase Invoice)",
+ "timespan": "Last Year",
+ "color": "#a83333",
+ "value_based_on": "base_grand_total",
+ "filters_json": json.dumps({}),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Purchase Invoice",
+ "type": "Bar",
+ "width": "Half"
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Monthly",
+ "chart_name": "Outgoing Bills (Sales Invoice)",
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_grand_total",
+ "filters_json": json.dumps({}),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Sales Invoice",
+ "type": "Bar",
+ "width": "Half"
+ },
+ {
+ 'doctype': 'Dashboard Chart',
+ 'name': 'Project Summary',
+ 'chart_name': 'Project Summary',
+ 'chart_type': 'Report',
+ 'report_name': 'Project Summary',
+ 'is_public': 1,
+ 'filters_json': json.dumps({"company": company.name, "status": "Open"}),
+ 'type': 'Bar',
+ 'custom_options': '{"type": "bar", "colors": ["#fc4f51", "#78d6ff", "#7575ff"], "axisOptions": { "shortenYAxisNumbers": 1}, "barOptions": { "stacked": 1 }}',
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "time_interval": "Daily",
+ "chart_name": "Patient Appointments",
+ "timespan": "Last Month",
+ "color": "#77ecca",
+ "filters_json": json.dumps({}),
+ "chart_type": "Count",
+ "timeseries": 1,
+ "based_on": "appointment_datetime",
+ "owner": "Administrator",
+ "document_type": "Patient Appointment",
+ "type": "Line",
+ "width": "Half"
+ }
+ ]
+ }
+
+def get_account(account_type, company):
+ accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
+ if accounts:
+ return accounts[0].name
diff --git a/erpnext/setup/setup_wizard/data/uom_conversion_data.json b/erpnext/setup/setup_wizard/data/uom_conversion_data.json
index 174ecd5..27a917d 100644
--- a/erpnext/setup/setup_wizard/data/uom_conversion_data.json
+++ b/erpnext/setup/setup_wizard/data/uom_conversion_data.json
@@ -1571,5 +1571,19 @@
"to_uom": "Parts Per Million",
"abbr": "ppm",
"value": "10000"
+ },
+ {
+ "category": "Mass",
+ "from_uom": "Pound",
+ "to_uom": "Ounce",
+ "abbr": "oz",
+ "value": "16"
+ },
+ {
+ "category": "Mass",
+ "from_uom": "Gram",
+ "to_uom": "Ounce",
+ "abbr": "oz",
+ "value": "0.035274"
}
]
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 8bb0a05..0d70d91 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -336,13 +336,14 @@
"category_name": _(d.get("category"))
}).insert(ignore_permissions=True)
- uom_conversion = frappe.get_doc({
- "doctype": "UOM Conversion Factor",
- "category": _(d.get("category")),
- "from_uom": _(d.get("from_uom")),
- "to_uom": _(d.get("to_uom")),
- "value": d.get("value")
- }).insert(ignore_permissions=True)
+ if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}):
+ uom_conversion = frappe.get_doc({
+ "doctype": "UOM Conversion Factor",
+ "category": _(d.get("category")),
+ "from_uom": _(d.get("from_uom")),
+ "to_uom": _(d.get("to_uom")),
+ "value": d.get("value")
+ }).insert(ignore_permissions=True)
def add_market_segments():
records = [
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 4ac546e..a7e8388 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -42,9 +42,9 @@
return {
"doc": decorate_quotation_doc(doc),
- "shipping_addresses": [{"name": address.name, "display": address.display}
+ "shipping_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
for address in addresses if address.address_type == "Shipping"],
- "billing_addresses": [{"name": address.name, "display": address.display}
+ "billing_addresses": [{"name": address.name, "title": address.address_title, "display": address.display}
for address in addresses if address.address_type == "Billing"],
"shipping_rules": get_applicable_shipping_rules(party),
"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
@@ -78,8 +78,10 @@
if is_stock_item:
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
+ if not cint(item_stock.in_stock):
+ throw(_("{1} Not in Stock").format(item.item_code))
if item.qty > item_stock.stock_qty[0][0]:
- throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
+ throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
sales_order.flags.ignore_permissions = True
sales_order.insert()
@@ -319,7 +321,7 @@
def set_price_list_and_rate(quotation, cart_settings):
"""set price list based on billing territory"""
- _set_price_list(quotation, cart_settings)
+ _set_price_list(cart_settings, quotation)
# reset values
quotation.price_list_currency = quotation.currency = \
@@ -334,23 +336,24 @@
# set it in cookies for using in product page
frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list)
-def _set_price_list(quotation, cart_settings):
+def _set_price_list(cart_settings, quotation=None):
"""Set price list based on customer or shopping cart default"""
from erpnext.accounts.party import get_default_price_list
-
- # check if customer price list exists
+ party_name = quotation.get("party_name") if quotation else get_party().get("name")
selling_price_list = None
- if quotation.party_name:
- selling_price_list = frappe.db.get_value('Customer', quotation.party_name, 'default_price_list')
- # else check for territory based price list
+ # check if default customer price list exists
+ if party_name:
+ selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name))
+
+ # check default price list in shopping cart
if not selling_price_list:
selling_price_list = cart_settings.price_list
- if not selling_price_list and quotation.party_name:
- selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
+ if quotation:
+ quotation.selling_price_list = selling_price_list
- quotation.selling_price_list = selling_price_list
+ return selling_price_list
def set_taxes(quotation, cart_settings):
"""set taxes based on billing territory"""
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index 21ee335..7c08f5b 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from erpnext.shopping_cart.cart import _get_cart_quotation
+from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \
import get_shopping_cart_settings, show_quantity_in_website
from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
@@ -21,9 +21,11 @@
if not skip_quotation_creation:
cart_quotation = _get_cart_quotation()
+ selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
+
price = get_price(
item_code,
- cart_quotation.selling_price_list,
+ selling_price_list,
cart_settings.default_customer_group,
cart_settings.company
)
@@ -42,7 +44,7 @@
if product_info["price"]:
if frappe.session.user != "Guest":
- item = cart_quotation.get({"item_code": item_code})
+ item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None
if item:
product_info["qty"] = item[0].qty
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 4ca43a8..2b80fb8 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -10,7 +10,6 @@
"""boot session - send website info if guest"""
bootinfo.custom_css = frappe.db.get_value('Style Settings', None, 'custom_css') or ''
- bootinfo.website_settings = frappe.get_doc('Website Settings')
if frappe.session['user']!='Guest':
update_page_info(bootinfo)
diff --git a/erpnext/startup/filters.py b/erpnext/startup/filters.py
new file mode 100644
index 0000000..a99e49b
--- /dev/null
+++ b/erpnext/startup/filters.py
@@ -0,0 +1,14 @@
+
+import frappe
+
+def get_filters_config():
+ filters_config = {
+ "fiscal year": {
+ "label": "Fiscal Year",
+ "get_field": "erpnext.accounts.utils.get_fiscal_year_filter_field",
+ "valid_for_fieldtypes": ["Date", "Datetime", "DateRange"],
+ "depends_on": "company",
+ }
+ }
+
+ return filters_config
\ No newline at end of file
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/__init__.py b/erpnext/stock/dashboard_chart_source/__init__.py
similarity index 100%
rename from erpnext/stock/report/purchase_order_items_to_be_received/__init__.py
rename to erpnext/stock/dashboard_chart_source/__init__.py
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/__init__.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
similarity index 100%
copy from erpnext/stock/report/purchase_order_items_to_be_received/__init__.py
copy to erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/__init__.py
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
new file mode 100644
index 0000000..a413754
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.js
@@ -0,0 +1,14 @@
+frappe.provide('frappe.dashboards.chart_sources');
+
+frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = {
+ method: "erpnext.stock.dashboard_chart_source.warehouse_wise_stock_value.warehouse_wise_stock_value.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ }
+ ]
+};
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
new file mode 100644
index 0000000..6d967c0
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.json
@@ -0,0 +1,13 @@
+{
+ "creation": "2020-05-14 14:27:44.108017",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2020-05-14 14:27:44.108017",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Warehouse wise Stock Value",
+ "owner": "Administrator",
+ "source_name": "Warehouse wise Stock Value ",
+ "timeseries": 0
+}
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
new file mode 100644
index 0000000..374a34e
--- /dev/null
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
@@ -0,0 +1,48 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe, json
+from frappe import _
+from frappe.utils.dashboard import cache_source
+from erpnext.stock.utils import get_stock_value_from_bin
+
+@frappe.whitelist()
+@cache_source
+def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
+ to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+ labels, datapoints = [], []
+ filters = frappe.parse_json(filters)
+
+ warehouse_filters = [['is_group', '=', 0]]
+ if filters and filters.get("company"):
+ warehouse_filters.append(['company', '=', filters.get("company")])
+
+ warehouses = frappe.get_list("Warehouse", fields=['name'], filters=warehouse_filters, order_by='name')
+
+ for wh in warehouses:
+ balance = get_stock_value_from_bin(warehouse=wh.name)
+ wh["balance"] = balance[0][0]
+
+ warehouses = [x for x in warehouses if not (x.get('balance') == None)]
+
+ if not warehouses:
+ return []
+
+ sorted_warehouse_map = sorted(warehouses, key = lambda i: i['balance'], reverse=True)
+
+ if len(sorted_warehouse_map) > 10:
+ sorted_warehouse_map = sorted_warehouse_map[:10]
+
+ for warehouse in sorted_warehouse_map:
+ labels.append(_(warehouse.get("name")))
+ datapoints.append(warehouse.get("balance"))
+
+ return{
+ "labels": labels,
+ "datasets": [{
+ "name": _("Stock Value"),
+ "values": datapoints
+ }],
+ "type": "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/dashboard_fixtures.py b/erpnext/stock/dashboard_fixtures.py
new file mode 100644
index 0000000..7625b1a
--- /dev/null
+++ b/erpnext/stock/dashboard_fixtures.py
@@ -0,0 +1,170 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+import json
+from frappe import _
+from frappe.utils import nowdate
+from erpnext.accounts.dashboard_fixtures import _get_fiscal_year
+from erpnext.buying.dashboard_fixtures import get_company_for_dashboards
+
+def get_data():
+ fiscal_year = _get_fiscal_year(nowdate())
+
+ if not fiscal_year:
+ return frappe._dict()
+
+ company = frappe.get_doc("Company", get_company_for_dashboards())
+ fiscal_year_name = fiscal_year.get("name")
+ start_date = str(fiscal_year.get("year_start_date"))
+ end_date = str(fiscal_year.get("year_end_date"))
+
+ return frappe._dict({
+ "dashboards": get_dashboards(),
+ "charts": get_charts(company, fiscal_year_name, start_date, end_date),
+ "number_cards": get_number_cards(company, fiscal_year_name, start_date, end_date),
+ })
+
+def get_dashboards():
+ return [{
+ "name": "Stock",
+ "dashboard_name": "Stock",
+ "charts": [
+ { "chart": "Warehouse wise Stock Value", "width": "Full"},
+ { "chart": "Purchase Receipt Trends", "width": "Half"},
+ { "chart": "Delivery Trends", "width": "Half"},
+ { "chart": "Oldest Items", "width": "Half"},
+ { "chart": "Item Shortage Summary", "width": "Half"}
+ ],
+ "cards": [
+ { "card": "Total Active Items"},
+ { "card": "Total Warehouses"},
+ { "card": "Total Stock Value"}
+ ]
+ }]
+
+def get_charts(company, fiscal_year_name, start_date, end_date):
+ return [
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Purchase Receipt Trends",
+ "time_interval": "Monthly",
+ "chart_name": _("Purchase Receipt Trends"),
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Purchase Receipt", "docstatus", "=", 1]]),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Purchase Receipt",
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
+ },
+ {
+ "doctype": "Dashboard Chart",
+ "name": "Delivery Trends",
+ "time_interval": "Monthly",
+ "chart_name": _("Delivery Trends"),
+ "timespan": "Last Year",
+ "color": "#7b933d",
+ "value_based_on": "base_net_total",
+ "filters_json": json.dumps([["Delivery Note", "docstatus", "=", 1]]),
+ "chart_type": "Sum",
+ "timeseries": 1,
+ "based_on": "posting_date",
+ "owner": "Administrator",
+ "document_type": "Delivery Note",
+ "type": "Bar",
+ "width": "Half",
+ "is_public": 1
+ },
+ {
+ "name": "Warehouse wise Stock Value",
+ "chart_name": _("Warehouse wise Stock Value"),
+ "chart_type": "Custom",
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({}),
+ "is_custom": 0,
+ "is_public": 1,
+ "owner": "Administrator",
+ "source": "Warehouse wise Stock Value",
+ "type": "Bar"
+ },
+ {
+ "name": "Oldest Items",
+ "chart_name": _("Oldest Items"),
+ "chart_type": "Report",
+ "custom_options": json.dumps({
+ "colors": ["#5e64ff"]
+ }),
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name,
+ "to_date": nowdate(),
+ "show_warehouse_wise_stock": 0
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Stock Ageing",
+ "type": "Bar"
+ },
+ {
+ "name": "Item Shortage Summary",
+ "chart_name": _("Item Shortage Summary"),
+ "chart_type": "Report",
+ "doctype": "Dashboard Chart",
+ "filters_json": json.dumps({
+ "company": company.name
+ }),
+ "is_custom": 1,
+ "is_public": 1,
+ "owner": "Administrator",
+ "report_name": "Item Shortage Report",
+ "type": "Bar"
+ }
+ ]
+
+def get_number_cards(company, fiscal_year_name, start_date, end_date):
+ return [
+ {
+ "name": "Total Active Items",
+ "label": _("Total Active Items"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Item",
+ "filters_json": json.dumps([["Item", "disabled", "=", 0]]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "name": "Total Warehouses",
+ "label": _("Total Warehouses"),
+ "function": "Count",
+ "doctype": "Number Card",
+ "document_type": "Warehouse",
+ "filters_json": json.dumps([["Warehouse", "disabled", "=", 0]]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Monthly"
+ },
+ {
+ "name": "Total Stock Value",
+ "label": _("Total Stock Value"),
+ "function": "Sum",
+ "aggregate_function_based_on": "stock_value",
+ "doctype": "Number Card",
+ "document_type": "Bin",
+ "filters_json": json.dumps([]),
+ "is_public": 1,
+ "owner": "Administrator",
+ "show_percentage_stats": 1,
+ "stats_time_interval": "Daily"
+ }
+ ]
\ No newline at end of file
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 38475a6..9404292 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -2,8 +2,13 @@
"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]"
+ },
+ {
+ "hidden": 0,
"label": "Stock Transactions",
- "links": "[\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\": \"Material Request\",\n \"name\": \"Material Request\",\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]"
+ "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,
@@ -13,12 +18,7 @@
{
"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\": \"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\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
- },
- {
- "hidden": 0,
- "label": "Items and Pricing",
- "links": "[\n {\n \"label\": \"Item\",\n \"name\": \"Item\",\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 \"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\": \"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\": \"Item Variant Settings\",\n \"name\": \"Item Variant Settings\",\n \"type\": \"doctype\"\n }\n]"
+ "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]"
},
{
"hidden": 0,
@@ -33,7 +33,7 @@
{
"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 \"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 \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Purchase Receipt\"\n ],\n \"doctype\": \"Purchase Receipt\",\n \"is_query_report\": true,\n \"label\": \"Purchase Order Items To Be Received\",\n \"name\": \"Purchase Order Items To Be Received\",\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]"
+ "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 \"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 \"Delivery Note\"\n ],\n \"doctype\": \"Delivery Note\",\n \"is_query_report\": true,\n \"label\": \"Ordered Items To Be Delivered\",\n \"name\": \"Ordered Items To Be Delivered\",\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,
@@ -41,34 +41,46 @@
"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": [],
+ "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,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Stock",
- "modified": "2020-04-01 11:28:51.148421",
+ "modified": "2020-05-27 20:38:25.255323",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
+ "onboarding": "Stock",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#cef6d1",
+ "format": "{} Available",
"label": "Item",
"link_to": "Item",
+ "stats_filter": "{\n \"disabled\" : 0\n}",
"type": "DocType"
},
{
- "label": "Pricing Rule",
- "link_to": "Pricing Rule",
+ "color": "#ffe8cd",
+ "format": "{} Pending",
+ "label": "Material Request",
+ "link_to": "Material Request",
+ "stats_filter": "{\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%'],\n \"status\": \"Pending\"\n}",
"type": "DocType"
},
{
@@ -77,6 +89,22 @@
"type": "DocType"
},
{
+ "color": "#ffe8cd",
+ "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": "#ffe8cd",
+ "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"
@@ -85,6 +113,12 @@
"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/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 9f5dee9..84d2057 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -24,10 +24,10 @@
"return_against",
"customer_po_details",
"po_no",
- "section_break_18",
- "pick_list",
"column_break_17",
"po_date",
+ "section_break_18",
+ "pick_list",
"contact_info",
"shipping_address_name",
"shipping_address",
@@ -296,7 +296,6 @@
},
{
"collapsible": 1,
- "collapsible_depends_on": "po_no",
"fieldname": "customer_po_details",
"fieldtype": "Section Break",
"label": "Customer PO Details"
@@ -304,7 +303,7 @@
{
"allow_on_submit": 1,
"fieldname": "po_no",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Customer's Purchase Order No",
"no_copy": 1,
"oldfieldname": "po_no",
@@ -318,7 +317,6 @@
"fieldtype": "Column Break"
},
{
- "depends_on": "eval:doc.po_no",
"fieldname": "po_date",
"fieldtype": "Date",
"label": "Customer's Purchase Order Date",
@@ -326,7 +324,6 @@
"oldfieldtype": "Data",
"print_hide": 1,
"print_width": "100px",
- "read_only": 1,
"width": "100px"
},
{
@@ -1256,7 +1253,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-17 12:51:41.288600",
+ "modified": "2020-05-19 17:03:45.880106",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7a1c127..3436a5d 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1144,6 +1144,17 @@
item.clear_cache()
@frappe.whitelist()
+def get_item_details(item_code, company=None):
+ out = frappe._dict()
+ if company:
+ out = get_item_defaults(item_code, company) or frappe._dict()
+
+ doc = frappe.get_cached_doc("Item", item_code)
+ out.update(doc.as_dict())
+
+ return out
+
+@frappe.whitelist()
def get_uom_conv_factor(uom, stock_uom):
uoms = [uom, stock_uom]
value = ""
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 71b998f..2f75bbd 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -34,7 +34,7 @@
if self.numeric_values:
validate_is_incremental(self, self.name, item.value, item.name)
else:
- validate_item_attribute_value(attributes_list, self.name, item.value, item.name)
+ validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
def validate_numeric(self):
if self.numeric_values:
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index db8bffd..3a8deb6 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -18,7 +18,7 @@
// formatter for material request item
frm.set_indicator_formatter('item_code',
- function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; });
+ function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
frm.set_query("item_code", "items", function() {
return {
@@ -30,7 +30,16 @@
return {
filters: {'company': doc.company}
};
- })
+ });
+
+ frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+ var row = locals[cdt][cdn];
+ return {
+ filters: {
+ "item": row.item_code
+ }
+ }
+ });
},
onload: function(frm) {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index df140ff..32bd4a0 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -53,6 +53,8 @@
"dimension_col_break",
"cost_center",
"section_break_37",
+ "bom_no",
+ "section_break_46",
"page_break"
],
"fields": [
@@ -371,8 +373,10 @@
"label": "Image"
},
{
+ "depends_on": "eval:parent.material_request_type == \"Manufacture\"",
"fieldname": "section_break_37",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "label": "Manufacturing"
},
{
"fieldname": "received_qty",
@@ -428,12 +432,24 @@
"fieldtype": "Link",
"label": "Source Warehouse (Material Transfer)",
"options": "Warehouse"
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "label": "BOM No",
+ "no_copy": 1,
+ "options": "BOM",
+ "print_hide": 1
+ },
+ {
+ "fieldname": "section_break_46",
+ "fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-05-01 09:00:00.992835",
+ "modified": "2020-05-15 09:00:00.992835",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index d46b98b..3a5ef76 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -31,10 +31,16 @@
};
});
frm.set_query('item_code', 'locations', () => {
+ return erpnext.queries.item({ "is_stock_item": 1 });
+ });
+ frm.set_query('batch_no', 'locations', (frm, cdt, cdn) => {
+ const row = locals[cdt][cdn];
return {
+ query: 'erpnext.controllers.queries.get_batch_no',
filters: {
- is_stock_item: 1
- }
+ item_code: row.item_code,
+ warehouse: row.warehouse
+ },
};
});
},
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 231af1a..4b8b594 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -24,6 +24,9 @@
for item in self.locations:
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue
+ if not item.serial_no:
+ frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format(
+ frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))))
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
@@ -116,11 +119,13 @@
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
+ auto_set_serial_no = frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo")
+
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
- 'serial_no': serial_nos,
+ 'serial_no': serial_nos if auto_set_serial_no else item_doc.serial_no,
'batch_no': item_location.batch_no
}))
@@ -203,6 +208,7 @@
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
and sle.`company` = %(company)s
+ and batch.disabled = 0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
@@ -300,6 +306,7 @@
set_delivery_note_missing_values(delivery_note)
delivery_note.pick_list = pick_list.name
+ delivery_note.customer = pick_list.customer if pick_list.customer else None
return delivery_note
@@ -467,4 +474,4 @@
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
- item.material_request_item = location.material_request_item
\ No newline at end of file
+ item.material_request_item = location.material_request_item
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index e9568ee..50c18f6 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -25,7 +25,7 @@
frm.custom_make_buttons = {
'Stock Entry': 'Return',
- 'Purchase Invoice': 'Invoice'
+ 'Purchase Invoice': 'Purchase Invoice'
};
frm.set_query("expense_account", "items", function() {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 467a206..44d5f69 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -32,6 +32,8 @@
"col_break_address",
"shipping_address",
"shipping_address_display",
+ "billing_address",
+ "billing_address_display",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -130,13 +132,17 @@
{
"fieldname": "supplier_section",
"fieldtype": "Section Break",
- "options": "fa fa-user"
+ "options": "fa fa-user",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -147,7 +153,9 @@
"hidden": 1,
"label": "Title",
"no_copy": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "naming_series",
@@ -159,7 +167,9 @@
"options": "MAT-PRE-.YYYY.-",
"print_hide": 1,
"reqd": 1,
- "set_only_once": 1
+ "set_only_once": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"bold": 1,
@@ -174,6 +184,8 @@
"print_width": "150px",
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -184,18 +196,24 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_delivery_note",
"fieldtype": "Data",
- "label": "Supplier Delivery Note"
+ "label": "Supplier Delivery Note",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break1",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -210,6 +228,8 @@
"print_width": "100px",
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -223,6 +243,8 @@
"print_hide": 1,
"print_width": "100px",
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -231,7 +253,9 @@
"fieldname": "set_posting_time",
"fieldtype": "Check",
"label": "Edit Posting Date and Time",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "company",
@@ -245,6 +269,8 @@
"print_width": "150px",
"remember_last_selected_value": 1,
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -254,7 +280,9 @@
"label": "Is Return",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "is_return",
@@ -264,46 +292,60 @@
"no_copy": 1,
"options": "Purchase Receipt",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
- "label": "Address and Contact"
+ "label": "Address and Contact",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Contact Person",
"options": "Contact",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "address_display",
"fieldtype": "Small Text",
"label": "Address",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_display",
"fieldtype": "Small Text",
"in_global_search": 1,
"label": "Contact",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"label": "Mobile No",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "contact_email",
@@ -311,32 +353,42 @@
"label": "Contact Email",
"options": "Email",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_address",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address",
"fieldtype": "Link",
"label": "Select Shipping Address",
"options": "Address",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_address_display",
"fieldtype": "Small Text",
"label": "Shipping Address",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"label": "Currency and Price List",
- "options": "fa fa-tag"
+ "options": "fa fa-tag",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "currency",
@@ -346,7 +398,9 @@
"oldfieldtype": "Select",
"options": "Currency",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Rate at which supplier's currency is converted to company's base currency",
@@ -357,13 +411,17 @@
"oldfieldtype": "Currency",
"precision": "9",
"print_hide": 1,
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break2",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -371,7 +429,9 @@
"fieldtype": "Link",
"label": "Price List",
"options": "Price List",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "buying_price_list",
@@ -380,7 +440,9 @@
"label": "Price List Currency",
"options": "Currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "buying_price_list",
@@ -388,7 +450,9 @@
"fieldtype": "Float",
"label": "Price List Exchange Rate",
"precision": "9",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
@@ -397,11 +461,15 @@
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 1,
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "sec_warehouse",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Accepted Warehouse' in each row of the items table.",
@@ -409,7 +477,9 @@
"fieldtype": "Link",
"label": "Accepted Warehouse",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Sets 'Rejected Warehouse' in each row of the items table.",
@@ -420,11 +490,15 @@
"oldfieldname": "rejected_warehouse",
"oldfieldtype": "Link",
"options": "Warehouse",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "col_break_warehouse",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "No",
@@ -434,7 +508,9 @@
"oldfieldname": "is_subcontracted",
"oldfieldtype": "Select",
"options": "No\nYes",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:doc.is_subcontracted==\"Yes\"",
@@ -447,13 +523,17 @@
"options": "Warehouse",
"print_hide": 1,
"print_width": "50px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50px"
},
{
"fieldname": "items_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-shopping-cart"
+ "options": "fa fa-shopping-cart",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_bulk_edit": 1,
@@ -463,20 +543,26 @@
"oldfieldname": "purchase_receipt_details",
"oldfieldtype": "Table",
"options": "Purchase Receipt Item",
- "reqd": 1
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "pricing_rule_details",
"fieldtype": "Section Break",
- "label": "Pricing Rules"
+ "label": "Pricing Rules",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "pricing_rules",
"fieldtype": "Table",
"label": "Pricing Rule Detail",
"options": "Pricing Rule Detail",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "supplied_items",
@@ -485,7 +571,9 @@
"label": "Get Current Stock",
"oldfieldtype": "Button",
"options": "get_current_stock",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -496,7 +584,9 @@
"oldfieldtype": "Section Break",
"options": "fa fa-table",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "supplied_items",
@@ -507,18 +597,24 @@
"oldfieldtype": "Table",
"options": "Purchase Receipt Item Supplied",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break0",
"fieldtype": "Section Break",
- "oldfieldtype": "Section Break"
+ "oldfieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_qty",
"fieldtype": "Float",
"label": "Total Quantity",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total",
@@ -526,7 +622,9 @@
"label": "Total (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_net_total",
@@ -539,18 +637,24 @@
"print_width": "150px",
"read_only": 1,
"reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
"fieldname": "column_break_27",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total",
"fieldtype": "Currency",
"label": "Total",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "net_total",
@@ -560,42 +664,56 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_net_weight",
"fieldtype": "Float",
"label": "Total Net Weight",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"description": "Add / Edit Taxes and Charges",
"fieldname": "taxes_charges_section",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_col",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "shipping_rule",
"fieldtype": "Link",
"label": "Shipping Rule",
- "options": "Shipping Rule"
+ "options": "Shipping Rule",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_section",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges",
@@ -604,7 +722,9 @@
"oldfieldname": "purchase_other_charges",
"oldfieldtype": "Link",
"options": "Purchase Taxes and Charges Template",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes",
@@ -612,13 +732,17 @@
"label": "Purchase Taxes and Charges",
"oldfieldname": "purchase_tax_details",
"oldfieldtype": "Table",
- "options": "Purchase Taxes and Charges"
+ "options": "Purchase Taxes and Charges",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
- "label": "Tax Breakup"
+ "label": "Tax Breakup",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_charges_calculation",
@@ -627,13 +751,17 @@
"no_copy": 1,
"oldfieldtype": "HTML",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break",
- "options": "fa fa-money"
+ "options": "fa fa-money",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_added",
@@ -643,7 +771,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_taxes_and_charges_deducted",
@@ -653,7 +783,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_total_taxes_and_charges",
@@ -663,12 +795,16 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break3",
"fieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -679,7 +815,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "taxes_and_charges_deducted",
@@ -689,7 +827,9 @@
"oldfieldtype": "Currency",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "total_taxes_and_charges",
@@ -697,14 +837,18 @@
"label": "Total Taxes and Charges",
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"fieldname": "section_break_42",
"fieldtype": "Section Break",
- "label": "Additional Discount"
+ "label": "Additional Discount",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Grand Total",
@@ -712,7 +856,9 @@
"fieldtype": "Select",
"label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet Total",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_discount_amount",
@@ -720,28 +866,38 @@
"label": "Additional Discount Amount (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_44",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"label": "Additional Discount Percentage",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Additional Discount Amount",
"options": "currency",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "section_break_46",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_grand_total",
@@ -751,7 +907,9 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounding_adjustment",
@@ -760,7 +918,9 @@
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_in_words",
@@ -769,7 +929,9 @@
"oldfieldname": "in_words",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "base_rounded_total",
@@ -779,11 +941,15 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_50",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "grand_total",
@@ -793,7 +959,9 @@
"oldfieldname": "grand_total_import",
"oldfieldtype": "Currency",
"options": "currency",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "rounding_adjustment",
@@ -802,7 +970,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"depends_on": "eval:!doc.disable_rounded_total",
@@ -812,7 +982,9 @@
"no_copy": 1,
"options": "currency",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "in_words",
@@ -821,13 +993,17 @@
"oldfieldname": "in_words_import",
"oldfieldtype": "Data",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "0",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
- "label": "Disable Rounded Total"
+ "label": "Disable Rounded Total",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -836,7 +1012,9 @@
"fieldtype": "Section Break",
"label": "Terms and Conditions",
"oldfieldtype": "Section Break",
- "options": "fa fa-legal"
+ "options": "fa fa-legal",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "tc_name",
@@ -845,14 +1023,18 @@
"oldfieldname": "tc_name",
"oldfieldtype": "Link",
"options": "Terms and Conditions",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "terms",
"fieldtype": "Text Editor",
"label": "Terms and Conditions",
"oldfieldname": "terms",
- "oldfieldtype": "Text Editor"
+ "oldfieldtype": "Text Editor",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_no",
@@ -861,7 +1043,9 @@
"label": "Bill No",
"oldfieldname": "bill_no",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "bill_date",
@@ -870,7 +1054,9 @@
"label": "Bill Date",
"oldfieldname": "bill_date",
"oldfieldtype": "Date",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -878,7 +1064,9 @@
"fieldtype": "Section Break",
"label": "More Information",
"oldfieldtype": "Section Break",
- "options": "fa fa-file-text"
+ "options": "fa fa-file-text",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"default": "Draft",
@@ -895,6 +1083,8 @@
"read_only": 1,
"reqd": 1,
"search_index": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -910,6 +1100,8 @@
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1,
"width": "150px"
},
{
@@ -919,7 +1111,9 @@
"label": "Range",
"oldfieldname": "range",
"oldfieldtype": "Data",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break4",
@@ -927,6 +1121,8 @@
"oldfieldtype": "Column Break",
"print_hide": 1,
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -935,12 +1131,16 @@
"label": "% Amount Billed",
"no_copy": 1,
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "subscription_detail",
"fieldtype": "Section Break",
- "label": "Auto Repeat Detail"
+ "label": "Auto Repeat Detail",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "auto_repeat",
@@ -949,13 +1149,17 @@
"no_copy": 1,
"options": "Auto Repeat",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "printing_settings",
"fieldtype": "Section Break",
- "label": "Printing Settings"
+ "label": "Printing Settings",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -963,7 +1167,9 @@
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -975,13 +1181,17 @@
"oldfieldtype": "Link",
"options": "Print Heading",
"print_hide": 1,
- "report_hide": 1
+ "report_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "language",
"fieldtype": "Data",
"label": "Print Language",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"allow_on_submit": 1,
@@ -989,11 +1199,15 @@
"fieldname": "group_same_items",
"fieldtype": "Check",
"label": "Group same items",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break_97",
- "fieldtype": "Column Break"
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "other_details",
@@ -1004,6 +1218,8 @@
"options": "<div class=\"columnHeading\">Other Details</div>",
"print_hide": 1,
"print_width": "30%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "30%"
},
{
@@ -1011,13 +1227,17 @@
"fieldtype": "Small Text",
"label": "Instructions",
"oldfieldname": "instructions",
- "oldfieldtype": "Text"
+ "oldfieldtype": "Text",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "remarks",
"fieldtype": "Small Text",
"label": "Remarks",
- "print_hide": 1
+ "print_hide": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"collapsible": 1,
@@ -1025,19 +1245,25 @@
"fieldname": "transporter_info",
"fieldtype": "Section Break",
"label": "Transporter Details",
- "options": "fa fa-truck"
+ "options": "fa fa-truck",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "transporter_name",
"fieldtype": "Data",
"label": "Transporter Name",
"oldfieldname": "transporter_name",
- "oldfieldtype": "Data"
+ "oldfieldtype": "Data",
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "column_break5",
"fieldtype": "Column Break",
"print_width": "50%",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "50%"
},
{
@@ -1048,6 +1274,8 @@
"oldfieldname": "lr_no",
"oldfieldtype": "Data",
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -1058,6 +1286,8 @@
"oldfieldname": "lr_date",
"oldfieldtype": "Date",
"print_width": "100px",
+ "show_days": 1,
+ "show_seconds": 1,
"width": "100px"
},
{
@@ -1066,26 +1296,48 @@
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "inter_company_reference",
"fieldtype": "Link",
"label": "Inter Company Reference",
"options": "Delivery Note",
- "read_only": 1
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
},
{
"fieldname": "scan_barcode",
"fieldtype": "Data",
- "label": "Scan Barcode"
+ "label": "Scan Barcode",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address",
+ "fieldtype": "Link",
+ "label": "Select Billing Address",
+ "options": "Address",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "billing_address_display",
+ "fieldtype": "Small Text",
+ "label": "Billing Address",
+ "read_only": 1,
+ "show_days": 1,
+ "show_seconds": 1
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-04-18 18:02:18.020763",
+ "modified": "2020-06-13 22:26:03.600092",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
@@ -1152,4 +1404,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index a9f3cd0..c951066 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "naming_series:",
"creation": "2013-04-30 13:13:03",
"doctype": "DocType",
@@ -8,6 +9,7 @@
"field_order": [
"naming_series",
"report_date",
+ "status",
"column_break_4",
"inspection_type",
"reference_type",
@@ -20,17 +22,16 @@
"column_break1",
"item_name",
"description",
- "status",
+ "bom_no",
+ "specification_details",
+ "quality_inspection_template",
+ "readings",
"section_break_14",
"inspected_by",
"verified_by",
- "bom_no",
"column_break_17",
"remarks",
- "amended_from",
- "specification_details",
- "quality_inspection_template",
- "readings"
+ "amended_from"
],
"fields": [
{
@@ -231,7 +232,8 @@
"icon": "fa fa-search",
"idx": 1,
"is_submittable": 1,
- "modified": "2019-07-12 12:07:23.153698",
+ "links": [],
+ "modified": "2020-04-26 17:50:25.068222",
"modified_by": "Administrator",
"module": "Stock",
"name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index 731a730..d9f8b62 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -420,14 +420,14 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
- "options": "\nActive\nDelivered\nExpired",
+ "options": "\nActive\nInactive\nDelivered\nExpired",
"read_only": 1
}
],
"icon": "fa fa-barcode",
"idx": 1,
"links": [],
- "modified": "2020-04-08 13:29:58.517772",
+ "modified": "2020-05-21 19:29:58.517772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No",
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 914eea3..f3514c7 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -42,6 +42,8 @@
self.status = "Delivered"
elif self.warranty_expiry_date and getdate(self.warranty_expiry_date) <= getdate(nowdate()):
self.status = "Expired"
+ elif not self.warehouse:
+ self.status = "Inactive"
else:
self.status = "Active"
diff --git a/erpnext/stock/doctype/serial_no/serial_no_list.js b/erpnext/stock/doctype/serial_no/serial_no_list.js
index 651f790..7526d1d 100644
--- a/erpnext/stock/doctype/serial_no/serial_no_list.js
+++ b/erpnext/stock/doctype/serial_no/serial_no_list.js
@@ -5,6 +5,8 @@
return [__("Delivered"), "green", "delivery_document_type,is,set"];
} else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) {
return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set"];
+ } else if (!doc.warehouse) {
+ return [__("Inactive"), "grey", "warehouse,is,not set"];
} else {
return [__("Active"), "green", "delivery_document_type,is,not set"];
}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index be2dd52..5fbd512 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -500,7 +500,7 @@
if raw_material_cost and self.purpose == "Manufacture":
d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
- elif self.purpose == "Repack" and total_fg_qty:
+ elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually:
d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
d.basic_amount = d.basic_rate * d.qty
@@ -732,11 +732,15 @@
pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_work_order(pro_doc)
pro_doc.run_method("update_status")
+
if self.fg_completed_qty:
pro_doc.run_method("update_work_order_qty")
if self.purpose == "Manufacture":
pro_doc.run_method("update_planned_qty")
+ if not pro_doc.operations:
+ pro_doc.set_actual_dates()
+
def get_item_details(self, args=None, for_update=False):
item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no,
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index c16a41c..7b9c129 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -23,6 +23,7 @@
"image",
"image_view",
"quantity_and_rate",
+ "set_basic_rate_manually",
"qty",
"basic_rate",
"basic_amount",
@@ -491,12 +492,21 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse",
+ "fieldname": "set_basic_rate_manually",
+ "fieldtype": "Check",
+ "label": "Set Basic Rate Manually",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-23 19:19:28.539769",
+ "modified": "2020-06-08 12:57:03.172887",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.js b/erpnext/stock/doctype/stock_settings/stock_settings.js
index cc0e2cf..877d0c3 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.js
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.js
@@ -15,3 +15,37 @@
frm.set_query("sample_retention_warehouse", filters);
}
});
+
+frappe.tour['Stock Settings'] = [
+ {
+ fieldname: "item_naming_by",
+ title: __("Item Naming By"),
+ description: __("By default, the Item Name is set as per the Item Code entered. If you want Items to be named by a set Naming Series choose the 'Naming Series' option.")
+ },
+ {
+ fieldname: "default_warehouse",
+ title: __("Default Warehouse"),
+ description: __("Set a Default Warehouse for Inventory Transactions. This will be fetched into the Default Warehouse in the Item master.")
+ },
+ {
+ fieldname: "allow_negative_stock",
+ title: __("Allow Negative Stock"),
+ description: __("This will allow stock items to be displayed in negative values. Using this option depends on your use case. With this option unchecked, the system warns before obstructing a transaction that is causing negative stock.")
+
+ },
+ {
+ fieldname: "valuation_method",
+ title: __("Valuation Method"),
+ description: __("Choose between FIFO and Moving Average Valuation Methods. Click ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/stock/articles/item-valuation-fifo-and-moving-average' target='_blank'>here</a>" + __(" to know more about them.")
+ },
+ {
+ fieldname: "show_barcode_field",
+ title: __("Show Barcode Field"),
+ description: __("Show 'Scan Barcode' field above every child table to insert Items with ease.")
+ },
+ {
+ fieldname: "automatically_set_serial_nos_based_on_fifo",
+ title: __("Automatically Set Serial Nos based on FIFO"),
+ description: __("Serial numbers for stock will be set automatically based on the Items entered based on first in first out in transactions like Purchase/Sales Invoices, Delivery Notes, etc.")
+ }
+];
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 11b6403..0ed3b27 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -305,7 +305,8 @@
"weight_uom":item.weight_uom,
"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
"transaction_date": args.get("transaction_date"),
- "against_blanket_order": args.get("against_blanket_order")
+ "against_blanket_order": args.get("against_blanket_order"),
+ "bom_no": item.get("default_bom")
})
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
diff --git a/erpnext/stock/module_onboarding/stock/stock.json b/erpnext/stock/module_onboarding/stock/stock.json
new file mode 100644
index 0000000..de24575
--- /dev/null
+++ b/erpnext/stock/module_onboarding/stock/stock.json
@@ -0,0 +1,54 @@
+{
+ "allow_roles": [
+ {
+ "role": "Manufacturing Manager"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Manufacturing User"
+ },
+ {
+ "role": "Stock User"
+ }
+ ],
+ "creation": "2020-05-15 03:18:44.400108",
+ "docstatus": 0,
+ "doctype": "Module Onboarding",
+ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/stock",
+ "idx": 0,
+ "is_complete": 0,
+ "modified": "2020-05-19 19:03:23.602423",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock",
+ "owner": "Administrator",
+ "steps": [
+ {
+ "step": "Setup your Warehouse"
+ },
+ {
+ "step": "Create a Product"
+ },
+ {
+ "step": "Introduction to Stock Entry"
+ },
+ {
+ "step": "Create a Stock Entry"
+ },
+ {
+ "step": "Create a Supplier"
+ },
+ {
+ "step": "Create a Purchase Receipt"
+ },
+ {
+ "step": "Stock Settings"
+ }
+ ],
+ "subtitle": "Inventory, Warehouses, Analysis and more.",
+ "success_message": "The Stock Module is all set up!",
+ "title": "Let's Setup the Stock Module.",
+ "user_can_dismiss": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/buying_settings/buying_settings.json b/erpnext/stock/onboarding_step/buying_settings/buying_settings.json
new file mode 100644
index 0000000..a788ccd
--- /dev/null
+++ b/erpnext/stock/onboarding_step/buying_settings/buying_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Update Settings",
+ "creation": "2020-05-06 15:53:44.667414",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:06.323797",
+ "modified_by": "Administrator",
+ "name": "Buying Settings",
+ "owner": "Administrator",
+ "reference_document": "Buying Settings",
+ "show_full_form": 0,
+ "title": "Configure Buying Settings.",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_product/create_a_product.json b/erpnext/stock/onboarding_step/create_a_product/create_a_product.json
new file mode 100644
index 0000000..d2068e1
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_product/create_a_product.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-12 18:16:06.624554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-12 18:30:02.489949",
+ "modified_by": "Administrator",
+ "name": "Create a Product",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_full_form": 0,
+ "title": "Create a Product",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
new file mode 100644
index 0000000..b7811a4
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_purchase_receipt/create_a_purchase_receipt.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-19 18:59:13.266713",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:59:13.266713",
+ "modified_by": "Administrator",
+ "name": "Create a Purchase Receipt",
+ "owner": "Administrator",
+ "reference_document": "Purchase Receipt",
+ "show_full_form": 1,
+ "title": "Create a Purchase Receipt",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
new file mode 100644
index 0000000..2b83f65
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_stock_entry/create_a_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-15 03:20:16.277043",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:30:58.047696",
+ "modified_by": "Administrator",
+ "name": "Create a Stock Entry",
+ "owner": "Administrator",
+ "reference_document": "Stock Entry",
+ "show_full_form": 1,
+ "title": "Create a Stock Entry",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
new file mode 100644
index 0000000..7a64224
--- /dev/null
+++ b/erpnext/stock/onboarding_step/create_a_supplier/create_a_supplier.json
@@ -0,0 +1,19 @@
+{
+ "action": "Create Entry",
+ "creation": "2020-05-14 22:09:10.043554",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-14 22:09:10.043554",
+ "modified_by": "Administrator",
+ "name": "Create a Supplier",
+ "owner": "Administrator",
+ "reference_document": "Supplier",
+ "show_full_form": 0,
+ "title": "Create a Supplier",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
new file mode 100644
index 0000000..009a44f
--- /dev/null
+++ b/erpnext/stock/onboarding_step/introduction_to_stock_entry/introduction_to_stock_entry.json
@@ -0,0 +1,19 @@
+{
+ "action": "Watch Video",
+ "creation": "2020-05-15 02:47:17.958806",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-26 15:55:41.457289",
+ "modified_by": "Administrator",
+ "name": "Introduction to Stock Entry",
+ "owner": "Administrator",
+ "show_full_form": 0,
+ "title": "Introduction to Stock Entry",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/watch?v=Njt107hlY3I"
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
new file mode 100644
index 0000000..557c905
--- /dev/null
+++ b/erpnext/stock/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -0,0 +1,20 @@
+{
+ "action": "Go to Page",
+ "creation": "2020-05-19 18:54:19.383397",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2020-05-19 18:54:19.383397",
+ "modified_by": "Administrator",
+ "name": "Setup your Warehouse",
+ "owner": "Administrator",
+ "path": "Tree/Warehouse",
+ "reference_document": "Warehouse",
+ "show_full_form": 0,
+ "title": "Setup your Warehouse",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/onboarding_step/stock_settings/stock_settings.json b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
new file mode 100644
index 0000000..7591bff
--- /dev/null
+++ b/erpnext/stock/onboarding_step/stock_settings/stock_settings.json
@@ -0,0 +1,19 @@
+{
+ "action": "Show Form Tour",
+ "creation": "2020-05-15 02:53:57.209967",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_mandatory": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2020-05-15 03:55:15.444151",
+ "modified_by": "Administrator",
+ "name": "Stock Settings",
+ "owner": "Administrator",
+ "reference_document": "Stock Settings",
+ "show_full_form": 0,
+ "title": "Explore Stock Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
index 27cf6b6..5a931e7 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -10,5 +11,39 @@
data = []
conditions = get_columns(filters, "Delivery Note")
data = get_data(filters, conditions)
-
- return conditions["columns"], data
\ No newline at end of file
+
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # consider only consolidated row
+ data = [row for row in data if row[0]]
+
+ if len(data) > 10:
+ # get top 10 if data too long
+ data = sorted(data, key = lambda i: i[-1],reverse=True)
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Delivered Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.js b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
new file mode 100644
index 0000000..ca42a33
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Item Shortage Report"] = {
+ "filters": [
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "width": "80",
+ "options": "Company",
+ "reqd": 1,
+ "default": frappe.defaults.get_default("company")
+ },
+ {
+ "fieldname": "warehouse",
+ "label": __("Warehouse"),
+ "fieldtype": "MultiSelectList",
+ "width": "100",
+ get_data: function(txt) {
+ return frappe.db.get_link_options('Warehouse', txt);
+ }
+ }
+ ]
+};
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.json b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
index 577a853..17285c0 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.json
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.json
@@ -1,29 +1,30 @@
{
- "add_total_row": 0,
- "apply_user_permissions": 1,
- "creation": "2013-08-20 13:43:30",
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
- "modified": "2017-02-24 20:00:46.439935",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Shortage Report",
- "owner": "Administrator",
- "query": "SELECT bin.warehouse as \"Warehouse:Link/Warehouse:150\",\n\tbin.item_code as \"Item Code:Link/Item:100\",\n\tbin.actual_qty as \"Actual Quantity:Float:120\",\n\tbin.ordered_qty as \"Ordered Quantity:Float:120\",\n\tbin.planned_qty as \"Planned Quantity:Float:120\",\n\tbin.reserved_qty as \"Reserved Quantity:Float:120\",\n\tbin.projected_qty as \"Project Quantity:Float:120\",\n\titem.item_name as \"Item Name:Data:150\",\n\titem.description as \"Description::200\"\nFROM tabBin as bin\nINNER JOIN tabItem as item\nON bin.item_code=item.name\nWHERE bin.projected_qty<0\nORDER BY bin.projected_qty;",
- "ref_doctype": "Bin",
- "report_name": "Item Shortage Report",
- "report_type": "Query Report",
+ "add_total_row": 0,
+ "creation": "2013-08-20 13:43:30",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 3,
+ "is_standard": "Yes",
+ "json": "{\"add_total_row\": 0, \"sort_by\": \"Bin.projected_qty\", \"sort_order\": \"asc\", \"sort_by_next\": \"\", \"filters\": [[\"Bin\", \"projected_qty\", \"<\", \"0\"]], \"sort_order_next\": \"desc\", \"columns\": [[\"warehouse\", \"Bin\"], [\"item_code\", \"Bin\"], [\"actual_qty\", \"Bin\"], [\"ordered_qty\", \"Bin\"], [\"planned_qty\", \"Bin\"], [\"reserved_qty\", \"Bin\"], [\"projected_qty\", \"Bin\"]]}",
+ "modified": "2020-05-14 12:32:07.158991",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Shortage Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "query": "",
+ "ref_doctype": "Bin",
+ "report_name": "Item Shortage Report",
+ "report_type": "Script Report",
"roles": [
{
"role": "Sales User"
- },
+ },
{
"role": "Purchase User"
- },
+ },
{
"role": "Stock User"
}
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
new file mode 100644
index 0000000..086d833
--- /dev/null
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -0,0 +1,162 @@
+# 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()
+ conditions = get_conditions(filters)
+ data = get_data(conditions, filters)
+
+ if not data:
+ return [], [], None, []
+
+ chart_data = get_chart_data(data)
+
+ return columns, data, None, chart_data
+
+def get_conditions(filters):
+ conditions = ""
+
+ if filters.get("warehouse"):
+ conditions += "AND warehouse in %(warehouse)s"
+ if filters.get("company"):
+ conditions += "AND company = %(company)s"
+
+ return conditions
+
+def get_data(conditions, filters):
+ data = frappe.db.sql("""
+ SELECT
+ bin.warehouse,
+ bin.item_code,
+ bin.actual_qty ,
+ bin.ordered_qty ,
+ bin.planned_qty ,
+ bin.reserved_qty ,
+ bin.reserved_qty_for_production,
+ bin.projected_qty ,
+ warehouse.company,
+ item.item_name ,
+ item.description
+ FROM
+ `tabBin` bin,
+ `tabWarehouse` warehouse,
+ `tabItem` item
+ WHERE
+ bin.projected_qty<0
+ AND warehouse.name = bin.warehouse
+ AND bin.item_code=item.name
+ {0}
+ ORDER BY bin.projected_qty;""".format(conditions), filters, as_dict=1)
+
+ return data
+
+def get_chart_data(data):
+ labels, datapoints = [], []
+
+ for row in data:
+ labels.append(row.get("item_code"))
+ datapoints.append(row.get("projected_qty"))
+
+ if len(data) > 10:
+ labels = labels[:10]
+ datapoints = datapoints[:10]
+
+ return {
+ "data": {
+ "labels": labels,
+ "datasets":[
+ {
+ "name": _("Projected Qty"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type": "bar"
+ }
+
+def get_columns():
+ columns = [
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 150
+ },
+ {
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 150
+ },
+ {
+ "label": _("Actual Quantity"),
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Ordered Quantity"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Planned Quantity"),
+ "fieldname": "planned_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity"),
+ "fieldname": "reserved_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Reserved Quantity for Production"),
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Projected Quantity"),
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "width": 120,
+ "convertible": "qty"
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 120
+ },
+ {
+ "label": _("Item Name"),
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Description"),
+ "fieldname": "description",
+ "fieldtype": "Data",
+ "width": 120
+ }
+ ]
+
+ return columns
+
+
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 9a97210..5df3fa8 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -8,7 +8,7 @@
def execute(filters=None):
if not filters: filters = {}
- float_preceision = frappe.db.get_default("float_preceision")
+ float_precision = frappe.db.get_default("float_precision")
condition = get_condition(filters)
@@ -25,7 +25,7 @@
data = []
for item in items:
total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(delivered_item_map.get(item.name,0))
- avg_daily_outgoing = flt(total_outgoing / diff, float_preceision)
+ avg_daily_outgoing = flt(total_outgoing / diff, float_precision)
reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock)
data.append([item.name, item.item_name, item.item_group, item.brand, item.description,
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json b/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json
deleted file mode 100644
index dfaa9ed..0000000
--- a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "add_total_row": 1,
- "creation": "2013-02-22 18:01:55",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 3,
- "is_standard": "Yes",
- "modified": "2019-04-01 22:12:05.573343",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Purchase Order Items To Be Received",
- "owner": "Administrator",
- "prepared_report": 0,
- "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`status` as \"Status:Data:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc",
- "ref_doctype": "Purchase Receipt",
- "report_name": "Purchase Order Items To Be Received",
- "report_type": "Query Report",
- "roles": [
- {
- "role": "Stock Manager"
- },
- {
- "role": "Stock User"
- },
- {
- "role": "Purchase User"
- },
- {
- "role": "Accounts User"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/__init__.py
+++ /dev/null
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
deleted file mode 100644
index 48c0f42..0000000
--- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "add_total_row": 0,
- "creation": "2019-09-16 14:10:33.102865",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2019-09-21 15:19:55.710578",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Purchase Order Items To Be Received or Billed",
- "owner": "Administrator",
- "prepared_report": 0,
- "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`",
- "ref_doctype": "Purchase Order",
- "report_name": "Purchase Order Items To Be Received or Billed",
- "report_type": "Query Report",
- "roles": [
- {
- "role": "Purchase Manager"
- },
- {
- "role": "Purchase User"
- },
- {
- "role": "Stock User"
- },
- {
- "role": "Stock Manager"
- }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
index 0e58920..8227f15 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from erpnext.controllers.trends import get_columns,get_data
def execute(filters=None):
@@ -11,4 +12,40 @@
conditions = get_columns(filters, "Purchase Receipt")
data = get_data(filters, conditions)
- return conditions["columns"], data
\ No newline at end of file
+ chart_data = get_chart_data(data, filters)
+
+ return conditions["columns"], data, None, chart_data
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("group_by"):
+ # consider only consolidated row
+ data = [row for row in data if row[0]]
+
+ data = sorted(data, key = lambda i: i[-1], reverse=True)
+
+ if len(data) > 10:
+ # get top 10 if data too long
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[-1])
+
+ return {
+ "data": {
+ "labels" : labels,
+ "datasets" : [
+ {
+ "name": _("Total Received Amount"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar",
+ "colors":["#5e64ff"]
+ }
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 803a5c8..723ed5c 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -37,7 +37,9 @@
data.append(row)
- return columns, data
+ chart_data = get_chart_data(data, filters)
+
+ return columns, data, None, chart_data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
@@ -51,7 +53,7 @@
age_qty += batch_age * 1
total_qty += 1
- return (age_qty / total_qty) if total_qty else 0.0
+ return flt(age_qty / total_qty, 2) if total_qty else 0.0
def get_columns(filters):
columns = [
@@ -178,10 +180,10 @@
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
- if 0 < batch[0] <= qty_to_pop:
+ if 0 < flt(batch[0]) <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
- qty_to_pop -= batch[0]
+ qty_to_pop -= flt(batch[0])
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else:
# all from current batch
@@ -230,3 +232,34 @@
where wh.lft >= {0} and rgt <= {1})""".format(lft, rgt))
return "and {}".format(" and ".join(conditions)) if conditions else ""
+
+def get_chart_data(data, filters):
+ if not data:
+ return []
+
+ labels, datapoints = [], []
+
+ if filters.get("show_warehouse_wise_stock"):
+ return {}
+
+ data.sort(key = lambda row: row[6], reverse=True)
+
+ if len(data) > 10:
+ data = data[:10]
+
+ for row in data:
+ labels.append(row[0])
+ datapoints.append(row[6])
+
+ return {
+ "data" : {
+ "labels": labels,
+ "datasets": [
+ {
+ "name": _("Average Age"),
+ "values": datapoints
+ }
+ ]
+ },
+ "type" : "bar"
+ }
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index f21dc3f..11e758f 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -230,12 +230,12 @@
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
- if qty >= 0:
+ if flt(qty) >= 0:
total = sum(f[0] for f in previous_stock_queue)
return sum(flt(f[0]) * flt(f[1]) for f in previous_stock_queue) / flt(total) if total else 0.0
else:
available_qty_for_outgoing, outgoing_cost = 0, 0
- qty_to_pop = abs(qty)
+ qty_to_pop = abs(flt(qty))
while qty_to_pop and previous_stock_queue:
batch = previous_stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index 596987f..b1ad7c8 100644
--- a/erpnext/support/desk_page/support/support.json
+++ b/erpnext/support/desk_page/support/support.json
@@ -2,8 +2,8 @@
"cards": [
{
"hidden": 0,
- "label": "Service Level Agreement",
- "links": "[\n {\n \"description\": \"Service Level.\",\n \"label\": \"Service Level\",\n \"name\": \"Service Level\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Service Level Agreement.\",\n \"label\": \"Service Level Agreement\",\n \"name\": \"Service Level Agreement\",\n \"type\": \"doctype\"\n }\n]"
+ "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,
@@ -12,8 +12,8 @@
},
{
"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]"
+ "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,
@@ -39,11 +39,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
- "icon": "",
+ "hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Support",
- "modified": "2020-04-01 11:28:51.120583",
+ "modified": "2020-06-04 11:54:56.124219",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",
@@ -52,19 +52,22 @@
"pin_to_top": 0,
"shortcuts": [
{
+ "color": "#ffc4c4",
+ "format": "{} Assigned",
"label": "Issue",
"link_to": "Issue",
- "type": "DocType"
- },
- {
- "label": "Service Level",
- "link_to": "Service Level",
+ "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 bad40cc..e7e5bd3 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -38,10 +38,35 @@
},
refresh: function (frm) {
-
if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") {
if (frm.doc.service_level_agreement) {
- set_time_to_resolve_and_response(frm);
+ frappe.call({
+ 'method': 'frappe.client.get',
+ args: {
+ doctype: 'Service Level Agreement',
+ name: frm.doc.service_level_agreement
+ },
+ callback: function(data) {
+ let statuses = data.message.pause_sla_on;
+ const hold_statuses = [];
+ $.each(statuses, (_i, entry) => {
+ hold_statuses.push(entry.status);
+ });
+ if (hold_statuses.includes(frm.doc.status)) {
+ frm.dashboard.clear_headline();
+ let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])};
+ frm.dashboard.set_headline_alert(
+ '<div class="row">' +
+ '<div class="col-xs-12">' +
+ '<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
+ '</div>' +
+ '</div>'
+ );
+ } else {
+ set_time_to_resolve_and_response(frm);
+ }
+ }
+ });
}
frm.add_custom_button(__("Close"), function () {
@@ -55,6 +80,7 @@
frm: frm
});
}, __("Make"));
+
} else {
if (frm.doc.service_level_agreement) {
frm.dashboard.clear_headline();
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index c12cef4..6525ab2 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -31,9 +31,13 @@
"resolution_by",
"resolution_by_variance",
"service_level_agreement_creation",
+ "on_hold_since",
+ "total_hold_time",
"response",
"mins_to_first_response",
"first_responded_on",
+ "column_break_26",
+ "avg_response_time",
"additional_info",
"lead",
"contact",
@@ -50,7 +54,9 @@
"resolution_date",
"content_type",
"attachment",
- "via_customer_portal"
+ "via_customer_portal",
+ "resolution_time",
+ "user_resolution_time"
],
"fields": [
{
@@ -114,7 +120,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Open\nReplied\nHold\nClosed",
+ "options": "Open\nReplied\nHold\nResolved\nClosed",
"search_index": 1
},
{
@@ -161,6 +167,7 @@
"options": "Service Level Agreement"
},
{
+ "depends_on": "eval: doc.status != 'Replied';",
"fieldname": "response_by",
"fieldtype": "Datetime",
"label": "Response By",
@@ -174,6 +181,7 @@
"read_only": 1
},
{
+ "depends_on": "eval: doc.status != 'Replied';",
"fieldname": "resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
@@ -328,7 +336,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.service_level_agreement",
+ "depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
"description": "in hours",
"fieldname": "response_by_variance",
"fieldtype": "Float",
@@ -336,7 +344,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.service_level_agreement",
+ "depends_on": "eval: doc.service_level_agreement && doc.status != 'Replied';",
"description": "in hours",
"fieldname": "resolution_by_variance",
"fieldtype": "Float",
@@ -362,12 +370,48 @@
"label": "Issue Split From",
"options": "Issue",
"read_only": 1
+ },
+ {
+ "fieldname": "column_break_26",
+ "fieldtype": "Column Break"
+ },
+ {
+ "bold": 1,
+ "fieldname": "avg_response_time",
+ "fieldtype": "Duration",
+ "label": "Average Response Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "resolution_time",
+ "fieldtype": "Duration",
+ "label": "Resolution Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "user_resolution_time",
+ "fieldtype": "Duration",
+ "label": "User Resolution Time",
+ "read_only": 1
+ },
+ {
+ "fieldname": "on_hold_since",
+ "fieldtype": "Datetime",
+ "hidden": 1,
+ "label": "On Hold Since",
+ "read_only": 1
+ },
+ {
+ "fieldname": "total_hold_time",
+ "fieldtype": "Duration",
+ "label": "Total Hold Time",
+ "read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2020-03-13 02:19:49.477928",
+ "modified": "2020-06-10 12:47:37.146914",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
@@ -395,4 +439,4 @@
"title_field": "subject",
"track_changes": 1,
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 117267f..883e603 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,7 +7,7 @@
from frappe import _
from frappe import utils
from frappe.model.document import Document
-from frappe.utils import now, time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime
+from frappe.utils import time_diff_in_hours, now_datetime, getdate, get_weekdays, add_to_date, today, get_time, get_datetime, time_diff_in_seconds, time_diff
from datetime import datetime, timedelta
from frappe.model.mapper import get_mapped_doc
from frappe.utils.user import is_website_user
@@ -47,8 +47,8 @@
self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
if self.contact:
- contact = frappe.get_doc('Contact', self.contact)
- self.customer = contact.get_link_for('Customer')
+ contact = frappe.get_doc("Contact", self.contact)
+ self.customer = contact.get_link_for("Customer")
if not self.company:
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
@@ -56,18 +56,70 @@
def update_status(self):
status = frappe.db.get_value("Issue", self.name, "status")
- if self.status!="Open" and status =="Open" and not self.first_responded_on:
+ if self.status != "Open" and status == "Open" and not self.first_responded_on:
self.first_responded_on = frappe.flags.current_time or now_datetime()
- if self.status=="Closed" and status !="Closed":
+ if self.status in ["Closed", "Resolved"] and status not in ["Resolved", "Closed"]:
self.resolution_date = frappe.flags.current_time or now_datetime()
if frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing":
set_service_level_agreement_variance(issue=self.name)
self.update_agreement_status()
+ set_resolution_time(issue=self)
+ set_user_resolution_time(issue=self)
- if self.status=="Open" and status !="Open":
+ if self.status == "Open" and status != "Open":
# if no date, it should be set as None and not a blank string "", as per mysql strict config
self.resolution_date = None
+ self.reset_issue_metrics()
+ # enable SLA and variance on Reopen
+ self.agreement_fulfilled = "Ongoing"
+ set_service_level_agreement_variance(issue=self.name)
+
+ self.handle_hold_time(status)
+
+ def handle_hold_time(self, status):
+ if self.service_level_agreement:
+ # set response and resolution variance as None as the issue is on Hold for status as Replied
+ pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"],
+ filters={"parent": self.service_level_agreement})
+ hold_statuses = [entry.status for entry in pause_sla_on]
+ update_values = {}
+
+ if self.status in hold_statuses and status not in hold_statuses:
+ update_values['on_hold_since'] = frappe.flags.current_time or now_datetime()
+ if not self.first_responded_on:
+ update_values['response_by'] = None
+ update_values['response_by_variance'] = 0
+ update_values['resolution_by'] = None
+ update_values['resolution_by_variance'] = 0
+
+ # calculate hold time when status is changed from Replied to any other status
+ if self.status not in hold_statuses and status in hold_statuses:
+ hold_time = self.total_hold_time if self.total_hold_time else 0
+ now_time = frappe.flags.current_time or now_datetime()
+ update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since)
+
+ # re-calculate SLA variables after issue changes from Replied to Open
+ # add hold time to SLA variables
+ if self.status == "Open" and status in hold_statuses:
+ start_date_time = get_datetime(self.service_level_agreement_creation)
+ priority = get_priority(self)
+ now_time = frappe.flags.current_time or now_datetime()
+ hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
+
+ if not self.first_responded_on:
+ response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+ update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time))
+ response_by_variance = round(time_diff_in_hours(self.response_by, now_time))
+ update_values['response_by_variance'] = response_by_variance + (hold_time // 3600)
+
+ resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
+ update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time))
+ resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time))
+ update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600)
+ update_values['on_hold_since'] = None
+
+ self.db_set(update_values)
def update_agreement_status(self):
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":
@@ -128,6 +180,7 @@
replicated_issue.response_by_variance = None
replicated_issue.resolution_by = None
replicated_issue.resolution_by_variance = None
+ replicated_issue.reset_issue_metrics()
frappe.get_doc(replicated_issue).insert()
@@ -137,7 +190,7 @@
communications = frappe.get_all("Communication",
filters={"reference_doctype": "Issue",
"reference_name": comm_to_split_from.reference_name,
- "creation": ('>=', comm_to_split_from.creation)})
+ "creation": (">=", comm_to_split_from.creation)})
for communication in communications:
doc = frappe.get_doc("Communication", communication.name)
@@ -173,20 +226,15 @@
self.service_level_agreement = service_level_agreement.name
self.priority = service_level_agreement.default_priority if not priority else priority
- service_level_agreement = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
- priority = service_level_agreement.get_service_level_agreement_priority(self.priority)
- priority.update({
- "support_and_resolution": service_level_agreement.support_and_resolution,
- "holiday_list": service_level_agreement.holiday_list
- })
+ priority = get_priority(self)
if not self.creation:
self.creation = now_datetime()
self.service_level_agreement_creation = now_datetime()
start_date_time = get_datetime(self.service_level_agreement_creation)
- self.response_by = get_expected_time_for(parameter='response', service_level=priority, start_date_time=start_date_time)
- self.resolution_by = get_expected_time_for(parameter='resolution', service_level=priority, start_date_time=start_date_time)
+ self.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
+ self.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime()))
self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime()))
@@ -221,36 +269,41 @@
self.agreement_fulfilled = "Ongoing"
self.save()
+ def reset_issue_metrics(self):
+ self.db_set("resolution_time", None)
+ self.db_set("user_resolution_time", None)
+
+
+def get_priority(issue):
+ service_level_agreement = frappe.get_doc("Service Level Agreement", issue.service_level_agreement)
+ priority = service_level_agreement.get_service_level_agreement_priority(issue.priority)
+ priority.update({
+ "support_and_resolution": service_level_agreement.support_and_resolution,
+ "holiday_list": service_level_agreement.holiday_list
+ })
+ return priority
+
+
def get_expected_time_for(parameter, service_level, start_date_time):
current_date_time = start_date_time
expected_time = current_date_time
start_time = None
end_time = None
- # lets assume response time is in days by default
- if parameter == 'response':
- allotted_days = service_level.get("response_time")
- time_period = service_level.get("response_time_period")
- elif parameter == 'resolution':
- allotted_days = service_level.get("resolution_time")
- time_period = service_level.get("resolution_time_period")
+ if parameter == "response":
+ allotted_seconds = service_level.get("response_time")
+ elif parameter == "resolution":
+ allotted_seconds = service_level.get("resolution_time")
else:
frappe.throw(_("{0} parameter is invalid").format(parameter))
- allotted_hours = 0
- if time_period == 'Hour':
- allotted_hours = allotted_days
- allotted_days = 0
- elif time_period == 'Week':
- allotted_days *= 7
-
- expected_time_is_set = 1 if allotted_days == 0 and time_period in ['Day', 'Week'] else 0
+ expected_time_is_set = 0
support_days = {}
for service in service_level.get("support_and_resolution"):
support_days[service.workday] = frappe._dict({
- 'start_time': service.start_time,
- 'end_time': service.end_time,
+ "start_time": service.start_time,
+ "end_time": service.end_time,
})
holidays = get_holidays(service_level.get("holiday_list"))
@@ -264,25 +317,22 @@
if getdate(current_date_time) == getdate(start_date_time) and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time \
else support_days[current_weekday].start_time
end_time = support_days[current_weekday].end_time
- time_left_today = time_diff_in_hours(end_time, start_time)
+ time_left_today = time_diff_in_seconds(end_time, start_time)
# no time left for support today
- if time_left_today < 0: pass
- elif time_period == 'Hour':
- if time_left_today >= allotted_hours:
+ if time_left_today <= 0: pass
+ elif allotted_seconds:
+ if time_left_today >= allotted_seconds:
expected_time = datetime.combine(getdate(current_date_time), get_time(start_time))
- expected_time = add_to_date(expected_time, hours=allotted_hours)
+ expected_time = add_to_date(expected_time, seconds=allotted_seconds)
expected_time_is_set = 1
else:
- allotted_hours = allotted_hours - time_left_today
- else:
- allotted_days -= 1
- expected_time_is_set = allotted_days <= 0
+ allotted_seconds = allotted_seconds - time_left_today
if not expected_time_is_set:
current_date_time = add_to_date(current_date_time, days=1)
- if end_time and time_period != 'Hour':
+ if end_time and allotted_seconds >= 86400:
current_date_time = datetime.combine(getdate(current_date_time), get_time(end_time))
else:
current_date_time = expected_time
@@ -311,6 +361,36 @@
if variance < 0:
frappe.db.set_value(dt="Issue", dn=doc.name, field="agreement_fulfilled", val="Failed", update_modified=False)
+
+def set_resolution_time(issue):
+ # total time taken from issue creation to closing
+ resolution_time = time_diff_in_seconds(issue.resolution_date, issue.creation)
+ issue.db_set("resolution_time", resolution_time)
+
+
+def set_user_resolution_time(issue):
+ # total time taken by a user to close the issue apart from wait_time
+ communications = frappe.get_list("Communication", filters={
+ "reference_doctype": issue.doctype,
+ "reference_name": issue.name
+ },
+ fields=["sent_or_received", "name", "creation"],
+ order_by="creation"
+ )
+
+ pending_time = []
+ for i in range(len(communications)):
+ if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
+ wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
+ if wait_time > 0:
+ pending_time.append(wait_time)
+
+ total_pending_time = sum(pending_time)
+ resolution_time_in_secs = time_diff_in_seconds(issue.resolution_date, issue.creation)
+ user_resolution_time = resolution_time_in_secs - total_pending_time
+ issue.db_set("user_resolution_time", user_resolution_time)
+
+
def get_list_context(context=None):
return {
"title": _("Issues"),
@@ -318,7 +398,7 @@
"row_template": "templates/includes/issue_row.html",
"show_sidebar": True,
"show_search": True,
- 'no_breadcrumbs': True
+ "no_breadcrumbs": True
}
@@ -326,12 +406,12 @@
from frappe.www.list import get_list
user = frappe.session.user
- contact = frappe.db.get_value('Contact', {'user': user}, 'name')
+ contact = frappe.db.get_value("Contact", {"user": user}, "name")
customer = None
if contact:
- contact_doc = frappe.get_doc('Contact', contact)
- customer = contact_doc.get_link_for('Customer')
+ contact_doc = frappe.get_doc("Contact", contact)
+ customer = contact_doc.get_link_for("Customer")
ignore_permissions = False
if is_website_user():
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 7a5e3e3..fb8ceb5 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -5,15 +5,18 @@
import frappe
import unittest
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
-from frappe.utils import now_datetime, get_datetime
+from frappe.utils import now_datetime, get_datetime, flt
import datetime
from datetime import timedelta
class TestIssue(unittest.TestCase):
- def test_response_time_and_resolution_time_based_on_different_sla(self):
+ def setUp(self):
+ frappe.db.sql("delete from `tabService Level Agreement`")
+ frappe.db.sql("delete from `tabEmployee`")
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
create_service_level_agreements_for_issues()
+ def test_response_time_and_resolution_time_based_on_different_sla(self):
creation = datetime.datetime(2019, 3, 4, 12, 0)
# make issue with customer specific SLA
@@ -72,8 +75,67 @@
self.assertEqual(issue.agreement_fulfilled, 'Fulfilled')
-def make_issue(creation=None, customer=None, index=0):
+ def test_issue_metrics(self):
+ creation = datetime.datetime(2020, 3, 4, 4, 0)
+ issue = make_issue(creation, index=1)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 4, 15)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 0)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 5)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+ issue.reload()
+ issue.status = 'Closed'
+ issue.save()
+
+ self.assertEqual(issue.avg_response_time, 600)
+ self.assertEqual(issue.resolution_time, 3900)
+ self.assertEqual(issue.user_resolution_time, 1200)
+
+ def test_hold_time_on_replied(self):
+ creation = datetime.datetime(2020, 3, 4, 4, 0)
+
+ issue = make_issue(creation, index=1)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ creation = datetime.datetime(2020, 3, 4, 4, 15)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
+ issue.reload()
+ issue.status = 'Replied'
+ issue.save()
+
+ self.assertEqual(issue.on_hold_since, frappe.flags.current_time)
+
+ creation = datetime.datetime(2020, 3, 4, 5, 0)
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 0)
+ create_communication(issue.name, "test@example.com", "Received", creation)
+
+ issue.reload()
+ self.assertEqual(flt(issue.total_hold_time, 2), 2700)
+ self.assertEqual(issue.resolution_by, datetime.datetime(2020, 3, 4, 16, 45))
+
+ creation = datetime.datetime(2020, 3, 4, 5, 5)
+ create_communication(issue.name, "test@admin.com", "Sent", creation)
+
+ frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
+ issue.reload()
+ issue.status = 'Closed'
+ issue.save()
+
+ issue.reload()
+ self.assertEqual(flt(issue.total_hold_time, 2), 2700)
+
+
+def make_issue(creation=None, customer=None, index=0):
issue = frappe.get_doc({
"doctype": "Issue",
"subject": "Service Level Agreement Issue {0}".format(index),
@@ -86,6 +148,7 @@
return issue
+
def create_customer(name, customer_group, territory):
create_customer_group(customer_group)
@@ -99,6 +162,7 @@
"territory": territory
}).insert(ignore_permissions=True)
+
def create_customer_group(customer_group):
if not frappe.db.exists("Customer Group", {"customer_group_name": customer_group}):
@@ -107,6 +171,7 @@
"customer_group_name": customer_group
}).insert(ignore_permissions=True)
+
def create_territory(territory):
if not frappe.db.exists("Territory", {"territory_name": territory}):
@@ -114,3 +179,21 @@
"doctype": "Territory",
"territory_name": territory,
}).insert(ignore_permissions=True)
+
+
+def create_communication(reference_name, sender, sent_or_received, creation):
+ issue = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "communication_medium": "Email",
+ "sent_or_received": sent_or_received,
+ "email_status": "Open",
+ "subject": "Test Issue",
+ "sender": sender,
+ "content": "Test",
+ "status": "Linked",
+ "reference_doctype": "Issue",
+ "creation": creation,
+ "reference_name": reference_name
+ })
+ issue.save()
diff --git a/erpnext/support/doctype/service_level/__init__.py b/erpnext/support/doctype/pause_sla_on_status/__init__.py
similarity index 100%
rename from erpnext/support/doctype/service_level/__init__.py
rename to erpnext/support/doctype/pause_sla_on_status/__init__.py
diff --git a/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json
new file mode 100644
index 0000000..5b03f25
--- /dev/null
+++ b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json
@@ -0,0 +1,33 @@
+{
+ "actions": [],
+ "creation": "2020-06-05 13:59:43.265588",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "status"
+ ],
+ "fields": [
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Status",
+ "reqd": 1,
+ "show_days": 1,
+ "show_seconds": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-06-05 15:15:29.986608",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "Pause SLA On Status",
+ "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/support/doctype/pause_sla_on_status/pause_sla_on_status.py b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.py
new file mode 100644
index 0000000..a3b547e
--- /dev/null
+++ b/erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.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 PauseSLAOnStatus(Document):
+ pass
diff --git a/erpnext/support/doctype/service_level/service_level.js b/erpnext/support/doctype/service_level/service_level.js
deleted file mode 100644
index abe254b..0000000
--- a/erpnext/support/doctype/service_level/service_level.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Service Level', {
-
-});
diff --git a/erpnext/support/doctype/service_level/service_level.json b/erpnext/support/doctype/service_level/service_level.json
deleted file mode 100644
index dced3aa..0000000
--- a/erpnext/support/doctype/service_level/service_level.json
+++ /dev/null
@@ -1,111 +0,0 @@
-{
- "autoname": "field:service_level",
- "creation": "2018-11-19 12:44:30.407502",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "service_level",
- "employee_group",
- "column_break_2",
- "holiday_list",
- "default_priority",
- "response_and_resoution_time",
- "priorities",
- "section_break_01",
- "support_and_resolution"
- ],
- "fields": [
- {
- "fieldname": "service_level",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Level",
- "reqd": 1,
- "unique": 1
- },
- {
- "fieldname": "column_break_2",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "holiday_list",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Holiday List (ignored during SLA calculation)",
- "options": "Holiday List",
- "reqd": 1
- },
- {
- "fieldname": "employee_group",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Employee Group",
- "options": "Employee Group"
- },
- {
- "fieldname": "response_and_resoution_time",
- "fieldtype": "Section Break",
- "label": "Response and Resoution Time"
- },
- {
- "fieldname": "section_break_01",
- "fieldtype": "Section Break",
- "label": "Support Hours"
- },
- {
- "fieldname": "support_and_resolution",
- "fieldtype": "Table",
- "label": "Support and Resolution",
- "options": "Service Day",
- "reqd": 1
- },
- {
- "fieldname": "priorities",
- "fieldtype": "Table",
- "label": "Priorities",
- "options": "Service Level Priority",
- "reqd": 1
- },
- {
- "fieldname": "default_priority",
- "fieldtype": "Link",
- "label": "Default Priority",
- "options": "Issue Priority",
- "read_only": 1
- }
- ],
- "modified": "2019-06-06 12:58:03.464056",
- "modified_by": "Administrator",
- "module": "Support",
- "name": "Service Level",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- },
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "All",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC"
-}
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level/service_level.py b/erpnext/support/doctype/service_level/service_level.py
deleted file mode 100644
index 89fa25c..0000000
--- a/erpnext/support/doctype/service_level/service_level.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from datetime import datetime
-from frappe.utils import get_weekdays
-
-class ServiceLevel(Document):
-
- def validate(self):
- self.check_priorities()
- self.check_support_and_resolution()
-
- def check_priorities(self):
- default_priority = []
- priorities = []
-
- for priority in self.priorities:
- # Check if response and resolution time is set for every priority
- if not (priority.response_time or priority.resolution_time):
- frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
-
- priorities.append(priority.priority)
-
- if priority.default_priority:
- default_priority.append(priority.default_priority)
-
- if priority.response_time_period == "Hour":
- response = priority.response_time * 0.0416667
- elif priority.response_time_period == "Day":
- response = priority.response_time
- elif priority.response_time_period == "Week":
- response = priority.response_time * 7
-
- if priority.resolution_time_period == "Hour":
- resolution = priority.resolution_time * 0.0416667
- elif priority.resolution_time_period == "Day":
- resolution = priority.resolution_time
- elif priority.resolution_time_period == "Week":
- resolution = priority.resolution_time * 7
-
- if response > resolution:
- frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
-
- # Check if repeated priority
- if not len(set(priorities)) == len(priorities):
- repeated_priority = get_repeated(priorities)
- frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
-
- # Check if repeated default priority
- if not len(set(default_priority)) == len(default_priority):
- frappe.throw(_("Select only one Priority as Default."))
-
- # set default priority from priorities
- try:
- self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
- except Exception:
- frappe.throw(_("Select a Default Priority."))
-
- def check_support_and_resolution(self):
- week = get_weekdays()
- support_days = []
-
- for support_and_resolution in self.support_and_resolution:
- # Check if start and end time is set for every support day
- if not (support_and_resolution.start_time or support_and_resolution.end_time):
- frappe.throw(_("Set Start Time and End Time for \
- Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
-
- support_days.append(support_and_resolution.workday)
- support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
-
- if support_and_resolution.start_time >= support_and_resolution.end_time:
- frappe.throw(_("Start Time can't be greater than or equal to End Time \
- for {0}.".format(support_and_resolution.workday)))
-
- # Check for repeated workday
- if not len(set(support_days)) == len(support_days):
- repeated_days = get_repeated(support_days)
- frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
-
-def get_repeated(values):
- unique_list = []
- diff = []
- for value in values:
- if value not in unique_list:
- unique_list.append(str(value))
- else:
- if value not in diff:
- diff.append(str(value))
- return " ".join(diff)
diff --git a/erpnext/support/doctype/service_level/service_level_dashboard.py b/erpnext/support/doctype/service_level/service_level_dashboard.py
deleted file mode 100644
index 393095e..0000000
--- a/erpnext/support/doctype/service_level/service_level_dashboard.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from frappe import _
-
-def get_data():
- return {
- 'fieldname': 'service_level',
- 'transactions': [
- {
- 'label': _('Service Level Agreement'),
- 'items': ['Service Level Agreement']
- }
- ]
- }
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level/test_service_level.py b/erpnext/support/doctype/service_level/test_service_level.py
deleted file mode 100644
index 09577df..0000000
--- a/erpnext/support/doctype/service_level/test_service_level.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
-from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
-
-import frappe
-import unittest
-
-class TestServiceLevel(unittest.TestCase):
-
- def test_service_level(self):
- employee_group = make_employee_group()
- make_holiday_list()
- make_priorities()
-
- # Default Service Level
- test_make_service_level = create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
- get_make_service_level = get_service_level("__Test Service Level")
-
- self.assertEqual(test_make_service_level.name, get_make_service_level.name)
- self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
- self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
-
- # Service Level
- test_make_service_level = create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
- get_make_service_level = get_service_level("_Test Service Level")
-
- self.assertEqual(test_make_service_level.name, get_make_service_level.name)
- self.assertEqual(test_make_service_level.holiday_list, get_make_service_level.holiday_list)
- self.assertEqual(test_make_service_level.employee_group, get_make_service_level.employee_group)
-
-
-def create_service_level(service_level, holiday_list, employee_group, response_time, resolution_time):
- sl = frappe.get_doc({
- "doctype": "Service Level",
- "service_level": service_level,
- "holiday_list": holiday_list,
- "employee_group": employee_group,
- "priorities": [
- {
- "priority": "Low",
- "response_time": response_time,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- },
- {
- "priority": "Medium",
- "response_time": response_time,
- "default_priority": 1,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- },
- {
- "priority": "High",
- "response_time": response_time,
- "response_time_period": "Hour",
- "resolution_time": resolution_time,
- "resolution_time_period": "Hour",
- }
- ],
- "support_and_resolution": [
- {
- "workday": "Monday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Tuesday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Wednesday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Thursday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Friday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Saturday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- },
- {
- "workday": "Sunday",
- "start_time": "10:00:00",
- "end_time": "18:00:00",
- }
- ]
- })
-
- sl_exists = frappe.db.exists("Service Level", {"service_level": service_level})
-
- if not sl_exists:
- sl.insert()
- return sl
- else:
- return frappe.get_doc("Service Level", {"service_level": service_level})
-
-def get_service_level(service_level):
- return frappe.get_doc("Service Level", service_level)
-
-def make_holiday_list():
- holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
- if not holiday_list:
- now = frappe.utils.now_datetime()
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": "__Test Holiday List",
- "from_date": "2019-01-01",
- "to_date": "2019-12-31",
- "holidays": [
- {
- "description": "Test Holiday 1",
- "holiday_date": "2019-03-05"
- },
- {
- "description": "Test Holiday 2",
- "holiday_date": "2019-03-07"
- },
- {
- "description": "Test Holiday 3",
- "holiday_date": "2019-02-11"
- },
- ]
- }).insert()
-
-def create_service_level_for_sla():
- employee_group = make_employee_group()
- make_holiday_list()
- make_priorities()
-
- # Default Service Level
- create_service_level("__Test Service Level", "__Test Holiday List", employee_group, 4, 6)
-
- # Service Level
- create_service_level("_Test Service Level", "__Test Holiday List", employee_group, 2, 3)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 1d486f4..5346195 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -2,28 +2,15 @@
// For license information, please see license.txt
frappe.ui.form.on('Service Level Agreement', {
- service_level: function(frm) {
- frm.fields_dict.support_and_resolution.grid.remove_all();
- frappe.call({
- "method": "frappe.client.get",
- args: {
- doctype: "Service Level",
- name: frm.doc.service_level
- },
- callback: function(data){
- let count = Math.max(data.message.priorities.length, data.message.support_and_resolution.length);
- let i = 0;
- while (i < count){
- if (data.message.priorities[i]) {
- frm.add_child("priorities", data.message.priorities[i]);
- }
- if (data.message.support_and_resolution[i]) {
- frm.add_child("support_and_resolution", data.message.support_and_resolution[i]);
- }
- i++;
- }
- frm.refresh();
- }
+ setup: function(frm) {
+ let allow_statuses = [];
+ const exclude_statuses = ['Open', 'Closed', 'Resolved'];
+
+ frappe.model.with_doctype('Issue', () => {
+ let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
+ statuses = statuses.split('\n');
+ allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
+ frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
});
- },
-});
+ }
+});
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index 9a83ca7..939c199 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "format:SLA-{service_level}-{####}",
"creation": "2018-12-26 21:08:15.448812",
"doctype": "DocType",
@@ -6,12 +7,13 @@
"engine": "InnoDB",
"field_order": [
"enable",
+ "section_break_2",
"service_level",
+ "default_priority",
"default_service_level_agreement",
- "holiday_list",
"column_break_2",
"employee_group",
- "default_priority",
+ "holiday_list",
"entity_section",
"entity_type",
"column_break_10",
@@ -21,6 +23,8 @@
"active",
"column_break_7",
"end_date",
+ "section_break_18",
+ "pause_sla_on",
"response_and_resolution_time_section",
"priorities",
"support_and_resolution_section_break",
@@ -28,42 +32,31 @@
],
"fields": [
{
- "default": "0",
- "depends_on": "eval: !doc.customer;",
- "fieldname": "default_service_level_agreement",
- "fieldtype": "Check",
- "label": "Default Service Level Agreement"
- },
- {
"fieldname": "service_level",
- "fieldtype": "Link",
+ "fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Service Level",
- "options": "Service Level",
"reqd": 1
},
{
- "fetch_from": "service_level.holiday_list",
"fieldname": "holiday_list",
"fieldtype": "Link",
"label": "Holiday List",
"options": "Holiday List",
- "read_only": 1
+ "reqd": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
- "fetch_from": "service_level.employee_group",
"fieldname": "employee_group",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee Group",
- "options": "Employee Group",
- "read_only": 1
+ "options": "Employee Group"
},
{
"fieldname": "agreement_details_section",
@@ -103,21 +96,15 @@
"fieldname": "support_and_resolution",
"fieldtype": "Table",
"label": "Support and Resolution",
- "options": "Service Day"
+ "options": "Service Day",
+ "reqd": 1
},
{
"fieldname": "priorities",
"fieldtype": "Table",
"label": "Priorities",
- "options": "Service Level Priority"
- },
- {
- "fetch_from": "service_level.default_priority",
- "fieldname": "default_priority",
- "fieldtype": "Link",
- "label": "Default Priority",
- "options": "Issue Priority",
- "read_only": 1
+ "options": "Service Level Priority",
+ "reqd": 1
},
{
"default": "1",
@@ -156,9 +143,38 @@
"fieldname": "enable",
"fieldtype": "Check",
"label": "Enable"
+ },
+ {
+ "fieldname": "section_break_2",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fieldname": "default_service_level_agreement",
+ "fieldtype": "Check",
+ "label": "Default Service Level Agreement"
+ },
+ {
+ "fieldname": "default_priority",
+ "fieldtype": "Link",
+ "label": "Default Priority",
+ "options": "Issue Priority",
+ "read_only": 1
+ },
+ {
+ "fieldname": "section_break_18",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "pause_sla_on",
+ "fieldtype": "Table",
+ "label": "Pause SLA On",
+ "options": "Pause SLA On Status"
}
],
- "modified": "2019-07-09 17:22:16.402939",
+ "links": [],
+ "modified": "2020-06-10 12:30:15.050785",
"modified_by": "Administrator",
"module": "Support",
"name": "Service Level Agreement",
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index a399c58..c692315 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -6,11 +6,73 @@
import frappe
from frappe.model.document import Document
from frappe import _
-from frappe.utils import getdate
+from frappe.utils import getdate, get_weekdays
class ServiceLevelAgreement(Document):
def validate(self):
+ self.validate_doc()
+ self.check_priorities()
+ self.check_support_and_resolution()
+
+ def check_priorities(self):
+ default_priority = []
+ priorities = []
+
+ for priority in self.priorities:
+ # Check if response and resolution time is set for every priority
+ if not (priority.response_time or priority.resolution_time):
+ frappe.throw(_("Set Response Time and Resolution for Priority {0} at index {1}.").format(priority.priority, priority.idx))
+
+ priorities.append(priority.priority)
+
+ if priority.default_priority:
+ default_priority.append(priority.default_priority)
+
+ response = priority.response_time
+ resolution = priority.resolution_time
+
+ if response > resolution:
+ frappe.throw(_("Response Time for {0} at index {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
+
+ # Check if repeated priority
+ if not len(set(priorities)) == len(priorities):
+ repeated_priority = get_repeated(priorities)
+ frappe.throw(_("Priority {0} has been repeated.").format(repeated_priority))
+
+ # Check if repeated default priority
+ if not len(set(default_priority)) == len(default_priority):
+ frappe.throw(_("Select only one Priority as Default."))
+
+ # set default priority from priorities
+ try:
+ self.default_priority = next(d.priority for d in self.priorities if d.default_priority)
+ except Exception:
+ frappe.throw(_("Select a Default Priority."))
+
+ def check_support_and_resolution(self):
+ week = get_weekdays()
+ support_days = []
+
+ for support_and_resolution in self.support_and_resolution:
+ # Check if start and end time is set for every support day
+ if not (support_and_resolution.start_time or support_and_resolution.end_time):
+ frappe.throw(_("Set Start Time and End Time for \
+ Support Day {0} at index {1}.".format(support_and_resolution.workday, support_and_resolution.idx)))
+
+ support_days.append(support_and_resolution.workday)
+ support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
+
+ if support_and_resolution.start_time >= support_and_resolution.end_time:
+ frappe.throw(_("Start Time can't be greater than or equal to End Time \
+ for {0}.".format(support_and_resolution.workday)))
+
+ # Check for repeated workday
+ if not len(set(support_days)) == len(support_days):
+ repeated_days = get_repeated(support_days)
+ frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
+
+ def validate_doc(self):
if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
frappe.throw(_("Service Level Agreement tracking is not enabled."))
@@ -35,9 +97,7 @@
return frappe._dict({
"priority": priority.priority,
"response_time": priority.response_time,
- "response_time_period": priority.response_time_period,
- "resolution_time": priority.resolution_time,
- "resolution_time_period": priority.resolution_time_period
+ "resolution_time": priority.resolution_time
})
def check_agreement_status():
@@ -110,4 +170,15 @@
return {
"priority": [priority.priority for priority in frappe.get_list("Service Level Priority", filters={"parent": name}, fields=["priority"])],
"service_level_agreements": [d.name for d in frappe.get_list("Service Level Agreement", filters=filters, or_filters=or_filters)]
- }
\ No newline at end of file
+ }
+
+def get_repeated(values):
+ unique_list = []
+ diff = []
+ for value in values:
+ if value not in unique_list:
+ unique_list.append(str(value))
+ else:
+ if value not in diff:
+ diff.append(str(value))
+ return " ".join(diff)
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index 4a741ea..07ef368 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -5,19 +5,20 @@
import frappe
import unittest
-from erpnext.support.doctype.service_level.test_service_level import create_service_level_for_sla
+from erpnext.hr.doctype.employee_group.test_employee_group import make_employee_group
+from erpnext.support.doctype.issue_priority.test_issue_priority import make_priorities
class TestServiceLevelAgreement(unittest.TestCase):
-
- def test_service_level_agreement(self):
+ def setUp(self):
+ frappe.db.sql("delete from `tabService Level Agreement`")
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
- create_service_level_for_sla()
-
+ def test_service_level_agreement(self):
# Default Service Level Agreement
create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
- service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type=None, entity=None, response_time=4, resolution_time=6)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+
get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
@@ -28,8 +29,8 @@
# Service Level Agreement for Customer
customer = create_customer()
create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer", entity=customer, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Customer", entity=customer, response_time=7200, resolution_time=10800)
get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
@@ -40,8 +41,8 @@
# Service Level Agreement for Customer Group
customer_group = create_customer_group()
create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer Group", entity=customer_group, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Customer Group", entity=customer_group, response_time=7200, resolution_time=10800)
get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
@@ -52,8 +53,8 @@
# Service Level Agreement for Territory
territory = create_territory()
create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Territory", entity=territory, response_time=2, resolution_time=3)
+ holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
+ entity_type="Territory", entity=territory, response_time=7200, resolution_time=10800)
get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
@@ -71,14 +72,19 @@
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
return service_level_agreement
-def create_service_level_agreement(default_service_level_agreement, service_level, holiday_list, employee_group,
+def create_service_level_agreement(default_service_level_agreement, holiday_list, employee_group,
response_time, entity_type, entity, resolution_time):
+ employee_group = make_employee_group()
+ make_holiday_list()
+ make_priorities()
+
service_level_agreement = frappe.get_doc({
"doctype": "Service Level Agreement",
"enable": 1,
+ "service_level": "__Test Service Level",
"default_service_level_agreement": default_service_level_agreement,
- "service_level": service_level,
+ "default_priority": "Medium",
"holiday_list": holiday_list,
"employee_group": employee_group,
"entity_type": entity_type,
@@ -109,6 +115,11 @@
"resolution_time_period": "Hour",
}
],
+ "pause_sla_on": [
+ {
+ "status": "Replied"
+ }
+ ],
"support_and_resolution": [
{
"workday": "Monday",
@@ -167,6 +178,7 @@
else:
return frappe.get_doc("Service Level Agreement", service_level_agreement_exists)
+
def create_customer():
customer = frappe.get_doc({
"doctype": "Customer",
@@ -206,23 +218,41 @@
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
def create_service_level_agreements_for_issues():
- create_service_level_for_sla()
-
- create_service_level_agreement(default_service_level_agreement=1,
- service_level="__Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type=None, entity=None, response_time=4, resolution_time=6)
+ create_service_level_agreement(default_service_level_agreement=1, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
create_customer()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer", entity="_Test Customer", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
create_customer_group()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
create_territory()
- create_service_level_agreement(default_service_level_agreement=0,
- service_level="_Test Service Level", holiday_list="__Test Holiday List", employee_group="_Test Employee Group",
- entity_type="Territory", entity="_Test SLA Territory", response_time=2, resolution_time=3)
+ create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
+ employee_group="_Test Employee Group", entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
+
+def make_holiday_list():
+ holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
+ if not holiday_list:
+ holiday_list = frappe.get_doc({
+ "doctype": "Holiday List",
+ "holiday_list_name": "__Test Holiday List",
+ "from_date": "2019-01-01",
+ "to_date": "2019-12-31",
+ "holidays": [
+ {
+ "description": "Test Holiday 1",
+ "holiday_date": "2019-03-05"
+ },
+ {
+ "description": "Test Holiday 2",
+ "holiday_date": "2019-03-07"
+ },
+ {
+ "description": "Test Holiday 3",
+ "holiday_date": "2019-02-11"
+ },
+ ]
+ }).insert()
diff --git a/erpnext/support/doctype/service_level_priority/service_level_priority.json b/erpnext/support/doctype/service_level_priority/service_level_priority.json
index cd87a1c..65d5169 100644
--- a/erpnext/support/doctype/service_level_priority/service_level_priority.json
+++ b/erpnext/support/doctype/service_level_priority/service_level_priority.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-05-04 05:54:03.658991",
"doctype": "DocType",
"editable_grid": 1,
@@ -9,10 +10,8 @@
"default_priority",
"sb_00",
"response_time",
- "response_time_period",
"cb_00",
- "resolution_time",
- "resolution_time_period"
+ "resolution_time"
],
"fields": [
{
@@ -28,16 +27,11 @@
"fieldtype": "Section Break"
},
{
- "columns": 1,
- "fieldname": "response_time",
- "fieldtype": "Int",
- "in_list_view": 1,
- "label": "Response Time"
- },
- {
- "columns": 1,
+ "columns": 2,
"fieldname": "resolution_time",
- "fieldtype": "Int",
+ "fieldtype": "Duration",
+ "hide_days": 1,
+ "hide_seconds": 1,
"in_list_view": 1,
"label": "Resolution Time"
},
@@ -46,35 +40,30 @@
"fieldtype": "Column Break"
},
{
- "columns": 2,
- "fieldname": "response_time_period",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Response Time Period",
- "options": "Hour\nDay\nWeek"
- },
- {
- "columns": 2,
- "fieldname": "resolution_time_period",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Resolution Time Period",
- "options": "Hour\nDay\nWeek"
- },
- {
"fieldname": "cb_01",
"fieldtype": "Column Break"
},
{
+ "columns": 1,
"default": "0",
"fieldname": "default_priority",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Default Priority"
+ },
+ {
+ "columns": 2,
+ "fieldname": "response_time",
+ "fieldtype": "Duration",
+ "hide_days": 1,
+ "hide_seconds": 1,
+ "in_list_view": 1,
+ "label": "First Response Time"
}
],
"istable": 1,
- "modified": "2019-05-21 06:54:42.674377",
+ "links": [],
+ "modified": "2020-06-10 12:45:47.545915",
"modified_by": "Administrator",
"module": "Support",
"name": "Service Level Priority",
@@ -84,4 +73,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/support/doctype/support_settings/support_settings.js b/erpnext/support/doctype/support_settings/support_settings.js
index 1d1069d..78adca8 100644
--- a/erpnext/support/doctype/support_settings/support_settings.js
+++ b/erpnext/support/doctype/support_settings/support_settings.js
@@ -3,6 +3,6 @@
frappe.ui.form.on('Support Settings', {
refresh: function(frm) {
-
+ //
}
});
diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json
index be9e064..5d3d3ac 100644
--- a/erpnext/support/doctype/support_settings/support_settings.json
+++ b/erpnext/support/doctype/support_settings/support_settings.json
@@ -1,4 +1,5 @@
{
+ "actions": "",
"creation": "2017-02-17 13:07:35.686409",
"doctype": "DocType",
"editable_grid": 1,
@@ -21,6 +22,10 @@
"post_description_key",
"post_route_key",
"post_route_string",
+ "greetings_section_section",
+ "greeting_title",
+ "column_break_19",
+ "greeting_subtitle",
"search_apis_sb",
"search_apis"
],
@@ -122,13 +127,44 @@
},
{
"default": "0",
+ "depends_on": "eval:doc.track_service_level_agreement;",
"fieldname": "allow_resetting_service_level_agreement",
"fieldtype": "Check",
"label": "Allow Resetting Service Level Agreement"
+ },
+ {
+ "default": "We're here to help",
+ "fieldname": "greeting_title",
+ "fieldtype": "Data",
+ "label": "Greeting Title",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "column_break_19",
+ "fieldtype": "Column Break",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "default": "Browse help topics",
+ "fieldname": "greeting_subtitle",
+ "fieldtype": "Data",
+ "label": "Greeting Subtitle",
+ "show_days": 1,
+ "show_seconds": 1
+ },
+ {
+ "fieldname": "greetings_section_section",
+ "fieldtype": "Section Break",
+ "label": "Greetings Section",
+ "show_days": 1,
+ "show_seconds": 1
}
],
"issingle": 1,
- "modified": "2019-07-10 22:52:39.663873",
+ "links": [],
+ "modified": "2020-06-11 13:08:38.473616",
"modified_by": "Administrator",
"module": "Support",
"name": "Support Settings",
diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json
index 0f15e47..1df9fb7 100644
--- a/erpnext/support/web_form/issues/issues.json
+++ b/erpnext/support/web_form/issues/issues.json
@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2020-03-06 05:24:05.749664",
+ "modified": "2020-05-19 13:01:10.729088",
"modified_by": "Administrator",
"module": "Support",
"name": "issues",
@@ -76,7 +76,7 @@
{
"allow_read_on_all_link_options": 0,
"fieldname": "description",
- "fieldtype": "Text",
+ "fieldtype": "Text Editor",
"hidden": 0,
"label": "Description",
"max_length": 0,
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 5fd9011..163c955 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -193,14 +193,17 @@
filtered_items_count === 1 ?
filtered_items[0] : '';
+ // Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock.
+ const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock;
+ const add_to_cart = `<a href data-action="btn_add_to_cart" data-item-code="${one_item}">${__('Add to cart')}</a>`;
+ const product_action = in_stock ? add_to_cart : `<a style="color:#74808b;">${__('Not in Stock')}</a>`;
+
const item_add_to_cart = one_item ? `
<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
<div>
<div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
</div>
- <a href data-action="btn_add_to_cart" data-item-code="${one_item}">
- ${__('Add to cart')}
- </a>
+ ${product_action}
</div>
`: '';
diff --git a/erpnext/templates/generators/student_admission.html b/erpnext/templates/generators/student_admission.html
index ae70df8..8b15344 100644
--- a/erpnext/templates/generators/student_admission.html
+++ b/erpnext/templates/generators/student_admission.html
@@ -14,12 +14,12 @@
{%- if introduction -%}
<div>{{ introduction }}</div>
-{% endif %}
+{% endif %}
-{%- if application_form_route -%}
+{%- if doc.enable_admission_application -%}
<p>
<a class='btn btn-primary'
- href='/{{ doc.application_form_route }}'>
+ href='/student-applicant'>
{{ _("Apply Now") }}</a>
</p>
{% endif %}
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
index c91723e..646210e 100644
--- a/erpnext/templates/includes/cart/address_card.html
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -3,7 +3,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
</div>
<div class="card-body">
- <h5 class="card-title">{{ address.name }}</h5>
+ <h5 class="card-title">{{ address.title }}</h5>
<p class="card-text text-muted">
{{ address.display }}
</p>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 60de3af..aa25c88 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -109,7 +109,7 @@
reqd: 1
},
{
- label: __('Pin Code'),
+ label: __('Postal Code'),
fieldname: 'pincode',
fieldtype: 'Data'
},
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index ce0f47d..df715ab 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -24,7 +24,7 @@
woo_settings.creation_user = "Administrator"
woo_settings.save(ignore_permissions=True)
- def test_sales_order_for_woocommerece(self):
+ def test_sales_order_for_woocommerce(self):
frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}
order()
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index ea96503..024aa6f 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -166,7 +166,7 @@
last_transaction_time = frappe.db.sql("""
select MAX(timestamp(posting_date, posting_time)) as posting_time
from `tabStock Ledger Entry`
- where docstatus = 1 and fiscal_year = %s""", (fiscal_year))[0][0]
+ where docstatus = 1""")[0][0]
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index f090214..0126b59 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -11,7 +11,7 @@
<div class="input-group input-group-sm mb-3">
<input type="search" class="form-control" placeholder="{{_('Search')}}"
aria-label="{{_('Product Search')}}" aria-describedby="product-search"
- value="{{ frappe.form_dict.search or '' }}"
+ value="{{ frappe.sanitize_html(frappe.form_dict.search) or '' }}"
>
</div>
</div>
diff --git a/erpnext/hr/report/department_analytics/__init__.py b/erpnext/www/support/__init__.py
similarity index 100%
copy from erpnext/hr/report/department_analytics/__init__.py
copy to erpnext/www/support/__init__.py
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
new file mode 100644
index 0000000..93da503
--- /dev/null
+++ b/erpnext/www/support/index.html
@@ -0,0 +1,58 @@
+{% extends "templates/web.html" %}
+
+{% block content %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <div class="hero-content">
+ <h1 class="hero-title">{{ greeting_title or _("We're here to help!") }}</h1>
+ {% if greeting_subtitle %}
+ <p class="hero-subtitle">{{ greeting_subtitle }}</p>
+ {% endif %}
+ </div>
+ </div>
+</section>
+
+{% if favorite_article_list %}
+<section class="section section-padding-top section-padding-bottom bg-light">
+ <div class='container'>
+ <h2>{{ _("Frequently Read Articles") }}</h2>
+ <div class="row">
+ {% for favorite_article in favorite_article_list %}
+ <div class="mt-4 col-12 col-sm-6 col-lg-4">
+ <div class="card card-md h-100 kb-card">
+ <div class="card-body">
+ <h6 class="card-subtitle mb-2 text-uppercase small text-muted">
+ {{ favorite_article['category'] }}</h6>
+ <h3 class="card-title">{{ favorite_article['title'] }}</h3>
+ <p class="card-text">{{ favorite_article['description'] }}</p>
+ </div>
+ <a href="{{ favorite_article['route'] }}" class="stretched-link"></a>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% if help_article_list %}
+<section class="section section-padding-top section-padding-bottom">
+ <div class='container'>
+ <h2>{{ _("Help Articles") }}</h2>
+ <div class="row">
+ {% for item in help_article_list %}
+ <div class="mt-5 col-12 col-sm-6 col-lg-4">
+ <h5>{{ item['category'].name }}</h5>
+ <div>
+ {% for article in item['articles'] %}
+ <a href="{{ article.route }}" class="mt-2 d-block">{{ article.title }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+</section>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
new file mode 100644
index 0000000..5d26743
--- /dev/null
+++ b/erpnext/www/support/index.py
@@ -0,0 +1,74 @@
+from __future__ import unicode_literals
+import frappe
+
+def get_context(context):
+ context.no_cache = 1
+ context.align_greeting = ''
+ setting = frappe.get_doc("Support Settings")
+
+ context.greeting_title = setting.greeting_title
+ context.greeting_subtitle = setting.greeting_subtitle
+
+ # Support content
+ favorite_articles = get_favorite_articles_by_page_view()
+ if len(favorite_articles) < 6:
+ name_list = []
+ if favorite_articles:
+ for article in favorite_articles:
+ name_list.append(article.name)
+ for record in (frappe.get_all("Help Article",
+ fields=["title", "content", "route", "category"],
+ filters={"name": ['not in', tuple(name_list)], "published": 1},
+ order_by="creation desc", limit=(6-len(favorite_articles)))):
+ favorite_articles.append(record)
+
+ context.favorite_article_list = get_favorite_articles(favorite_articles)
+ context.help_article_list = get_help_article_list()
+
+def get_favorite_articles_by_page_view():
+ return frappe.db.sql(
+ """
+ SELECT
+ t1.name as name,
+ t1.title as title,
+ t1.content as content,
+ t1.route as route,
+ t1.category as category,
+ count(t1.route) as count
+ FROM `tabHelp Article` AS t1
+ INNER JOIN
+ `tabWeb Page View` AS t2
+ ON t1.route = t2.path
+ WHERE t1.published = 1
+ GROUP BY route
+ ORDER BY count DESC
+ LIMIT 6;
+ """, as_dict=True)
+
+def get_favorite_articles(favorite_articles):
+ favorite_article_list=[]
+ for article in favorite_articles:
+ description = frappe.utils.strip_html(article.content)
+ if len(description) > 120:
+ description = description[:120] + '...'
+ favorite_article_dict = {
+ 'title': article.title,
+ 'description': description,
+ 'route': article.route,
+ 'category': article.category,
+ }
+ favorite_article_list.append(favorite_article_dict)
+ return favorite_article_list
+
+def get_help_article_list():
+ help_article_list=[]
+ category_list = frappe.get_all("Help Category", fields="name")
+ for category in category_list:
+ help_articles = frappe.get_all("Help Article", fields="*", filters={"category": category.name, "published": 1}, order_by="modified desc", limit=5)
+ if help_articles:
+ help_aricles_per_caetgory = {
+ 'category': category,
+ 'articles': help_articles,
+ }
+ help_article_list.append(help_aricles_per_caetgory)
+ return help_article_list
\ No newline at end of file