From 10d9974e7721257fa1b1b87a6cb0506db9b01c0b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 30 Apr 2020 16:28:52 +0530 Subject: [PATCH 01/47] chore: add total row in sales analytics report --- .../sales_analytics/sales_analytics.json | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json index 71932610a6..cbc5999248 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.json +++ b/erpnext/selling/report/sales_analytics/sales_analytics.json @@ -1,31 +1,31 @@ { - "add_total_row": 0, - "creation": "2018-09-21 12:46:29.451048", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2019-05-24 05:37:02.866139", - "modified_by": "Administrator", - "module": "Selling", - "name": "Sales Analytics", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Sales Order", - "report_name": "Sales Analytics", - "report_type": "Script Report", + "add_total_row": 1, + "creation": "2018-09-21 12:46:29.451048", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-04-30 16:27:53.112907", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Order", + "report_name": "Sales Analytics", + "report_type": "Script Report", "roles": [ { "role": "Stock User" - }, + }, { "role": "Maintenance User" - }, + }, { "role": "Accounts User" - }, + }, { "role": "Sales Manager" } From dfa60e0ed8f428a4bd789a6055b645eab7c57ff8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 30 Apr 2020 20:06:30 +0530 Subject: [PATCH 02/47] chore: calculate total row month-wise in sales analytics --- .../report/sales_analytics/sales_analytics.json | 4 ++-- .../report/sales_analytics/sales_analytics.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json index cbc5999248..bf9edd6cd4 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.json +++ b/erpnext/selling/report/sales_analytics/sales_analytics.json @@ -1,5 +1,5 @@ { - "add_total_row": 1, + "add_total_row": 0, "creation": "2018-09-21 12:46:29.451048", "disable_prepared_report": 0, "disabled": 0, @@ -7,7 +7,7 @@ "doctype": "Report", "idx": 0, "is_standard": "Yes", - "modified": "2020-04-30 16:27:53.112907", + "modified": "2020-04-30 19:49:02.303320", "modified_by": "Administrator", "module": "Selling", "name": "Sales Analytics", diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index f1726ab8bf..3fc4633ae3 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -194,6 +194,9 @@ class Analytics(object): def get_rows(self): self.data = [] self.get_periodic_data() + total_row = { + "entity": "Total", + } for entity, period_data in iteritems(self.entity_periodic_data): row = { @@ -207,6 +210,9 @@ class Analytics(object): row[scrub(period)] = amount total += amount + if not total_row.get(scrub(period)): total_row[scrub(period)] = 0 + total_row[scrub(period)] += amount + row["total"] = total if self.filters.tree_type == "Item": @@ -214,9 +220,14 @@ class Analytics(object): self.data.append(row) + self.data.append(total_row) + def get_rows_by_group(self): self.get_periodic_data() out = [] + total_row = { + "entity": "Total", + } for d in reversed(self.group_entries): row = { @@ -232,8 +243,14 @@ class Analytics(object): self.entity_periodic_data.setdefault(d.parent, frappe._dict()).setdefault(period, 0.0) self.entity_periodic_data[d.parent][period] += amount total += amount + + if not total_row.get(scrub(period)): total_row[scrub(period)] = 0 + total_row[scrub(period)] += amount + row["total"] = total out = [row] + out + + out.append(total_row) self.data = out def get_periodic_data(self): From 971170c59025cfebd38c7bb7a2bd9633064625d8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 2 May 2020 19:39:03 +0530 Subject: [PATCH 03/47] fix: incorrect total in sales analytics for customer/item group --- erpnext/selling/report/sales_analytics/sales_analytics.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 3fc4633ae3..c0c11339ca 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -225,9 +225,6 @@ class Analytics(object): def get_rows_by_group(self): self.get_periodic_data() out = [] - total_row = { - "entity": "Total", - } for d in reversed(self.group_entries): row = { @@ -245,12 +242,10 @@ class Analytics(object): total += amount if not total_row.get(scrub(period)): total_row[scrub(period)] = 0 - total_row[scrub(period)] += amount row["total"] = total out = [row] + out - out.append(total_row) self.data = out def get_periodic_data(self): From 826b39559db854df6a6c22447b7aacdb379055bc Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 2 May 2020 19:50:45 +0530 Subject: [PATCH 04/47] fix: review fixes --- erpnext/selling/report/sales_analytics/sales_analytics.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index c0c11339ca..97d9322918 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -241,8 +241,6 @@ class Analytics(object): self.entity_periodic_data[d.parent][period] += amount total += amount - if not total_row.get(scrub(period)): total_row[scrub(period)] = 0 - row["total"] = total out = [row] + out From ab9bb08c4b100fc4abf2361968c9706cb03781da Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 3 May 2020 14:53:13 +0530 Subject: [PATCH 05/47] fix: test --- .../report/sales_analytics/test_analytics.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 4d81a1e4dd..0a5198ccc5 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -83,6 +83,22 @@ class TestAnalytics(unittest.TestCase): "feb_2018": 0.0, "mar_2018": 0.0, "total": 3000.0 + }, + { + "entity": "Total", + "entity_name": "", + "apr_2017": 0.0, + "may_2017": 0.0, + "jun_2017": 2000.0, + "jul_2017": 1000.0, + "aug_2017": 0.0, + "sep_2017": 1500.0, + "oct_2017": 1000.0, + "nov_2017": 0.0, + "dec_2017": 0.0, + "jan_2018": 0.0, + "feb_2018": 0.0, + "mar_2018": 0.0 } ] result = sorted(report[1], key=lambda k: k['entity']) From 52cc6ad456940798be40923e216898e1b3a6082e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 4 May 2020 20:24:00 +0530 Subject: [PATCH 06/47] fix: test --- .../report/sales_analytics/test_analytics.py | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 0a5198ccc5..7e8501dcc1 100644 --- a/erpnext/selling/report/sales_analytics/test_analytics.py +++ b/erpnext/selling/report/sales_analytics/test_analytics.py @@ -33,6 +33,21 @@ class TestAnalytics(unittest.TestCase): report = execute(filters) expected_data = [ + { + 'entity': 'Total', + 'apr_2017': 0.0, + 'may_2017': 0.0, + 'jun_2017': 2000.0, + 'jul_2017': 1000.0, + 'aug_2017': 0.0, + 'sep_2017': 1500.0, + 'oct_2017': 1000.0, + 'nov_2017': 0.0, + 'dec_2017': 0.0, + 'jan_2018': 0.0, + 'feb_2018': 2000.0, + 'mar_2018': 0.0 + }, { "entity": "_Test Customer 1", "entity_name": "_Test Customer 1", @@ -83,22 +98,6 @@ class TestAnalytics(unittest.TestCase): "feb_2018": 0.0, "mar_2018": 0.0, "total": 3000.0 - }, - { - "entity": "Total", - "entity_name": "", - "apr_2017": 0.0, - "may_2017": 0.0, - "jun_2017": 2000.0, - "jul_2017": 1000.0, - "aug_2017": 0.0, - "sep_2017": 1500.0, - "oct_2017": 1000.0, - "nov_2017": 0.0, - "dec_2017": 0.0, - "jan_2018": 0.0, - "feb_2018": 0.0, - "mar_2018": 0.0 } ] result = sorted(report[1], key=lambda k: k['entity']) @@ -150,6 +149,21 @@ class TestAnalytics(unittest.TestCase): report = execute(filters) expected_data = [ + { + 'entity': 'Total', + 'apr_2017': 0.0, + 'may_2017': 0.0, + 'jun_2017': 20.0, + 'jul_2017': 10.0, + 'aug_2017': 0.0, + 'sep_2017': 15.0, + 'oct_2017': 10.0, + 'nov_2017': 0.0, + 'dec_2017': 0.0, + 'jan_2018': 0.0, + 'feb_2018': 20.0, + 'mar_2018': 0.0 + }, { "entity": "_Test Customer 1", "entity_name": "_Test Customer 1", From 6e95d248e35abeebeae65c65fa788afa1e1bc925 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 May 2020 16:15:07 +0530 Subject: [PATCH 07/47] fix: work order operation completed qty --- erpnext/manufacturing/doctype/job_card/job_card.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e9627a5514..e43b98aee1 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -206,30 +206,31 @@ class JobCard(Document): for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] - + field = "operation_id" if self.operation_id else "operation" data = frappe.get_all('Job Card', fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], filters = {"docstatus": 1, "work_order": self.work_order, - "workstation": self.workstation, "operation": self.operation}) + "workstation": self.workstation, field: self.get(field)}) if data and len(data) > 0: for_quantity = data[0].completed_qty time_in_mins = data[0].time_in_mins - if for_quantity: + if self.get(field): time_data = frappe.db.sql(""" SELECT min(from_time) as start_time, max(to_time) as end_time FROM `tabJob Card` jc, `tabJob Card Time Log` jctl WHERE jctl.parent = jc.name and jc.work_order = %s - and jc.workstation = %s and jc.operation = %s and jc.docstatus = 1 - """, (self.work_order, self.workstation, self.operation), as_dict=1) + and jc.workstation = %s and jc.{0} = %s and jc.docstatus = 1 + """.format(field), (self.work_order, self.workstation, self.get(field)), as_dict=1) wo = frappe.get_doc('Work Order', self.work_order) + work_order_field = "name" if field == "operation_id" else field for data in wo.operations: - if data.workstation == self.workstation and data.operation == self.operation: + if data.get(work_order_field) == self.get(field) and data.workstation == self.workstation: data.completed_qty = for_quantity data.actual_operation_time = time_in_mins data.actual_start_time = time_data[0].start_time if time_data else None From d078883cd436ae878f6b38607f1f3db693f62d04 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 6 May 2020 01:13:51 +0530 Subject: [PATCH 08/47] fixing callback urls --- .../linkedin_settings/linkedin_settings.js | 2 ++ .../linkedin_settings/linkedin_settings.py | 6 +++--- .../twitter_settings/twitter_settings.js | 2 ++ .../twitter_settings/twitter_settings.py | 19 +++++++++++-------- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js index 50b98e9ce1..263005ef6c 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js @@ -62,6 +62,8 @@ frappe.ui.form.on('LinkedIn Settings', { callback : function(r) { window.location.href = r.message; } + }).fail(function() { + frappe.dom.unfreeze(); }); } }, diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 5df35df3dd..bdde9eed37 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -15,7 +15,7 @@ class LinkedInSettings(Document): params = urlencode({ "response_type":"code", "client_id": self.consumer_key, - "redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback", + "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?", "scope": "r_emailaddress w_organization_social r_basicprofile r_liteprofile r_organization_social rw_organization_admin w_member_social" }) @@ -30,7 +30,7 @@ class LinkedInSettings(Document): "code": code, "client_id": self.consumer_key, "client_secret": self.get_password(fieldname="consumer_secret"), - "redirect_uri": get_site_url(frappe.local.site) + "/?cmd=erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback", + "redirect_uri": get_site_url(frappe.local.site) + "/api/method/erpnext.crm.doctype.linkedin_settings.linkedin_settings.callback?", } headers = { "Content-Type": "application/x-www-form-urlencoded" @@ -154,7 +154,7 @@ class LinkedInSettings(Document): return response -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def callback(code=None, error=None, error_description=None): if not error: linkedin_settings = frappe.get_doc("LinkedIn Settings") diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.js b/erpnext/crm/doctype/twitter_settings/twitter_settings.js index b55946a8bd..f6f431ca5c 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js @@ -47,6 +47,8 @@ frappe.ui.form.on('Twitter Settings', { callback : function(r) { window.location.href = r.message; } + }).fail(function() { + frappe.dom.unfreeze(); }); } }, diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py index 64f53b5eb0..7616b4c027 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -12,13 +12,12 @@ from tweepy.error import TweepError class TwitterSettings(Document): def get_authorize_url(self): - callback_url = "{0}/?cmd=erpnext.crm.doctype.twitter_settings.twitter_settings.callback".format(frappe.utils.get_url()) + callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url()) auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url) - try: redirect_url = auth.get_authorization_url() return redirect_url - except: + except tweepy.TweepError as e: frappe.msgprint(_("Error! Failed to get request token.")) frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key"))) @@ -91,8 +90,12 @@ class TwitterSettings(Document): frappe.db.commit() frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason)) -@frappe.whitelist() -def callback(oauth_token, oauth_verifier): - twitter_settings = frappe.get_single("Twitter Settings") - twitter_settings.get_access_token(oauth_token,oauth_verifier) - frappe.db.commit() +@frappe.whitelist(allow_guest=True) +def callback(oauth_token = None, oauth_verifier = None): + if oauth_token and oauth_verifier: + twitter_settings = frappe.get_single("Twitter Settings") + twitter_settings.get_access_token(oauth_token,oauth_verifier) + frappe.db.commit() + else: + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings") From 3e0c51879e8a17a8be8ce622e9fbbce93eeab2b8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 6 May 2020 10:46:13 +0530 Subject: [PATCH 09/47] fix: Set Purchase Receipt and Delivery Note detail patch (#21607) * fix: Set Purchase Receipt and Delievry Note detail patch * fix: Reload purchase receipt item --- .../v12_0/set_purchase_receipt_delivery_note_detail.py | 4 ++++ 1 file changed, 4 insertions(+) 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 f5bd8c3aa2..6f843cdabd 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 @@ -3,6 +3,10 @@ import frappe from collections import defaultdict def execute(): + + frappe.reload_doc('stock', 'doctype', 'delivery_note_item', force=True) + frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item', force=True) + def map_rows(doc_row, return_doc_row, detail_field, doctype): """Map rows after identifying similar ones.""" From b2103f547073b8441cfbaec4861c4fab74a187d2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 15:18:43 +0530 Subject: [PATCH 10/47] feat: remove old fixtures --- .../account_balance_timeline.py | 2 +- erpnext/patches.txt | 1 - .../patches/v12_0/add_default_dashboards.py | 10 -- .../setup_wizard/data/dashboard_charts.py | 133 ------------------ .../operations/install_fixtures.py | 23 --- 5 files changed, 1 insertion(+), 168 deletions(-) delete mode 100644 erpnext/patches/v12_0/add_default_dashboards.py delete mode 100644 erpnext/setup/setup_wizard/data/dashboard_charts.py diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index c3e2f7db12..5decccb486 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -6,7 +6,7 @@ import frappe, json from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form from erpnext.accounts.report.general_ledger.general_ledger import execute -from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan +from frappe.utils.dashboard import cache_source, get_from_date_from_timespan from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from frappe.utils.nestedset import get_descendants_of diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ba17b67de1..15235f15a4 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -630,7 +630,6 @@ execute:frappe.reload_doc('desk', 'doctype', 'dashboard') execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_source') execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart') execute:frappe.reload_doc('desk', 'doctype', 'dashboard_chart_field') -erpnext.patches.v12_0.add_default_dashboards # 2020-04-05 erpnext.patches.v12_0.remove_bank_remittance_custom_fields erpnext.patches.v12_0.generate_leave_ledger_entries execute:frappe.delete_doc_if_exists("Report", "Loan Repayment") diff --git a/erpnext/patches/v12_0/add_default_dashboards.py b/erpnext/patches/v12_0/add_default_dashboards.py deleted file mode 100644 index 2a91e1b932..0000000000 --- a/erpnext/patches/v12_0/add_default_dashboards.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2019, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from erpnext.setup.setup_wizard.operations.install_fixtures import add_dashboards - -def execute(): - frappe.reload_doc("desk", "doctype", "number_card_link") - frappe.reload_doc("healthcare", "doctype", "patient_appointment") - add_dashboards() diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py deleted file mode 100644 index b182dfc103..0000000000 --- a/erpnext/setup/setup_wizard/data/dashboard_charts.py +++ /dev/null @@ -1,133 +0,0 @@ -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" } - ] - } - ], - "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", - "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/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 3be6f44832..8bb0a0529d 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -485,8 +485,6 @@ def install_defaults(args=None): # bank account same as a CoA entry pass - add_dashboards() - # Now, with fixtures out of the way, onto concrete stuff records = [ @@ -504,27 +502,6 @@ def install_defaults(args=None): make_records(records) -def add_dashboards(): - from erpnext.setup.setup_wizard.data.dashboard_charts import get_company_for_dashboards - - if not get_company_for_dashboards(): - return - - from erpnext.setup.setup_wizard.data.dashboard_charts import get_default_dashboards - from frappe.modules.import_file import import_file_by_path - - dashboard_data = get_default_dashboards() - - # create account balance timeline before creating dashbaord charts - doctype = "dashboard_chart_source" - docname = "account_balance_timeline" - folder = os.path.dirname(frappe.get_module("erpnext.accounts").__file__) - doc_path = os.path.join(folder, doctype, docname, docname) + ".json" - import_file_by_path(doc_path, force=0, for_sync=True) - - make_records(dashboard_data["Charts"]) - make_records(dashboard_data["Dashboards"]) - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year From 28436d2bc104a7b01dd9f3311ed9ff1a93aebc9a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 15:19:10 +0530 Subject: [PATCH 11/47] feat: added dashboard fixtures for accounts --- erpnext/accounts/dashboard_fixtures.py | 119 +++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 erpnext/accounts/dashboard_fixtures.py diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py new file mode 100644 index 0000000000..30eb5e3115 --- /dev/null +++ b/erpnext/accounts/dashboard_fixtures.py @@ -0,0 +1,119 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +import json + + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + }) + +def get_dashboards(): + return [{ + "name": "Accounts", + "dashboard_name": "Accounts", + "charts": [ + { "chart": "Outgoing Bills (Sales Invoice)" }, + { "chart": "Incoming Bills (Purchase Invoice)" }, + { "chart": "Bank Balance" }, + { "chart": "Income" }, + { "chart": "Expenses" } + ] + }] + +def get_charts(): + 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 [ + { + "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, + "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" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "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({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Purchase Invoice", + "type": "Bar" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "name": "Outgoing Bills (Sales Invoice)", + "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" + } + ] + + +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 \ No newline at end of file From 8cdcb965c562b470dd2f5fe6d781361b8e4bd09d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 15:19:22 +0530 Subject: [PATCH 12/47] feat: added dashboard fixtures for health care --- erpnext/healthcare/dashboard_fixtures.py | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 erpnext/healthcare/dashboard_fixtures.py diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py new file mode 100644 index 0000000000..856222762b --- /dev/null +++ b/erpnext/healthcare/dashboard_fixtures.py @@ -0,0 +1,40 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +import json + + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + }) + +def get_dashboards(): + return [{ + "name": "Healthcare", + "dashboard_name": "Healthcare", + "charts": [ + { "chart": "Patient Appointments" } + ] + }] + +def get_charts(): + return [ + { + "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" + } + ] From 2596c8bbe72f25d1f10ed51c422d802b1f29598c Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 6 May 2020 17:06:53 +0530 Subject: [PATCH 13/47] fix: upload attendance (#21620) --- erpnext/hr/doctype/upload_attendance/upload_attendance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index f75bb4155e..61faea1871 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -145,7 +145,7 @@ def import_attendances(rows): def remove_holidays(rows): rows = [ row for row in rows if row[4] != "Holiday"] - return + return rows from frappe.modules import scrub From d50502ca40beb2b6985ceb6f674976d1e0ed3585 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 6 May 2020 17:36:48 +0530 Subject: [PATCH 14/47] fix: fix get_employee_details query (#21623) --- .../report/monthly_attendance_sheet/monthly_attendance_sheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d98ed1b414..82ed27715f 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -215,7 +215,7 @@ def get_conditions(filters): def get_employee_details(group_by, company): emp_map = {} query = """select name, employee_name, designation, department, branch, company, - holiday_list from `tabEmployee` where company = '%s' """ % frappe.db.escape(company) + holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(company) if group_by: group_by = group_by.lower() From b821f22b7cb83c8002a2709e6e5c8c6df87cfc53 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 6 May 2020 18:12:23 +0530 Subject: [PATCH 15/47] fix(minor): Reverse GL fix --- erpnext/accounts/general_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index fb1a4f4dba..bfe35ab006 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -297,7 +297,8 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None, fields = ["*"], filters = { "voucher_type": voucher_type, - "voucher_no": voucher_no + "voucher_no": voucher_no, + "is_cancelled": 0 }) if gl_entries: From 41616d4dd8aa3a723046aa0e07fdaf037c46b363 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 20:21:05 +0530 Subject: [PATCH 16/47] feat: add name to fixture --- erpnext/healthcare/dashboard_fixtures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py index 856222762b..fc3d62f2d8 100644 --- a/erpnext/healthcare/dashboard_fixtures.py +++ b/erpnext/healthcare/dashboard_fixtures.py @@ -25,6 +25,7 @@ def get_charts(): { "doctype": "Dashboard Chart", "time_interval": "Daily", + "name": "Patient Appointments", "chart_name": "Patient Appointments", "timespan": "Last Month", "color": "#77ecca", From 9de26f664831bd0939012477c0cc5cb427c4d1f6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 7 May 2020 12:06:47 +0530 Subject: [PATCH 17/47] fix: list index out of range (#21613) --- erpnext/stock/doctype/batch/batch.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 9b7249e66b..a091ac7fae 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.model.naming import make_autoname, revert_series_if_last -from frappe.utils import flt, cint +from frappe.utils import flt, cint, get_link_to_form from frappe.utils.jinja import render_template from frappe.utils.data import add_days from six import string_types @@ -124,7 +124,7 @@ class Batch(Document): if has_expiry_date and not self.expiry_date: frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \ .format(frappe.bold("Shelf Life in Days"), - frappe.utils.get_link_to_form("Item", self.item), + get_link_to_form("Item", self.item), frappe.bold("Batch Expiry Date")), title=_("Expiry Date Mandatory")) @@ -264,16 +264,20 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None): def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos cond = '' - if serial_no: + if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'): + serial_nos = get_serial_nos(serial_no) batch = frappe.get_all("Serial No", fields = ["distinct batch_no"], filters= { "item_code": item_code, "warehouse": warehouse, - "name": ("in", get_serial_nos(serial_no)) + "name": ("in", serial_nos) } ) + if not batch: + validate_serial_no_with_batch(serial_nos, item_code) + if batch and len(batch) > 1: return [] @@ -288,4 +292,15 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None): and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0} group by batch_id order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC - """.format(cond), (item_code, warehouse), as_dict=True) \ No newline at end of file + """.format(cond), (item_code, warehouse), as_dict=True) + +def validate_serial_no_with_batch(serial_nos, item_code): + if frappe.get_cached_value("Serial No", serial_nos[0], "item_code") != item_code: + frappe.throw(_("The serial no {0} does not belong to item {1}") + .format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code))) + + serial_no_link = ','.join([get_link_to_form("Serial No", sn) for sn in serial_nos]) + + message = "Serial Nos" if len(serial_nos) > 1 else "Serial No" + frappe.throw(_("There is no batch found against the {0}: {1}") + .format(message, serial_no_link)) \ No newline at end of file From c759e1790023890aeb75f5efc3f11907175678b4 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Thu, 7 May 2020 12:12:13 +0530 Subject: [PATCH 18/47] fix(item): patch to rename duplicate item_code values to name (#21622) Co-authored-by: Mangesh-Khairnar --- erpnext/patches.txt | 1 + .../patches/v11_0/rename_duplicate_item_code_values.py | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 erpnext/patches/v11_0/rename_duplicate_item_code_values.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ba17b67de1..ce0e4ac471 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -495,6 +495,7 @@ erpnext.patches.v10_0.rename_offer_letter_to_job_offer execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True) erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group # 24-12-2018 erpnext.patches.v10_0.add_default_cash_flow_mappers +erpnext.patches.v11_0.rename_duplicate_item_code_values erpnext.patches.v11_0.make_quality_inspection_template erpnext.patches.v10_0.update_status_for_multiple_source_in_po erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry diff --git a/erpnext/patches/v11_0/rename_duplicate_item_code_values.py b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py new file mode 100644 index 0000000000..00ab562c35 --- /dev/null +++ b/erpnext/patches/v11_0/rename_duplicate_item_code_values.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + items = [] + items = frappe.db.sql("""select item_code from `tabItem` group by item_code having count(*) > 1""", as_dict=True) + if items: + for item in items: + frappe.db.sql("""update `tabItem` set item_code=name where item_code = %s""", (item.item_code)) From e16498590377487967ee122ac6fc5590fdf5d85b Mon Sep 17 00:00:00 2001 From: SDLyu Date: Thu, 7 May 2020 16:49:29 +0800 Subject: [PATCH 19/47] Create taiwan.html --- erpnext/regional/address_template/templates/taiwan.html | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 erpnext/regional/address_template/templates/taiwan.html diff --git a/erpnext/regional/address_template/templates/taiwan.html b/erpnext/regional/address_template/templates/taiwan.html new file mode 100644 index 0000000000..43051afc77 --- /dev/null +++ b/erpnext/regional/address_template/templates/taiwan.html @@ -0,0 +1,4 @@ +{{ country }}
{% if pincode %}{{ pincode }}
{% endif -%}{{ county }}{{ city }}{{ address_line1 }}
{% if address_line2 %}{{ address_line2 }}{% endif -%} +{% if phone %}Phone: {{ phone }}
{% endif -%} +{% if fax %}Fax: {{ fax }}
{% endif -%} +{% if email_id %}Email: {{ email_id }}
{% endif -%} From b2eb4ee0f98b4cce7f4f32edb6cdcd9b19c7c08a Mon Sep 17 00:00:00 2001 From: SDLyu Date: Thu, 7 May 2020 17:09:17 +0800 Subject: [PATCH 20/47] Change
position Change
position for better format --- erpnext/regional/address_template/templates/taiwan.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/address_template/templates/taiwan.html b/erpnext/regional/address_template/templates/taiwan.html index 43051afc77..1715bea48a 100644 --- a/erpnext/regional/address_template/templates/taiwan.html +++ b/erpnext/regional/address_template/templates/taiwan.html @@ -1,4 +1,4 @@ -{{ country }}
{% if pincode %}{{ pincode }}
{% endif -%}{{ county }}{{ city }}{{ address_line1 }}
{% if address_line2 %}{{ address_line2 }}{% endif -%} -{% if phone %}Phone: {{ phone }}
{% endif -%} -{% if fax %}Fax: {{ fax }}
{% endif -%} -{% if email_id %}Email: {{ email_id }}
{% endif -%} +{{ country }}
{% if pincode %}{{ pincode }}
{% endif -%}{{ county }}{{ city }}{{ address_line1 }}{% if address_line2 %}{{ address_line2 }}{% endif -%} +{% if phone %}
Phone: {{ phone }}{% endif -%} +{% if fax %}
Fax: {{ fax }}{% endif -%} +{% if email_id %}
Email: {{ email_id }}{% endif -%} From 52189cba861e0d88cf9847f53bd5c125f683f824 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 7 May 2020 17:49:32 +0800 Subject: [PATCH 21/47] style: Improve formatting This commit improves indentations and makes sql queries more readable. --- .../budget_variance_report.py | 418 ++++++++++++------ 1 file changed, 284 insertions(+), 134 deletions(-) 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 39e218bfad..f286a45757 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -2,187 +2,337 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +from six import iteritems + import frappe from frappe import _ from frappe.utils import flt from frappe.utils import formatdate + from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges -from six import iteritems -from pprint import pprint + def execute(filters=None): - if not filters: filters = {} + if not filters: + filters = {} - columns = get_columns(filters) - if filters.get("budget_against_filter"): - dimensions = filters.get("budget_against_filter") - else: - dimensions = get_cost_centers(filters) + columns = get_columns(filters) - period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) - cam_map = get_dimension_account_month_map(filters) + if filters.get("budget_against_filter"): + dimensions = filters.get("budget_against_filter") + else: + dimensions = get_cost_centers(filters) - data = [] - 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 + period_month_ranges = get_period_month_ranges( + filters["period"], filters["from_fiscal_year"] + ) - period_data[0] += last_total + cam_map = get_dimension_account_month_map(filters) - if(filters.get("show_cumulative")): - last_total = period_data[0] - period_data[1] + data = [] + 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 - 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) + period_data[0] += last_total + + 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 columns, data - return columns, data def get_columns(filters): - columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] + columns = [ + _(filters.get("budget_against")) + + ":Link/%s:150" % (filters.get("budget_against")), + _("Account") + ":Link/Account:150", + ] - group_months = False if filters["period"] == "Monthly" else True + group_months = False if filters["period"] == "Monthly" else True - fiscal_year = get_fiscal_years(filters) + fiscal_year = get_fiscal_years(filters) - for year in fiscal_year: - for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): - if filters["period"] == "Yearly": - labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])] - for label in labels: - columns.append(label+":Float:150") - else: - for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]: - if group_months: - label = label % (formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM")) - else: - label = label % formatdate(from_date, format_string="MMM") + for year in fiscal_year: + for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): + if filters["period"] == "Yearly": + labels = [ + _("Budget") + " " + str(year[0]), + _("Actual ") + " " + str(year[0]), + _("Variance ") + " " + str(year[0]), + ] + for label in labels: + columns.append(label + ":Float:150") + else: + for label in [ + _("Budget") + " (%s)" + " " + str(year[0]), + _("Actual") + " (%s)" + " " + str(year[0]), + _("Variance") + " (%s)" + " " + str(year[0]), + ]: + if group_months: + label = label % ( + formatdate(from_date, format_string="MMM") + + "-" + + formatdate(to_date, format_string="MMM") + ) + else: + label = label % formatdate(from_date, format_string="MMM") - columns.append(label+":Float:150") + columns.append(label + ":Float:150") + + if filters["period"] != "Yearly": + return columns + [ + _("Total Budget") + ":Float:150", + _("Total Actual") + ":Float:150", + _("Total Variance") + ":Float:150", + ] + else: + return columns - if filters["period"] != "Yearly" : - return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150", - _("Total Variance") + ":Float:150"] - else: - return columns def get_cost_centers(filters): - cond = "and 1=1" - if filters.get("budget_against") == "Cost Center": - cond = "order by lft" + order_by = "" + if filters.get("budget_against") == "Cost Center": + order_by = "order by lft" - if filters.get("budget_against") in ["Cost Center", "Project"]: - return frappe.db.sql_list("""select name from `tab{tab}` where company=%s - {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) - else: - return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec + if filters.get("budget_against") in ["Cost Center", "Project"]: + return frappe.db.sql_list( + """ + select + name + from + `tab{tab}` + where + company = %s + {order_by}; + """.format( + tab=filters.get("budget_against"), order_by=order_by + ), + filters.get("company"), + ) + else: + return frappe.db.sql_list( + """ + select + name + from + `tab{tab}` + """.format( + tab=filters.get("budget_against") + ) + ) # nosec -#Get dimension & target details + +# Get dimension & target details def get_dimension_target_details(filters): - cond = "" - if filters.get("budget_against_filter"): - cond += " and b.{budget_against} in (%s)".format(budget_against = \ - frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter'))) + budget_against = frappe.scrub(filters.get("budget_against")) - return frappe.db.sql(""" - select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year - from `tabBudget` b, `tabBudget Account` ba - where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s - and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year - """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), - tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')), - as_dict=True) + cond = "" + if filters.get("budget_against_filter"): + cond += """ + and + b.{budget_against} in ( + %s + ) + """.format( + budget_against=budget_against + ) % ", ".join( + ["%s"] * len(filters.get("budget_against_filter")) + ) + + return frappe.db.sql( + """ + select + b.{budget_against} as name + from + `tabBudget` b + where + b.docstatus = 1 + and b.fiscal_year between %s and %s + and b.budget_against = %s + and b.company = %s + {cond} + order by + b.fiscal_year + """.format( + budget_against=budget_against, cond=cond + ), + tuple( + [ + filters.from_fiscal_year, + filters.to_fiscal_year, + filters.budget_against, + filters.company, + ] + + filters.get("budget_against_filter") + ), + as_dict=True, + ) -#Get target distribution details of accounts of cost center +# Get target distribution details of accounts of cost center def get_target_distribution_details(filters): - target_details = {} - for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation - from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md - where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1): - target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation)) + target_details = {} + for d in frappe.db.sql( + """ + select + md.name, + mdp.month, + mdp.percentage_allocation + from + `tabMonthly Distribution Percentage` mdp, + `tabMonthly Distribution` md + where + mdp.parent = md.name + and md.fiscal_year between %s + and %s + order by md.fiscal_year + """, + (filters.from_fiscal_year, filters.to_fiscal_year), + as_dict=1, + ): + target_details.setdefault(d.name, {}).setdefault( + d.month, flt(d.percentage_allocation) + ) - return target_details + return target_details -#Get actual details from gl entry + +# Get actual details from gl entry def get_actual_details(name, filters): - cond = "1=1" - budget_against=filters.get("budget_against").replace(" ", "_").lower() + budget_against = frappe.scrub(filters.get("budget_against")) - if filters.get("budget_against") == "Cost Center": - cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) - cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt) + cond = "" + if filters.get("budget_against") == "Cost Center": + cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) + cond = """ + and lft >= '{lft}' + and rgt <= '{rgt}' + """.format( + lft=cc_lft, rgt=cc_rgt + ) - ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year, - MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against - from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b - where - b.name = ba.parent - and b.docstatus = 1 - and ba.account=gl.account - and b.{budget_against} = gl.{budget_against} - and gl.fiscal_year between %s and %s - and b.{budget_against}=%s - and exists(select name from `tab{tab}` where name=gl.{budget_against} and {cond}) group by gl.name order by gl.fiscal_year - """.format(tab = filters.budget_against, budget_against = budget_against, cond = cond,from_year=filters.from_fiscal_year,to_year=filters.to_fiscal_year), - (filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1) + ac_details = frappe.db.sql( + """ + select + gl.account, + gl.debit, + gl.credit, + gl.fiscal_year, + MONTHNAME(gl.posting_date) as month_name, + b.{budget_against} as budget_against + from + `tabGL Entry` gl, + `tabBudget Account` ba, + `tabBudget` b + where + b.name = ba.parent + and b.docstatus = 1 + and ba.account=gl.account + and b.{budget_against} = gl.{budget_against} + and gl.fiscal_year between %s and %s + and b.{budget_against} = %s + and exists( + select + name + from + `tab{tab}` + where + name = gl.{budget_against} + {cond} + ) + group by + gl.name + order by gl.fiscal_year + """.format( + tab=filters.budget_against, budget_against=budget_against, cond=cond + ), + (filters.from_fiscal_year, filters.to_fiscal_year, name), + as_dict=1, + ) - cc_actual_details = {} - for d in ac_details: - cc_actual_details.setdefault(d.account, []).append(d) + cc_actual_details = {} + for d in ac_details: + cc_actual_details.setdefault(d.account, []).append(d) + + return cc_actual_details - return cc_actual_details def get_dimension_account_month_map(filters): - import datetime - dimension_target_details = get_dimension_target_details(filters) - tdd = get_target_distribution_details(filters) + import datetime - cam_map = {} + dimension_target_details = get_dimension_target_details(filters) + tdd = get_target_distribution_details(filters) - for ccd in dimension_target_details: - actual_details = get_actual_details(ccd.budget_against, filters) + cam_map = {} - for month_id in range(1, 13): - month = datetime.date(2013, month_id, 1).strftime('%B') - cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\ - .setdefault(month, frappe._dict({ - "target": 0.0, "actual": 0.0 - })) + for ccd in dimension_target_details: + actual_details = get_actual_details(ccd.budget_against, filters) - tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month] - month_percentage = tdd.get(ccd.monthly_distribution, {}).get(month, 0) \ - if ccd.monthly_distribution else 100.0/12 + for month_id in range(1, 13): + month = datetime.date(2013, month_id, 1).strftime("%B") + cam_map.setdefault(ccd.budget_against, {}).setdefault( + ccd.account, {} + ).setdefault(ccd.fiscal_year, {}).setdefault( + month, frappe._dict({"target": 0.0, "actual": 0.0}) + ) - tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 + tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month] + month_percentage = ( + tdd.get(ccd.monthly_distribution, {}).get(month, 0) + if ccd.monthly_distribution + else 100.0 / 12 + ) - for ad in actual_details.get(ccd.account, []): - if ad.month_name == month: - tav_dict.actual += flt(ad.debit) - flt(ad.credit) + tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 + + for ad in actual_details.get(ccd.account, []): + if ad.month_name == month: + tav_dict.actual += flt(ad.debit) - flt(ad.credit) + + return cam_map - return cam_map def get_fiscal_years(filters): - fiscal_year = frappe.db.sql("""select name from `tabFiscal Year` where - name between %(from_fiscal_year)s and %(to_fiscal_year)s""", - {'from_fiscal_year': filters["from_fiscal_year"], 'to_fiscal_year': filters["to_fiscal_year"]}) + fiscal_year = frappe.db.sql( + """ + select + name + from + `tabFiscal Year` + where + name between %(from_fiscal_year)s and %(to_fiscal_year)s + order by + year + """, + { + "from_fiscal_year": filters["from_fiscal_year"], + "to_fiscal_year": filters["to_fiscal_year"], + }, + ) - return fiscal_year + return fiscal_year From dd340c15de2daec6e21dcc5c35db362bee1d419c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 7 May 2020 15:35:54 +0530 Subject: [PATCH 22/47] fix: Loan Security Unpledge fixes --- erpnext/loan_management/doctype/loan/loan.py | 3 +- .../loan_management/doctype/loan/test_loan.py | 12 +- .../doctype/loan_repayment/loan_repayment.py | 9 +- .../loan_security_shortfall.py | 2 +- .../loan_security_unpledge.js | 8 +- .../loan_security_unpledge.py | 138 +++++++++--------- .../doctype/unpledge/unpledge.json | 13 +- 7 files changed, 92 insertions(+), 93 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index c550d4952d..76e10e5ddd 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -248,8 +248,7 @@ def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_d for loan_security in loan_security_pledge_details: unpledge_request.append('securities', { "loan_security": loan_security.loan_security, - "qty": loan_security.qty, - "against_pledge": loan_security.parent + "qty": loan_security.qty }) if as_dict: diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 77a1fcc574..364e2ffecf 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -15,6 +15,7 @@ from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_ from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import create_process_loan_security_shortfall from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge +from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty class TestLoan(unittest.TestCase): def setUp(self): @@ -152,7 +153,7 @@ class TestLoan(unittest.TestCase): repayment_entry.save() repayment_entry.submit() - penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year)) + penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', @@ -305,7 +306,7 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) process_loan_interest_accrual_for_demand_loans(posting_date = last_date) - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), + repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 6), "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) repayment_entry.submit() @@ -319,13 +320,12 @@ class TestLoan(unittest.TestCase): unpledge_request.submit() unpledge_request.status = 'Approved' unpledge_request.save() - - loan_security_pledge.load_from_db() loan.load_from_db() + pledged_qty = get_pledged_security_qty(loan.name) + self.assertEqual(loan.status, 'Closed') - for security in loan_security_pledge.securities: - self.assertEquals(security.qty, 0) + self.assertEquals(sum(pledged_qty.values()), 0) def create_loan_accounts(): diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 452c836819..2ab668a0e1 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -264,6 +264,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): penalty_amount = 0 payable_principal_amount = 0 final_due_date = '' + due_date = '' for entry in accrued_interest_entries: # Loan repayment due date is one day after the loan interest is accrued @@ -272,7 +273,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): due_date = add_days(entry.posting_date, 1) no_of_late_days = date_diff(posting_date, - add_days(due_date, loan_type_details.grace_period_in_days)) + 1 + add_days(due_date, loan_type_details.grace_period_in_days)) if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary): penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365 @@ -290,9 +291,9 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable - if payment_type == "Loan Closure" and not payable_principal_amount: - if final_due_date: - pending_days = date_diff(posting_date, final_due_date) + if payment_type == "Loan Closure": + if due_date: + pending_days = date_diff(posting_date, due_date) + 1 else: pending_days = date_diff(posting_date, against_loan_doc.disbursement_date) + 1 diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 8ca6e3e908..308c4385d3 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -69,7 +69,7 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) for loan, value in iteritems(loan_security_map): - if (value["security_value"]/value["loan_amount"]) < ltv_ratio: + if (value["loan_amount"]/value['security_value'] * 100) > ltv_ratio: create_loan_security_shortfall(loan, value, process_loan_security_shortfall) def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js index 72c5f38cf3..8223206277 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js @@ -4,10 +4,8 @@ frappe.ui.form.on('Loan Security Unpledge', { refresh: function(frm) { - frm.set_query("against_pledge", "securities", () => { - return { - filters : [["status", "in", ["Pledged", "Partially Pledged"]]] - }; - }); + if (frm.doc.docstatus == 1 && frm.doc.status == 'Approved') { + frm.set_df_property('status', 'read_only', 1); + } } }); diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py index b2bb22a3ce..5e9d82aa91 100644 --- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py +++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import get_datetime, flt import json +from six import iteritems from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price class LoanSecurityUnpledge(Document): def validate(self): - self.validate_pledges() self.validate_duplicate_securities() + self.validate_unpledge_qty() def on_cancel(self): self.update_loan_security_pledge(cancel=1) @@ -23,80 +24,52 @@ class LoanSecurityUnpledge(Document): def validate_duplicate_securities(self): security_list = [] for d in self.securities: - security = [d.loan_security, d.against_pledge] - if security not in security_list: - security_list.append(security) + if d.loan_security not in security_list: + security_list.append(d.loan_security) else: - frappe.throw(_("Row {0}: Loan Security {1} against Loan Security Pledge {2} added multiple times").format( - d.idx, frappe.bold(d.loan_security), frappe.bold(d.against_pledge))) + frappe.throw(_("Row {0}: Loan Security {1} added multiple times").format( + d.idx, frappe.bold(d.loan_security))) - def validate_pledges(self): - pledge_qty_map = self.get_pledge_details() - loan = frappe.get_doc("Loan", self.loan) + def validate_unpledge_qty(self): + pledge_qty_map = get_pledged_security_qty(self.loan) - remaining_qty = 0 - unpledge_value = 0 + ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", + fields=["name", "loan_to_value_ratio"], as_list=1)) + + loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price", + fields=["loan_security", "loan_security_price"], + filters = { + "valid_from": ("<=", get_datetime()), + "valid_upto": (">=", get_datetime()) + }, as_list=1)) + + loan_amount, principal_paid = frappe.get_value("Loan", self.loan, ['loan_amount', 'total_principal_paid']) + pending_principal_amount = loan_amount - principal_paid + security_value = 0 for security in self.securities: - pledged_qty = pledge_qty_map.get((security.against_pledge, security.loan_security), 0) - if not pledged_qty: - frappe.throw(_("Zero qty of {0} pledged against loan {1}").format(frappe.bold(security.loan_security), - frappe.bold(self.loan))) + pledged_qty = pledge_qty_map.get(security.loan_security) - unpledge_qty = pledged_qty - security.qty - security_price = security.qty * get_loan_security_price(security.loan_security) + if security.qty > pledged_qty: + frappe.throw(_("""Row {0}: {1} {2} of {3} is pledged against Loan {4}. + You are trying to unpledge more""").format(security.idx, pledged_qty, security.uom, + frappe.bold(security.loan_security), frappe.bold(self.loan))) - if unpledge_qty < 0: - frappe.throw(_("""Row {0}: Cannot unpledge more than {1} qty of {2} against - Loan Security Pledge {3}""").format(security.idx, frappe.bold(pledged_qty), - frappe.bold(security.loan_security), frappe.bold(security.against_pledge))) + qty_after_unpledge = pledged_qty - security.qty + ltv_ratio = ltv_ratio_map.get(security.loan_security_type) - remaining_qty += unpledge_qty - unpledge_value += security_price - flt(security_price * security.haircut/100) + security_value += qty_after_unpledge * loan_security_price_map.get(security.loan_security) - if unpledge_value > loan.total_principal_paid: - frappe.throw(_("Cannot Unpledge, loan security value is greater than the repaid amount")) + if not security_value and pending_principal_amount > 0: + frappe.throw("Cannot Unpledge, loan to value ratio is breaching") - def get_pledge_details(self): - pledge_qty_map = {} - - pledge_details = frappe.db.sql(""" - SELECT p.parent, p.loan_security, p.qty FROM - `tabLoan Security Pledge` lsp, - `tabPledge` p - WHERE - p.parent = lsp.name - AND lsp.loan = %s - AND lsp.docstatus = 1 - AND lsp.status in ('Pledged', 'Partially Pledged') - """, (self.loan), as_dict=1) - - for pledge in pledge_details: - pledge_qty_map.setdefault((pledge.parent, pledge.loan_security), pledge.qty) - - return pledge_qty_map + if security_value and (pending_principal_amount/security_value) * 100 > ltv_ratio: + frappe.throw("Cannot Unpledge, loan to value ratio is breaching") def on_update_after_submit(self): if self.status == "Approved": - self.update_loan_security_pledge() self.update_loan_status() - - def update_loan_security_pledge(self, cancel=0): - if cancel: - new_qty = 'p.qty + u.qty' - else: - new_qty = 'p.qty - u.qty' - - frappe.db.sql(""" - UPDATE - `tabPledge` p, `tabUnpledge` u, `tabLoan Security Pledge` lsp, `tabLoan Security Unpledge` lsu - SET p.qty = {new_qty} - WHERE - lsp.loan = %s - AND p.parent = u.against_pledge - AND p.parent = lsp.name - AND lsp.docstatus = 1 - AND p.loan_security = u.loan_security""".format(new_qty=new_qty),(self.loan)) + self.db_set('unpledge_time', get_datetime()) def update_loan_status(self, cancel=0): if cancel: @@ -104,10 +77,45 @@ class LoanSecurityUnpledge(Document): if loan_status == 'Closed': frappe.db.set_value('Loan', self.loan, 'status', 'Loan Closure Requested') else: - pledge_qty = frappe.db.sql("""SELECT SUM(c.qty) - FROM `tabLoan Security Pledge` p, `tabPledge` c - WHERE p.loan = %s AND c.parent = p.name""", (self.loan))[0][0] + pledged_qty = 0 + current_pledges = get_pledged_security_qty(self.loan) - if not pledge_qty: + for security, qty in iteritems(current_pledges): + pledged_qty += qty + + if not pledged_qty: frappe.db.set_value('Loan', self.loan, 'status', 'Closed') +@frappe.whitelist() +def get_pledged_security_qty(loan): + + current_pledges = {} + + unpledges = frappe._dict(frappe.db.sql(""" + SELECT u.loan_security, sum(u.qty) as qty + FROM `tabLoan Security Unpledge` up, `tabUnpledge` u + WHERE up.loan = %s + AND u.parent = up.name + AND up.status = 'Approved' + GROUP BY u.loan_security + """, (loan))) + + pledges = frappe._dict(frappe.db.sql(""" + SELECT p.loan_security, sum(p.qty) as qty + FROM `tabLoan Security Pledge` lp, `tabPledge`p + WHERE lp.loan = %s + AND p.parent = lp.name + AND lp.status = 'Pledged' + GROUP BY p.loan_security + """, (loan))) + + for security, qty in iteritems(pledges): + current_pledges.setdefault(security, qty) + current_pledges[security] -= unpledges.get(security, 0.0) + + return current_pledges + + + + + diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.json b/erpnext/loan_management/doctype/unpledge/unpledge.json index 9e6277d5f8..ee192d7377 100644 --- a/erpnext/loan_management/doctype/unpledge/unpledge.json +++ b/erpnext/loan_management/doctype/unpledge/unpledge.json @@ -1,11 +1,11 @@ { + "actions": [], "creation": "2019-09-21 13:22:19.793797", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "loan_security", - "against_pledge", "loan_security_type", "loan_security_code", "haircut", @@ -54,14 +54,6 @@ "label": "Quantity", "reqd": 1 }, - { - "fieldname": "against_pledge", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Against Pledge", - "options": "Loan Security Pledge", - "reqd": 1 - }, { "fetch_from": "loan_security.haircut", "fieldname": "haircut", @@ -71,7 +63,8 @@ } ], "istable": 1, - "modified": "2019-10-02 12:48:18.588236", + "links": [], + "modified": "2020-05-06 10:50:18.448552", "modified_by": "Administrator", "module": "Loan Management", "name": "Unpledge", From fc0e45d79c2d07c2313264d019489fa901f6c6dc Mon Sep 17 00:00:00 2001 From: Myuddin khatri Date: Thu, 7 May 2020 15:11:39 +0530 Subject: [PATCH 23/47] solved merge conflicts --- erpnext/crm/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py index 38bf79e5fc..95b19ec21e 100644 --- a/erpnext/crm/utils.py +++ b/erpnext/crm/utils.py @@ -19,6 +19,5 @@ def update_lead_phone_numbers(contact, method): mobile_no = primary_mobile_nos[0] lead = frappe.get_doc("Lead", contact_lead) - lead.phone = phone - lead.mobile_no = mobile_no - lead.save() + lead.db_set("phone", phone) + lead.db_set("mobile_no", mobile_no) From b3ee6314f9ef7937b96bf9c346c13161c9ca5226 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 7 May 2020 18:39:47 +0800 Subject: [PATCH 24/47] fix: Fix Budget Variance Report This commit fixes a bug in Budget Variance Report where it combines the actual expense amounts across different fiscal years. This was fixed by updating the function and queries for computing the actual expense amounts. --- .../budget_variance_report.py | 172 +++++++++++++----- 1 file changed, 127 insertions(+), 45 deletions(-) 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 f286a45757..1b110b0aac 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -2,12 +2,12 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import datetime from six import iteritems import frappe from frappe import _ -from frappe.utils import flt -from frappe.utils import formatdate +from frappe.utils import flt, formatdate from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges @@ -126,8 +126,8 @@ def get_cost_centers(filters): from `tab{tab}` where - company = %s - {order_by}; + company=%s + {order_by} """.format( tab=filters.get("budget_against"), order_by=order_by ), @@ -153,11 +153,11 @@ def get_dimension_target_details(filters): cond = "" if filters.get("budget_against_filter"): cond += """ - and - b.{budget_against} in ( - %s - ) - """.format( + and + b.{budget_against} in ( + %s + ) + """.format( budget_against=budget_against ) % ", ".join( ["%s"] * len(filters.get("budget_against_filter")) @@ -165,7 +165,7 @@ def get_dimension_target_details(filters): return frappe.db.sql( """ - select + select distinct b.{budget_against} as name from `tabBudget` b @@ -198,19 +198,19 @@ def get_target_distribution_details(filters): target_details = {} for d in frappe.db.sql( """ - select - md.name, - mdp.month, - mdp.percentage_allocation - from - `tabMonthly Distribution Percentage` mdp, - `tabMonthly Distribution` md - where - mdp.parent = md.name - and md.fiscal_year between %s - and %s - order by md.fiscal_year - """, + select + md.name, + mdp.month, + mdp.percentage_allocation + from + `tabMonthly Distribution Percentage` mdp, + `tabMonthly Distribution` md + where + mdp.parent = md.name + and md.fiscal_year between %s + and %s + order by md.fiscal_year + """, (filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1, ): @@ -229,8 +229,8 @@ def get_actual_details(name, filters): if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = """ - and lft >= '{lft}' - and rgt <= '{rgt}' + and lft >= \'{lft}\' + and rgt <= \'{rgt}\' """.format( lft=cc_lft, rgt=cc_rgt ) @@ -282,37 +282,44 @@ def get_actual_details(name, filters): def get_dimension_account_month_map(filters): - import datetime - dimension_target_details = get_dimension_target_details(filters) tdd = get_target_distribution_details(filters) cam_map = {} for ccd in dimension_target_details: - actual_details = get_actual_details(ccd.budget_against, filters) + accounts = get_accounts(ccd.name, filters) + actual_details = get_actual_details(ccd.name, filters) - for month_id in range(1, 13): - month = datetime.date(2013, month_id, 1).strftime("%B") - cam_map.setdefault(ccd.budget_against, {}).setdefault( - ccd.account, {} - ).setdefault(ccd.fiscal_year, {}).setdefault( - month, frappe._dict({"target": 0.0, "actual": 0.0}) - ) + for year in get_fiscal_years(filters): + year = year[0] + monthly_distribution = get_monthly_distribution(ccd.name, year, filters) + for month_id in range(1, 13): + month = datetime.date(2013, month_id, 1).strftime( + "%B" + ) # Get month string - tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month] - month_percentage = ( - tdd.get(ccd.monthly_distribution, {}).get(month, 0) - if ccd.monthly_distribution - else 100.0 / 12 - ) + for account in accounts: + account = account[0] + cam_map.setdefault(ccd.name, {}).setdefault(account, {}).setdefault( + year, {} + ).setdefault(month, frappe._dict({"target": 0.0, "actual": 0.0})) - tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 + tav_dict = cam_map[ccd.name][account][year][month] - for ad in actual_details.get(ccd.account, []): - if ad.month_name == month: - tav_dict.actual += flt(ad.debit) - flt(ad.credit) + month_percentage = ( + tdd.get(monthly_distribution, {}).get(month, 0) + if monthly_distribution + else 100.0 / 12 + ) + budget_amount = get_budget_amount(ccd.name, year, account, filters) + + tav_dict.target = flt(budget_amount) * month_percentage / 100 + + for ad in actual_details.get(account, []): + if ad.month_name == month and ad.fiscal_year == year: + tav_dict.actual += flt(ad.debit) - flt(ad.credit) return cam_map @@ -336,3 +343,78 @@ def get_fiscal_years(filters): ) return fiscal_year + + +def get_accounts(name, filters): + budget_against = frappe.scrub(filters.get("budget_against")) + + accounts = frappe.db.sql( + """ + select + distinct(ba.account) + from + `tabBudget Account` ba + join + `tabBudget` b + on b.name = ba.parent + where + b.docstatus = 1 + and b.fiscal_year between %s and %s + and b.{budget_against} = %s + order by + ba.account + """.format( + budget_against=budget_against + ), + (filters.from_fiscal_year, filters.to_fiscal_year, name), + ) + + return accounts + + +def get_monthly_distribution(name, year, filters): + budget_against = frappe.scrub(filters.get("budget_against")) + + monthly_distribution = frappe.db.sql( + """ + select + monthly_distribution + from + `tabBudget` + where + docstatus = 1 + and {budget_against} = %s + and fiscal_year = %s + """.format( + budget_against=budget_against + ), + (name, year), + ) + + return monthly_distribution[0][0] if monthly_distribution else None + + +def get_budget_amount(name, year, account, filters): + budget_against = frappe.scrub(filters.get("budget_against")) + + budget_amount = frappe.db.sql( + """ + select + ba.budget_amount + from + `tabBudget Account` ba + join + `tabBudget` b + on b.name = ba.parent + where + b.docstatus = 1 + and b.{budget_against} = %s + and b.fiscal_year = %s + and ba.account = %s + """.format( + budget_against=budget_against + ), + (name, year, account), + ) + + return budget_amount[0][0] if budget_amount else 0 From 2ba89ffd488b835bd288102c1fceed09f4bed83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=BCrker=20Tunal=C4=B1?= Date: Thu, 7 May 2020 12:38:37 +0300 Subject: [PATCH 25/47] fix: Job Card submitted qty Update Operation Status function in work order was throwing exception without checking the "Overproduction Percentage For Work Order" setting. To submit Job Card qty for more than the Work Order's "To Manufacture Qty" we need to apply this fix. (cherry picked from commit 9bd6d119c405f2ace6c93d53647af140c6543542) --- erpnext/manufacturing/doctype/work_order/work_order.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 84bfab2f1d..8301f30d83 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -421,6 +421,9 @@ class WorkOrder(Document): return holidays[holiday_list] def update_operation_status(self): + allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")) + max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage/100 * flt(self.qty)) + for d in self.get("operations"): if not d.completed_qty: d.status = "Pending" @@ -428,6 +431,8 @@ class WorkOrder(Document): d.status = "Work in Progress" elif flt(d.completed_qty) == flt(self.qty): d.status = "Completed" + elif flt(d.completed_qty) <= max_allowed_qty_for_wo: + d.status = "Completed" else: frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) From b1385f676a60fe13201399e7051d4c7b6cfcf878 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Thu, 7 May 2020 16:47:41 +0530 Subject: [PATCH 26/47] fix(lead): strip lead_name before splitting --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 74b358219d..ec7d14d6ae 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -153,7 +153,7 @@ class Lead(SellingController): if not self.lead_name: self.set_lead_name() - names = self.lead_name.split(" ") + names = self.lead_name.strip().split(" ") if len(names) > 1: first_name, last_name = names[0], " ".join(names[1:]) else: From ebf3b78b420acc40156bec117f080952b8137366 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 7 May 2020 19:20:16 +0530 Subject: [PATCH 27/47] renaming LMS to Learning Management System --- .../education_settings/education_settings.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/education/doctype/education_settings/education_settings.json b/erpnext/education/doctype/education_settings/education_settings.json index 967a030fd2..0e548dbf77 100644 --- a/erpnext/education/doctype/education_settings/education_settings.json +++ b/erpnext/education/doctype/education_settings/education_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2017-04-05 13:33:04.519313", "doctype": "DocType", "editable_grid": 1, @@ -42,12 +43,14 @@ "fieldtype": "Column Break" }, { + "default": "0", "description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.", "fieldname": "validate_batch", "fieldtype": "Check", "label": "Validate Batch for Students in Student Group" }, { + "default": "0", "description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.", "fieldname": "validate_course", "fieldtype": "Check", @@ -74,13 +77,13 @@ { "fieldname": "web_academy_settings_section", "fieldtype": "Section Break", - "label": "LMS Settings" + "label": "Learning Management System Settings" }, { "depends_on": "eval: doc.enable_lms", "fieldname": "portal_title", "fieldtype": "Data", - "label": "LMS Title" + "label": "Learning Management System Title" }, { "depends_on": "eval: doc.enable_lms", @@ -89,9 +92,10 @@ "label": "Description" }, { + "default": "0", "fieldname": "enable_lms", "fieldtype": "Check", - "label": "Enable LMS" + "label": "Enable Learning Management System" }, { "default": "0", @@ -102,7 +106,8 @@ } ], "issingle": 1, - "modified": "2019-05-13 18:36:13.127563", + "links": [], + "modified": "2020-05-07 19:18:10.639356", "modified_by": "Administrator", "module": "Education", "name": "Education Settings", @@ -141,4 +146,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From 383a94d7302d0f8da179f0cdc0bf2c0b76c3bd44 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 7 May 2020 23:32:53 +0530 Subject: [PATCH 28/47] fix: delete old appointment analytics tree grid report --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ce0e4ac471..525593327e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -680,3 +680,4 @@ 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.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive +execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") From 8b526977234881cb3315af16b5f7ca5fc864fb31 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Fri, 8 May 2020 02:23:28 +0530 Subject: [PATCH 29/47] Payment Order not allowing to create Payment Entry --- erpnext/accounts/doctype/payment_order/payment_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.py b/erpnext/accounts/doctype/payment_order/payment_order.py index 3f3174a69b..7ecdc41d03 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.py +++ b/erpnext/accounts/doctype/payment_order/payment_order.py @@ -80,7 +80,7 @@ def make_journal_entry(doc, supplier, mode_of_payment=None): paid_amt += d.amount je.append('accounts', { - 'account': doc.references[0].account, + 'account': doc.account, 'credit_in_account_currency': paid_amt }) From 5b1a0fe00ebb9af0c91894dfee5622b04a29e7d7 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Fri, 8 May 2020 13:06:50 +0530 Subject: [PATCH 30/47] fix: bump erpnext develop to version 13-dev (#21651) --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 786b9cfd16..38d8a62f07 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.0.0-dev' +__version__ = '13.0.0-dev' def get_default_company(user=None): '''Get default company for user''' From 7d4ccdf24006f70bef35b3a4882773df47d989d2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 May 2020 13:27:25 +0530 Subject: [PATCH 31/47] fix: wrong fieldname branch_code in add_fetch --- .../doctype/payment_request/payment_request.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json index 97ae5ffde3..7508683c08 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.json +++ b/erpnext/accounts/doctype/payment_request/payment_request.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2015-12-15 22:23:24.745065", "doctype": "DocType", @@ -210,13 +211,14 @@ "label": "IBAN" }, { - "fetch_from": "bank_account.branch_code", + "fetch_from": "bank.branch_code", + "fetch_if_empty": 1, "fieldname": "branch_code", "fieldtype": "Read Only", "label": "Branch Code" }, { - "fetch_from": "bank_account.swift_number", + "fetch_from": "bank.swift_number", "fieldname": "swift_number", "fieldtype": "Read Only", "label": "SWIFT Number" @@ -348,7 +350,8 @@ } ], "is_submittable": 1, - "modified": "2020-03-28 16:07:31.960798", + "links": [], + "modified": "2020-05-08 10:23:02.815237", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Request", From 074ea2f882640fce6848e3c69754d6584002489b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 May 2020 15:33:35 +0530 Subject: [PATCH 32/47] fix: local variable 'lft' referenced before assignment --- erpnext/stock/doctype/warehouse/warehouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index eb4867d2cf..cd86be3115 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -177,7 +177,7 @@ def convert_to_group_or_ledger(): return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger() def get_child_warehouses(warehouse): - lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt]) + lft, rgt = frappe.get_cached_value("Warehouse", warehouse, ["lft", "rgt"]) return frappe.db.sql_list("""select name from `tabWarehouse` where lft >= %s and rgt <= %s""", (lft, rgt)) From 65123d2adcff62ff01f99b043f278b1bfdbf3f89 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 May 2020 16:30:46 +0530 Subject: [PATCH 33/47] fix: Item Barcode stays the same after updation. --- erpnext/stock/doctype/item/item.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c62b3ab583..4cc50bba9e 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -572,6 +572,13 @@ class Item(WebsiteGenerator): frappe.throw(_("Barcode {0} is not a valid {1} code").format( item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode) + if item_barcode.barcode != item_barcode.name: + # if barcode is getting updated , the row name has to reset. + # Delete previous old row doc and re-enter row as if new to reset name in db. + item_barcode.set("__islocal", True) + item_barcode.name = None + frappe.delete_doc("Item Barcode", item_barcode.name) + def validate_warehouse_for_reorder(self): '''Validate Reorder level table for duplicate and conditional mandatory''' warehouse = [] From 8d9f86f78113de8dce8b4b8b193c7ab23430a143 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 8 May 2020 19:45:57 +0530 Subject: [PATCH 34/47] fix: Gross and net profit report fix --- .../gross_and_net_profit_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 1c458107d4..fb07472b22 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -9,8 +9,8 @@ from erpnext.accounts.report.financial_statements import (get_period_list, get_c import copy def execute(filters=None): - period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, - filters.periodicity, filters.accumulated_values, filters.company) + period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, '', '', + 'Fiscal Year', filters.periodicity, filters.accumulated_values, filters.company) columns, data = [], [] From 3392e6b80c9c8105e4ebca00d0c6e164f12baa84 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 8 May 2020 19:53:09 +0530 Subject: [PATCH 35/47] fix: User filters instead of hardcoded values --- .../gross_and_net_profit_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index fb07472b22..714e48d279 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -9,8 +9,8 @@ from erpnext.accounts.report.financial_statements import (get_period_list, get_c import copy def execute(filters=None): - period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, '', '', - 'Fiscal Year', filters.periodicity, filters.accumulated_values, filters.company) + period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.period_start_date, + filters.period_end_date, filters.filter_based_on, filters.periodicity, filters.accumulated_values, filters.company) columns, data = [], [] From bc058558052a9d3af7f151347f7af018aa403002 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Sat, 9 May 2020 16:02:11 +0800 Subject: [PATCH 36/47] fix: Simplify get_dimension_account_month_map This commit updates get_dimension_account_month_map to no longer show the actual expense when there is no budget. This also removes the other functions and queries related to it. Spaces are also converted to tabs. --- .../budget_variance_report.py | 500 ++++++++---------- 1 file changed, 209 insertions(+), 291 deletions(-) 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 1b110b0aac..4d5892a913 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -13,164 +13,165 @@ from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ def execute(filters=None): - if not filters: - filters = {} + if not filters: + filters = {} - columns = get_columns(filters) + columns = get_columns(filters) + if filters.get("budget_against_filter"): + dimensions = filters.get("budget_against_filter") + else: + dimensions = get_cost_centers(filters) - if filters.get("budget_against_filter"): - dimensions = filters.get("budget_against_filter") - else: - dimensions = get_cost_centers(filters) + period_month_ranges = get_period_month_ranges( + filters["period"], filters["from_fiscal_year"] + ) + cam_map = get_dimension_account_month_map(filters) - period_month_ranges = get_period_month_ranges( - filters["period"], filters["from_fiscal_year"] - ) + data = [] + 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 - cam_map = get_dimension_account_month_map(filters) + period_data[0] += last_total - data = [] - 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 + if filters.get("show_cumulative"): + last_total = period_data[0] - period_data[1] - period_data[0] += last_total + 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) - 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 columns, data + return columns, data def get_columns(filters): - columns = [ - _(filters.get("budget_against")) - + ":Link/%s:150" % (filters.get("budget_against")), - _("Account") + ":Link/Account:150", - ] + columns = [ + _(filters.get("budget_against")) + + ":Link/%s:150" % (filters.get("budget_against")), + _("Account") + ":Link/Account:150", + ] - group_months = False if filters["period"] == "Monthly" else True + group_months = False if filters["period"] == "Monthly" else True - fiscal_year = get_fiscal_years(filters) + fiscal_year = get_fiscal_years(filters) - for year in fiscal_year: - for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): - if filters["period"] == "Yearly": - labels = [ - _("Budget") + " " + str(year[0]), - _("Actual ") + " " + str(year[0]), - _("Variance ") + " " + str(year[0]), - ] - for label in labels: - columns.append(label + ":Float:150") - else: - for label in [ - _("Budget") + " (%s)" + " " + str(year[0]), - _("Actual") + " (%s)" + " " + str(year[0]), - _("Variance") + " (%s)" + " " + str(year[0]), - ]: - if group_months: - label = label % ( - formatdate(from_date, format_string="MMM") - + "-" - + formatdate(to_date, format_string="MMM") - ) - else: - label = label % formatdate(from_date, format_string="MMM") + for year in fiscal_year: + for from_date, to_date in get_period_date_ranges(filters["period"], year[0]): + if filters["period"] == "Yearly": + labels = [ + _("Budget") + " " + str(year[0]), + _("Actual ") + " " + str(year[0]), + _("Variance ") + " " + str(year[0]), + ] + for label in labels: + columns.append(label + ":Float:150") + else: + for label in [ + _("Budget") + " (%s)" + " " + str(year[0]), + _("Actual") + " (%s)" + " " + str(year[0]), + _("Variance") + " (%s)" + " " + str(year[0]), + ]: + if group_months: + label = label % ( + formatdate(from_date, format_string="MMM") + + "-" + + formatdate(to_date, format_string="MMM") + ) + else: + label = label % formatdate(from_date, format_string="MMM") - columns.append(label + ":Float:150") + columns.append(label + ":Float:150") - if filters["period"] != "Yearly": - return columns + [ - _("Total Budget") + ":Float:150", - _("Total Actual") + ":Float:150", - _("Total Variance") + ":Float:150", - ] - else: - return columns + if filters["period"] != "Yearly": + return columns + [ + _("Total Budget") + ":Float:150", + _("Total Actual") + ":Float:150", + _("Total Variance") + ":Float:150", + ] + else: + return columns def get_cost_centers(filters): - order_by = "" - if filters.get("budget_against") == "Cost Center": - order_by = "order by lft" + order_by = "" + if filters.get("budget_against") == "Cost Center": + order_by = "order by lft" - if filters.get("budget_against") in ["Cost Center", "Project"]: - return frappe.db.sql_list( - """ + if filters.get("budget_against") in ["Cost Center", "Project"]: + return frappe.db.sql_list( + """ select name from `tab{tab}` where - company=%s + company = %s {order_by} """.format( - tab=filters.get("budget_against"), order_by=order_by - ), - filters.get("company"), - ) - else: - return frappe.db.sql_list( - """ + tab=filters.get("budget_against"), order_by=order_by + ), + filters.get("company"), + ) + else: + return frappe.db.sql_list( + """ select name from `tab{tab}` """.format( - tab=filters.get("budget_against") - ) - ) # nosec + tab=filters.get("budget_against") + ) + ) # nosec # Get dimension & target details def get_dimension_target_details(filters): - budget_against = frappe.scrub(filters.get("budget_against")) + budget_against = frappe.scrub(filters.get("budget_against")) + cond = "" + if filters.get("budget_against_filter"): + cond += """ + and + b.{budget_against} in ( + %s + ) + """.format( + budget_against=budget_against + ) % ", ".join(["%s"] * len(filters.get("budget_against_filter"))) - cond = "" - if filters.get("budget_against_filter"): - cond += """ - and - b.{budget_against} in ( - %s - ) - """.format( - budget_against=budget_against - ) % ", ".join( - ["%s"] * len(filters.get("budget_against_filter")) - ) - - return frappe.db.sql( - """ - select distinct - b.{budget_against} as name + return frappe.db.sql( + """ + select + b.{budget_against} as budget_against, + b.monthly_distribution, + ba.account, + ba.budget_amount, + b.fiscal_year from - `tabBudget` b + `tabBudget` b, + `tabBudget Account` ba where - b.docstatus = 1 + b.name = ba.parent + and b.docstatus = 1 and b.fiscal_year between %s and %s and b.budget_against = %s and b.company = %s @@ -178,65 +179,66 @@ def get_dimension_target_details(filters): order by b.fiscal_year """.format( - budget_against=budget_against, cond=cond - ), - tuple( - [ - filters.from_fiscal_year, - filters.to_fiscal_year, - filters.budget_against, - filters.company, - ] - + filters.get("budget_against_filter") - ), - as_dict=True, - ) + budget_against=budget_against, + cond=cond, + ), + tuple( + [ + filters.from_fiscal_year, + filters.to_fiscal_year, + filters.budget_against, + filters.company, + ] + + filters.get("budget_against_filter") + ), + as_dict=True, + ) # Get target distribution details of accounts of cost center def get_target_distribution_details(filters): - target_details = {} - for d in frappe.db.sql( - """ - select - md.name, - mdp.month, - mdp.percentage_allocation - from - `tabMonthly Distribution Percentage` mdp, - `tabMonthly Distribution` md - where - mdp.parent = md.name - and md.fiscal_year between %s - and %s - order by md.fiscal_year - """, - (filters.from_fiscal_year, filters.to_fiscal_year), - as_dict=1, - ): - target_details.setdefault(d.name, {}).setdefault( - d.month, flt(d.percentage_allocation) - ) + target_details = {} + for d in frappe.db.sql( + """ + select + md.name, + mdp.month, + mdp.percentage_allocation + from + `tabMonthly Distribution Percentage` mdp, + `tabMonthly Distribution` md + where + mdp.parent = md.name + and md.fiscal_year between %s and %s + order by + md.fiscal_year + """, + (filters.from_fiscal_year, filters.to_fiscal_year), + as_dict=1, + ): + target_details.setdefault(d.name, {}).setdefault( + d.month, flt(d.percentage_allocation) + ) - return target_details + return target_details # Get actual details from gl entry def get_actual_details(name, filters): - budget_against = frappe.scrub(filters.get("budget_against")) + budget_against = frappe.scrub(filters.get("budget_against")) - cond = "" - if filters.get("budget_against") == "Cost Center": - cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) - cond = """ - and lft >= \'{lft}\' - and rgt <= \'{rgt}\' + cond = "" + if filters.get("budget_against") == "Cost Center": + cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) + cond = """ + and lft >= "{lft}" + and rgt <= "{rgt}" """.format( - lft=cc_lft, rgt=cc_rgt - ) + lft=cc_lft, rgt=cc_rgt + ) - ac_details = frappe.db.sql( - """ + ac_details = frappe.db.sql( + """ select gl.account, gl.debit, @@ -268,65 +270,56 @@ def get_actual_details(name, filters): gl.name order by gl.fiscal_year """.format( - tab=filters.budget_against, budget_against=budget_against, cond=cond - ), - (filters.from_fiscal_year, filters.to_fiscal_year, name), - as_dict=1, - ) + tab=filters.budget_against, budget_against=budget_against, cond=cond + ), + (filters.from_fiscal_year, filters.to_fiscal_year, name), + as_dict=1, + ) - cc_actual_details = {} - for d in ac_details: - cc_actual_details.setdefault(d.account, []).append(d) + cc_actual_details = {} + for d in ac_details: + cc_actual_details.setdefault(d.account, []).append(d) - return cc_actual_details + return cc_actual_details def get_dimension_account_month_map(filters): - dimension_target_details = get_dimension_target_details(filters) - tdd = get_target_distribution_details(filters) + dimension_target_details = get_dimension_target_details(filters) + tdd = get_target_distribution_details(filters) - cam_map = {} + cam_map = {} - for ccd in dimension_target_details: - accounts = get_accounts(ccd.name, filters) - actual_details = get_actual_details(ccd.name, filters) + for ccd in dimension_target_details: + actual_details = get_actual_details(ccd.budget_against, filters) - for year in get_fiscal_years(filters): - year = year[0] - monthly_distribution = get_monthly_distribution(ccd.name, year, filters) - for month_id in range(1, 13): - month = datetime.date(2013, month_id, 1).strftime( - "%B" - ) # Get month string + for month_id in range(1, 13): + month = datetime.date(2013, month_id, 1).strftime("%B") + cam_map.setdefault(ccd.budget_against, {}).setdefault( + ccd.account, {} + ).setdefault(ccd.fiscal_year, {}).setdefault( + month, frappe._dict({"target": 0.0, "actual": 0.0}) + ) - for account in accounts: - account = account[0] - cam_map.setdefault(ccd.name, {}).setdefault(account, {}).setdefault( - year, {} - ).setdefault(month, frappe._dict({"target": 0.0, "actual": 0.0})) + tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month] + month_percentage = ( + tdd.get(ccd.monthly_distribution, {}).get(month, 0) + if ccd.monthly_distribution + else 100.0 / 12 + ) - tav_dict = cam_map[ccd.name][account][year][month] + tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 - month_percentage = ( - tdd.get(monthly_distribution, {}).get(month, 0) - if monthly_distribution - else 100.0 / 12 - ) + for ad in actual_details.get(ccd.account, []): + if ad.month_name == month and ad.fiscal_year == ccd.fiscal_year: + tav_dict.actual += flt(ad.debit) - flt(ad.credit) - budget_amount = get_budget_amount(ccd.name, year, account, filters) - - tav_dict.target = flt(budget_amount) * month_percentage / 100 - - for ad in actual_details.get(account, []): - if ad.month_name == month and ad.fiscal_year == year: - tav_dict.actual += flt(ad.debit) - flt(ad.credit) - return cam_map + return cam_map def get_fiscal_years(filters): - fiscal_year = frappe.db.sql( - """ + fiscal_year = frappe.db.sql( + """ select name from @@ -336,85 +329,10 @@ def get_fiscal_years(filters): order by year """, - { - "from_fiscal_year": filters["from_fiscal_year"], - "to_fiscal_year": filters["to_fiscal_year"], - }, - ) + { + "from_fiscal_year": filters["from_fiscal_year"], + "to_fiscal_year": filters["to_fiscal_year"], + }, + ) - return fiscal_year - - -def get_accounts(name, filters): - budget_against = frappe.scrub(filters.get("budget_against")) - - accounts = frappe.db.sql( - """ - select - distinct(ba.account) - from - `tabBudget Account` ba - join - `tabBudget` b - on b.name = ba.parent - where - b.docstatus = 1 - and b.fiscal_year between %s and %s - and b.{budget_against} = %s - order by - ba.account - """.format( - budget_against=budget_against - ), - (filters.from_fiscal_year, filters.to_fiscal_year, name), - ) - - return accounts - - -def get_monthly_distribution(name, year, filters): - budget_against = frappe.scrub(filters.get("budget_against")) - - monthly_distribution = frappe.db.sql( - """ - select - monthly_distribution - from - `tabBudget` - where - docstatus = 1 - and {budget_against} = %s - and fiscal_year = %s - """.format( - budget_against=budget_against - ), - (name, year), - ) - - return monthly_distribution[0][0] if monthly_distribution else None - - -def get_budget_amount(name, year, account, filters): - budget_against = frappe.scrub(filters.get("budget_against")) - - budget_amount = frappe.db.sql( - """ - select - ba.budget_amount - from - `tabBudget Account` ba - join - `tabBudget` b - on b.name = ba.parent - where - b.docstatus = 1 - and b.{budget_against} = %s - and b.fiscal_year = %s - and ba.account = %s - """.format( - budget_against=budget_against - ), - (name, year, account), - ) - - return budget_amount[0][0] if budget_amount else 0 + return fiscal_year From aadf264b497677a6d962f3086f5d7fabc6528f17 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Sat, 9 May 2020 16:15:51 +0530 Subject: [PATCH 37/47] Assessment Plan not getting created --- .../assessment_plan/assessment_plan.json | 702 ++---------------- 1 file changed, 54 insertions(+), 648 deletions(-) diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan.json b/erpnext/education/doctype/assessment_plan/assessment_plan.json index bc3946440c..95ed853c21 100644 --- a/erpnext/education/doctype/assessment_plan/assessment_plan.json +++ b/erpnext/education/doctype/assessment_plan/assessment_plan.json @@ -1,790 +1,207 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, - "allow_rename": 0, "autoname": "EDU-ASP-.YYYY.-.#####", - "beta": 0, "creation": "2015-11-12 16:34:34.658092", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "student_group", + "assessment_name", + "assessment_group", + "grading_scale", + "column_break_2", + "course", + "program", + "academic_year", + "academic_term", + "section_break_5", + "schedule_date", + "room", + "examiner", + "examiner_name", + "column_break_4", + "from_time", + "to_time", + "supervisor", + "supervisor_name", + "section_break_20", + "maximum_assessment_score", + "assessment_criteria", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "student_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Student Group", - "length": 0, - "no_copy": 0, "options": "Student Group", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "assessment_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assessment 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, - "unique": 0 + "label": "Assessment Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "assessment_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Assessment Group", - "length": 0, - "no_copy": 0, "options": "Assessment Group", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "course.default_grading_scale", + "fetch_if_empty": 1, "fieldname": "grading_scale", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Grading Scale", - "length": 0, - "no_copy": 0, "options": "Grading Scale", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "student_group.course", + "fetch_if_empty": 1, "fieldname": "course", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Course", - "length": 0, - "no_copy": 0, "options": "Course", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "student_group.program", "fieldname": "program", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "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 + "options": "Program" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "student_group.academic_year", "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": 0, - "in_standard_filter": 0, "label": "Academic Year", - "length": 0, - "no_copy": 0, - "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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Academic Year" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "student_group.academic_term", "fieldname": "academic_term", "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": "Academic Term", - "length": 0, - "no_copy": 0, - "options": "Academic Term", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Academic Term" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "", "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": "Schedule", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Schedule" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Today", "fieldname": "schedule_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": "Schedule 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "room", "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": "Room", - "length": 0, - "no_copy": 0, - "options": "Room", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Room" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "examiner", "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": "Examiner", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Instructor" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "examiner.instructor_name", "fieldname": "examiner_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": "Examiner 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 + "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_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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_time", "fieldtype": "Time", - "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": "From Time", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "to_time", "fieldtype": "Time", - "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": "To Time", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "supervisor", "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": "Supervisor", - "length": 0, - "no_copy": 0, - "options": "Instructor", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Instructor" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "supervisor.instructor_name", "fieldname": "supervisor_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Supervisor 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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_20", "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": "Evaluate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Evaluate" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "maximum_assessment_score", "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": "Maximum Assessment Score", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "assessment_criteria", "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": "Assessment Criteria", - "length": 0, - "no_copy": 0, "options": "Assessment Plan Criteria", - "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 + "reqd": 1 }, { - "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": "Assessment Plan", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 } ], - "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, - "menu_index": 0, - "modified": "2018-08-30 00:48:03.475522", + "links": [], + "modified": "2020-05-09 14:56:26.746988", "modified_by": "Administrator", "module": "Education", "name": "Assessment Plan", - "name_case": "", "owner": "Administrator", "permissions": [ { @@ -794,28 +211,17 @@ "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": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "restrict_to_domain": "Education", - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "assessment_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "assessment_name" } \ No newline at end of file From 65d3ac814d053cd1c191c8345bf8226fde4d75a7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 9 May 2020 17:39:59 +0530 Subject: [PATCH 38/47] fix: Add accidentally removed function --- erpnext/accounts/dashboard_fixtures.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index 30eb5e3115..a106f70dd0 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -107,6 +107,10 @@ def get_charts(): } ] +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_company_for_dashboards(): company = frappe.defaults.get_defaults().company From b81bd5f70c13e269a6a28bbd28022978d1d2a46f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 10 May 2020 17:24:11 +0530 Subject: [PATCH 39/47] fix: Formatting fixes --- .../budget_variance_report.py | 69 +++++-------------- 1 file changed, 19 insertions(+), 50 deletions(-) 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 4d5892a913..49c1d0f2cc 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -22,9 +22,7 @@ def execute(filters=None): else: dimensions = get_cost_centers(filters) - period_month_ranges = get_period_month_ranges( - filters["period"], filters["from_fiscal_year"] - ) + period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) cam_map = get_dimension_account_month_map(filters) data = [] @@ -41,9 +39,7 @@ def execute(filters=None): 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"] - ): + for i, fieldname in enumerate(["target", "actual", "variance"]): value = flt(month_data.get(fieldname)) period_data[i] += value totals[i] += value @@ -67,7 +63,7 @@ def get_columns(filters): columns = [ _(filters.get("budget_against")) + ":Link/%s:150" % (filters.get("budget_against")), - _("Account") + ":Link/Account:150", + _("Account") + ":Link/Account:150" ] group_months = False if filters["period"] == "Monthly" else True @@ -80,7 +76,7 @@ def get_columns(filters): labels = [ _("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), - _("Variance ") + " " + str(year[0]), + _("Variance ") + " " + str(year[0]) ] for label in labels: columns.append(label + ":Float:150") @@ -88,7 +84,7 @@ def get_columns(filters): for label in [ _("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), - _("Variance") + " (%s)" + " " + str(year[0]), + _("Variance") + " (%s)" + " " + str(year[0]) ]: if group_months: label = label % ( @@ -105,7 +101,7 @@ def get_columns(filters): return columns + [ _("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150", - _("Total Variance") + ":Float:150", + _("Total Variance") + ":Float:150" ] else: return columns @@ -126,11 +122,8 @@ def get_cost_centers(filters): where company = %s {order_by} - """.format( - tab=filters.get("budget_against"), order_by=order_by - ), - filters.get("company"), - ) + """.format(tab=filters.get("budget_against"), order_by=order_by), + filters.get("company")) else: return frappe.db.sql_list( """ @@ -138,10 +131,7 @@ def get_cost_centers(filters): name from `tab{tab}` - """.format( - tab=filters.get("budget_against") - ) - ) # nosec + """.format(tab=filters.get("budget_against"))) # nosec # Get dimension & target details @@ -149,14 +139,8 @@ def get_dimension_target_details(filters): budget_against = frappe.scrub(filters.get("budget_against")) cond = "" if filters.get("budget_against_filter"): - cond += """ - and - b.{budget_against} in ( - %s - ) - """.format( - budget_against=budget_against - ) % ", ".join(["%s"] * len(filters.get("budget_against_filter"))) + cond += """ and b.{budget_against} in (%s)""".format( + budget_against=budget_against) % ", ".join(["%s"] * len(filters.get("budget_against_filter"))) return frappe.db.sql( """ @@ -190,9 +174,7 @@ def get_dimension_target_details(filters): filters.company, ] + filters.get("budget_against_filter") - ), - as_dict=True, - ) + ), as_dict=True) # Get target distribution details of accounts of cost center @@ -213,29 +195,24 @@ def get_target_distribution_details(filters): order by md.fiscal_year """, - (filters.from_fiscal_year, filters.to_fiscal_year), - as_dict=1, - ): + (filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1): target_details.setdefault(d.name, {}).setdefault( d.month, flt(d.percentage_allocation) ) return target_details - # Get actual details from gl entry def get_actual_details(name, filters): budget_against = frappe.scrub(filters.get("budget_against")) - cond = "" + if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = """ and lft >= "{lft}" and rgt <= "{rgt}" - """.format( - lft=cc_lft, rgt=cc_rgt - ) + """.format(lft=cc_lft, rgt=cc_rgt) ac_details = frappe.db.sql( """ @@ -269,12 +246,8 @@ def get_actual_details(name, filters): group by gl.name order by gl.fiscal_year - """.format( - tab=filters.budget_against, budget_against=budget_against, cond=cond - ), - (filters.from_fiscal_year, filters.to_fiscal_year, name), - as_dict=1, - ) + """.format(tab=filters.budget_against, budget_against=budget_against, cond=cond), + (filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1) cc_actual_details = {} for d in ac_details: @@ -282,7 +255,6 @@ def get_actual_details(name, filters): return cc_actual_details - def get_dimension_account_month_map(filters): dimension_target_details = get_dimension_target_details(filters) tdd = get_target_distribution_details(filters) @@ -326,13 +298,10 @@ def get_fiscal_years(filters): `tabFiscal Year` where name between %(from_fiscal_year)s and %(to_fiscal_year)s - order by - year """, { "from_fiscal_year": filters["from_fiscal_year"], - "to_fiscal_year": filters["to_fiscal_year"], - }, - ) + "to_fiscal_year": filters["to_fiscal_year"] + }) return fiscal_year From 3a914a7cd74abf5445e1d9d45b84c3b307229911 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 11 May 2020 11:44:45 +0530 Subject: [PATCH 40/47] fix: '<' not supported between instances of 'str' and 'int' --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ddf4ec0393..10f2555475 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -181,7 +181,7 @@ class StockEntry(StockController): stock_items = self.get_stock_items() serialized_items = self.get_serialized_items() for item in self.get("items"): - if item.qty and item.qty < 0: + if flt(item.qty) and flt(item.qty) < 0: frappe.throw(_("Row {0}: The item {1}, quantity must be positive number") .format(item.idx, frappe.bold(item.item_code))) From 6e5952fa6952c3cae1a860c8825f4ed13e65dac1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 11 May 2020 16:11:43 +0530 Subject: [PATCH 41/47] fix: Changed label for payroll working days based on field --- erpnext/hr/doctype/hr_settings/hr_settings.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 9161ed822a..ebf8723be6 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -13,12 +13,12 @@ "stop_birthday_reminders", "expense_approver_mandatory_in_expense_claim", "payroll_settings", - "payroll_based_on", - "max_working_hours_against_timesheet", + "payroll_based_on", + "max_working_hours_against_timesheet", "include_holidays_in_total_working_days", "disable_rounded_total", "column_break_11", - "daily_wages_fraction_for_half_day", + "daily_wages_fraction_for_half_day", "email_salary_slip_to_employee", "encrypt_salary_slips_in_emails", "password_policy", @@ -191,7 +191,7 @@ "default": "Leave", "fieldname": "payroll_based_on", "fieldtype": "Select", - "label": "Calculate Working Days in Payroll based on", + "label": "Calculate Payroll Working Days Based On", "options": "Leave\nAttendance" }, { @@ -206,7 +206,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-04-13 21:20:59.382394", + "modified": "2020-05-11 13:02:51.274347", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", From 85a89812a42a8d8e5f80dbec89c77e5fe5e82988 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 11 May 2020 19:23:18 +0530 Subject: [PATCH 42/47] fix: Run income-tax-slab patch only if slab already exists in payroll period (#21684) --- ...ve_tax_slabs_from_payroll_period_to_income_tax_slab.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 179be2cfde..ec94cd01d1 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 @@ -7,7 +7,7 @@ import frappe from frappe.model.utils.rename_field import rename_field def execute(): - if not frappe.db.table_exists("Payroll Period"): + if not (frappe.db.table_exists("Payroll Period") and frappe.db.table_exists("Taxable Salary Slab")): return for doctype in ("income_tax_slab", "salary_structure_assignment", "employee_other_income", "income_tax_slab_other_charges"): @@ -60,6 +60,9 @@ def execute(): """, (income_tax_slab.name, company.name, period.start_date)) # move other incomes to separate document + if not frappe.db.table_exists("Employee Tax Exemption Proof Submission"): + return + migrated = [] proofs = frappe.get_all("Employee Tax Exemption Proof Submission", filters = {'docstatus': 1}, @@ -79,6 +82,9 @@ def execute(): except: pass + if not frappe.db.table_exists("Employee Tax Exemption Declaration"): + return + declerations = frappe.get_all("Employee Tax Exemption Declaration", filters = {'docstatus': 1}, fields =['payroll_period', 'employee', 'company', 'income_from_other_sources'] From 28c9468dad126985ee062d9d155449124196e1e7 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Mon, 11 May 2020 19:28:10 +0530 Subject: [PATCH 43/47] fix(patch): use translated string while setting notification template (#21679) --- erpnext/patches/v11_0/set_default_email_template_in_hr.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v11_0/set_default_email_template_in_hr.py b/erpnext/patches/v11_0/set_default_email_template_in_hr.py index 14954fbeb3..4622376109 100644 --- a/erpnext/patches/v11_0/set_default_email_template_in_hr.py +++ b/erpnext/patches/v11_0/set_default_email_template_in_hr.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals +from frappe import _ import frappe def execute(): hr_settings = frappe.get_single("HR Settings") - hr_settings.leave_approval_notification_template = "Leave Approval Notification" - hr_settings.leave_status_notification_template = "Leave Status Notification" - hr_settings.save() \ No newline at end of file + hr_settings.leave_approval_notification_template = _("Leave Approval Notification") + hr_settings.leave_status_notification_template = _("Leave Status Notification") + hr_settings.save() From 30f26b4457e0633266c33ab19d0ab0096db89490 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Mon, 11 May 2020 19:54:46 +0530 Subject: [PATCH 44/47] fix: do not setup charts if not setup_complete or no company found (#21670) * fix: do not setup charts if not setup_complete or no company found Signed-off-by: Chinmay D. Pai * chore: remove check for setup_complete moved the check for setup_complete to frappe Signed-off-by: Chinmay D. Pai * chore: return an empty dict from get_data if company not set Signed-off-by: Chinmay D. Pai --- erpnext/accounts/dashboard_fixtures.py | 176 +++++++++++++------------ 1 file changed, 90 insertions(+), 86 deletions(-) diff --git a/erpnext/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py index a106f70dd0..cdd166134f 100644 --- a/erpnext/accounts/dashboard_fixtures.py +++ b/erpnext/accounts/dashboard_fixtures.py @@ -1,15 +1,22 @@ # 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 def get_data(): - return frappe._dict({ - "dashboards": get_dashboards(), - "charts": get_charts(), + data = frappe._dict({ + "dashboards": [], + "charts": [] }) + 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 [{ @@ -24,88 +31,87 @@ def get_dashboards(): ] }] -def get_charts(): - company = frappe.get_doc("Company", get_company_for_dashboards()) +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) 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, - "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" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Monthly", - "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({}), - "chart_type": "Sum", - "timeseries": 1, - "based_on": "posting_date", - "owner": "Administrator", - "document_type": "Purchase Invoice", - "type": "Bar" - }, - { - "doctype": "Dashboard Chart", - "time_interval": "Monthly", - "name": "Outgoing Bills (Sales Invoice)", - "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" - } - ] + { + "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, + "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" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "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({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Purchase Invoice", + "type": "Bar" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "name": "Outgoing Bills (Sales Invoice)", + "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" + } + ] def get_account(account_type, company): accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) @@ -113,11 +119,9 @@ def get_account(account_type, company): return accounts[0].name def get_company_for_dashboards(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: + company = get_default_company() + if not company: company_list = frappe.get_list("Company") if company_list: - return company_list[0].name - return None \ No newline at end of file + company = company_list[0].name + return company From b52e40037abe4453934cc8673b3aa36bb35aaf59 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 11 May 2020 20:01:57 +0530 Subject: [PATCH 45/47] fix: Remove domain restriction from Location doctype (#21659) --- erpnext/assets/doctype/location/location.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/location/location.json b/erpnext/assets/doctype/location/location.json index 6a35130f30..f56fd05d98 100644 --- a/erpnext/assets/doctype/location/location.json +++ b/erpnext/assets/doctype/location/location.json @@ -141,7 +141,7 @@ ], "is_tree": 1, "links": [], - "modified": "2020-03-18 18:00:08.885805", + "modified": "2020-05-08 16:11:11.375701", "modified_by": "Administrator", "module": "Assets", "name": "Location", @@ -221,7 +221,6 @@ } ], "quick_entry": 1, - "restrict_to_domain": "Agriculture", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", From 97715f2877320486353a17662ea5f57f8eaf4f8c Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 11 May 2020 20:45:37 +0530 Subject: [PATCH 46/47] fix: Message for missing valuation rate (#21686) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- erpnext/stock/stock_ledger.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 10f2555475..62c9eb1eb2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -470,7 +470,7 @@ class StockEntry(StockController): "qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty), "serial_no": item.serial_no, "voucher_type": self.doctype, - "voucher_no": item.name, + "voucher_no": self.name, "company": self.company, "allow_zero_valuation": item.allow_zero_valuation_rate, }) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b4cb8cadb4..e1b3730f2f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -548,7 +548,16 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \ and cint(erpnext.is_perpetual_inventory_enabled(company)): frappe.local.message_log = [] - frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.") - .format(item_code, voucher_type, voucher_no)) + form_link = frappe.utils.get_link_to_form("Item", item_code) + + message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no) + message += "

