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''' 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/accounts/dashboard_fixtures.py b/erpnext/accounts/dashboard_fixtures.py new file mode 100644 index 0000000000..cdd166134f --- /dev/null +++ b/erpnext/accounts/dashboard_fixtures.py @@ -0,0 +1,127 @@ +# 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(): + 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 [{ + "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): + 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_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 = get_default_company() + if not company: + company_list = frappe.get_list("Company") + if company_list: + company = company_list[0].name + return company 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 }) 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", 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: 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..49c1d0f2cc 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -2,16 +2,19 @@ # 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 -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"): @@ -43,20 +46,25 @@ def execute(filters=None): period_data[0] += last_total - if(filters.get("show_cumulative")): + 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" : + if filters["period"] != "Yearly": row += totals data.append(row) 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 @@ -65,84 +73,181 @@ def get_columns(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])] + labels = [ + _("Budget") + " " + str(year[0]), + _("Actual ") + " " + str(year[0]), + _("Variance ") + " " + str(year[0]) + ] for label in labels: - columns.append(label+":Float:150") + columns.append(label + ":Float:150") else: - for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]: + 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")) + 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"] + 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" + order_by = "" if filters.get("budget_against") == "Cost Center": - cond = "order by lft" + 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")) + 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 + 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): + 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 = \ - frappe.scrub(filters.get('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(""" - 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) + 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=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)) + 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 -#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")) + cond = "" 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 = """ + 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: @@ -151,7 +256,6 @@ def get_actual_details(name, filters): 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) @@ -161,28 +265,43 @@ def get_dimension_account_month_map(filters): actual_details = get_actual_details(ccd.budget_against, 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 - })) + 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 = 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 + month_percentage = ( + tdd.get(ccd.monthly_distribution, {}).get(month, 0) + if ccd.monthly_distribution + else 100.0 / 12 + ) 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) + if ad.month_name == month and ad.fiscal_year == ccd.fiscal_year: + tav_dict.actual += flt(ad.debit) - flt(ad.credit) 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 + """, + { + "from_fiscal_year": filters["from_fiscal_year"], + "to_fiscal_year": filters["to_fiscal_year"] + }) return fiscal_year 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..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, - 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 = [], [] 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", 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: 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") 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) 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 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 diff --git a/erpnext/healthcare/dashboard_fixtures.py b/erpnext/healthcare/dashboard_fixtures.py new file mode 100644 index 0000000000..fc3d62f2d8 --- /dev/null +++ b/erpnext/healthcare/dashboard_fixtures.py @@ -0,0 +1,41 @@ +# 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", + "name": "Patient Appointments", + "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" + } + ] 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", 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 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() 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", 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 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'")) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ba17b67de1..f72172474c 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 @@ -630,7 +631,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") @@ -679,3 +679,5 @@ 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") +execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) 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)) 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() 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/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.""" 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'] diff --git a/erpnext/regional/address_template/templates/taiwan.html b/erpnext/regional/address_template/templates/taiwan.html new file mode 100644 index 0000000000..1715bea48a --- /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 -%} diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.json b/erpnext/selling/report/sales_analytics/sales_analytics.json index 71932610a6..bf9edd6cd4 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": 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": "2020-04-30 19:49:02.303320", + "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" } diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index f1726ab8bf..97d9322918 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,6 +220,8 @@ class Analytics(object): self.data.append(row) + self.data.append(total_row) + def get_rows_by_group(self): self.get_periodic_data() out = [] @@ -232,8 +240,10 @@ 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 + row["total"] = total out = [row] + out + self.data = out def get_periodic_data(self): diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py index 4d81a1e4dd..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", @@ -134,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", 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, 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 88d42590fc..0000000000 --- a/erpnext/setup/setup_wizard/data/dashboard_charts.py +++ /dev/null @@ -1,144 +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" } - ] - }, - { - "doctype": "Dashboard", - "dashboard_name": "Buying", - "charts": [ - { "chart": "Purchase Analytics" }, - { "chart": "Material Request Purchase Analysis" }, - { "chart": "Purchase Order Analysis" }, - { "chart": "Requested Items to Order" }, - ] - - } - ], - "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 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 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 = [] diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ddf4ec0393..62c9eb1eb2 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))) @@ -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/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)) 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 = "" + msg = message + solutions + sub_solutions + "" + + frappe.throw(msg=msg, title=_("Valuation Rate Missing")) return valuation_rate