" + _(" Here are the options to proceed:") + solutions = "
  • " + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "
  • " + solutions += "
  • " + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "
  • " + sub_solutions = "
    • " + _("Create an incoming stock transaction for the Item.") + "
    • " + sub_solutions += "
    • " + _("Mention Valuation Rate in the Item master.") + "
    " + msg = message + solutions + sub_solutions + "" + + frappe.throw(msg=msg, title=_("Valuation Rate Missing")) return valuation_rate From 498f6583210e730207e922823c5e6d87579c55b8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 11 May 2020 20:47:08 +0530 Subject: [PATCH 47/47] refactor: rename getting started to home (#21674) * feat: rename getting started to home * feat: added patch for renaming --- erpnext/patches.txt | 1 + .../getting_started.json => home/home.json} | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) rename erpnext/setup/desk_page/{getting_started/getting_started.json => home/home.json} (97%) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3f90d36916..f72172474c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -680,3 +680,4 @@ erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.retain_permission_rules_for_video_doctype erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") +execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) diff --git a/erpnext/setup/desk_page/getting_started/getting_started.json b/erpnext/setup/desk_page/home/home.json similarity index 97% rename from erpnext/setup/desk_page/getting_started/getting_started.json rename to erpnext/setup/desk_page/home/home.json index 63d8984c40..63cd5c5cec 100644 --- a/erpnext/setup/desk_page/getting_started/getting_started.json +++ b/erpnext/setup/desk_page/home/home.json @@ -47,26 +47,20 @@ } ], "category": "Modules", - "charts": [ - { - "chart_name": "Bank Balance", - "label": "Bank Balance" - } - ], + "charts": [], "creation": "2020-01-23 13:46:38.833076", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, - "icon": "", "idx": 0, "is_standard": 1, - "label": "Getting Started", - "modified": "2020-04-01 11:30:19.763099", + "label": "Home", + "modified": "2020-05-11 10:20:37.358701", "modified_by": "Administrator", "module": "Setup", - "name": "Getting Started", + "name": "Home", "owner": "Administrator", "pin_to_bottom": 0, "pin_to_top": 1,