Merge branch 'develop' of https://github.com/frappe/erpnext into purchase-dashboard
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 786b9cf..38d8a62 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
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 c3e2f7d..5decccb 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 @@
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 0000000..cdd1661
--- /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 3f3174a..7ecdc41 100644
--- a/erpnext/accounts/doctype/payment_order/payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/payment_order.py
@@ -80,7 +80,7 @@
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 97ae5ff..7508683 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 fb1a4f4..bfe35ab 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -297,7 +297,8 @@
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 39e218b..49c1d0f 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 @@
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 @@
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 @@
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 @@
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 1c45810..714e48d 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 @@
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 6a35130..f56fd05 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 74b3582..ec7d14d 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -153,7 +153,7 @@
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 50b98e9..263005e 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
@@ -62,6 +62,8 @@
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 5df35df..bdde9ee 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -15,7 +15,7 @@
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 @@
"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 @@
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 b55946a..f6f431c 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js
@@ -47,6 +47,8 @@
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 64f53b5..7616b4c 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -12,13 +12,12 @@
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 @@
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 38bf79e..95b19ec 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -19,6 +19,5 @@
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 bc39464..95ed853 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 967a030..0e548db 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 0000000..fc3d62f
--- /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 9161ed8..ebf8723 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 f75bb41..61faea1 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -145,7 +145,7 @@
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 d98ed1b..82ed277 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_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 c550d49..76e10e5 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -248,8 +248,7 @@
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 77a1fcc..364e2ff 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.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 @@
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 @@
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 @@
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 452c836..2ab668a 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 @@
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 @@
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 @@
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 8ca6e3e..308c438 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 @@
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 72c5f38..8223206 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 b2bb22a..5e9d82a 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.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 @@
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 @@
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 9e6277d..ee192d7 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",
@@ -55,14 +55,6 @@
"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",
"fieldtype": "Percent",
@@ -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 e9627a5..e43b98a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -206,30 +206,31 @@
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 84bfab2..8301f30 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -421,6 +421,9 @@
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 @@
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 ba17b67..f721724 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -495,6 +495,7 @@
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_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.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 0000000..00ab562
--- /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 14954fb..4622376 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 2a91e1b..0000000
--- 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 f5bd8c3..6f843cd 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 @@
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 179be2c..ec94cd0 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 @@
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 @@
""", (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 @@
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 0000000..1715bea
--- /dev/null
+++ b/erpnext/regional/address_template/templates/taiwan.html
@@ -0,0 +1,4 @@
+{{ country }}<br>{% if pincode %}{{ pincode }}<br>{% endif -%}{{ county }}{{ city }}{{ address_line1 }}{% if address_line2 %}{{ address_line2 }}{% endif -%}
+{% if phone %}<br>Phone: {{ phone }}{% endif -%}
+{% if fax %}<br>Fax: {{ fax }}{% endif -%}
+{% if email_id %}<br>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 7193261..bf9edd6 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 f1726ab..97d9322 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.py
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.py
@@ -194,6 +194,9 @@
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 @@
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 @@
self.data.append(row)
+ self.data.append(total_row)
+
def get_rows_by_group(self):
self.get_periodic_data()
out = []
@@ -232,8 +240,10 @@
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 4d81a1e..7e8501d 100644
--- a/erpnext/selling/report/sales_analytics/test_analytics.py
+++ b/erpnext/selling/report/sales_analytics/test_analytics.py
@@ -34,6 +34,21 @@
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",
"apr_2017": 0.0,
@@ -135,6 +150,21 @@
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",
"apr_2017": 0.0,
diff --git a/erpnext/setup/desk_page/getting_started/getting_started.json b/erpnext/setup/desk_page/home/home.json
similarity index 96%
rename from erpnext/setup/desk_page/getting_started/getting_started.json
rename to erpnext/setup/desk_page/home/home.json
index 63d8984..63cd5c5 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 88d4259..0000000
--- 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 3be6f44..8bb0a05 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -485,8 +485,6 @@
# 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 @@
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 9b7249e..a091ac7 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -7,7 +7,7 @@
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 @@
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_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 @@
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 c62b3ab..4cc50bb 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -572,6 +572,13 @@
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 ddf4ec0..62c9eb1 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -181,7 +181,7 @@
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 @@
"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 eb4867d..cd86be3 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -177,7 +177,7 @@
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 b4cb8ca..e1b3730 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -548,7 +548,16 @@
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 += "<br><br>" + _(" Here are the options to proceed:")
+ solutions = "<li>" + _("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) + "</li>"
+ solutions += "<li>" + _("If not, you can Cancel / Submit this entry ") + _("{0}").format(frappe.bold("after")) + _(" performing either one below:") + "</li>"
+ sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
+ sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
+ msg = message + solutions + sub_solutions + "</li>"
+
+ frappe.throw(msg=msg, title=_("Valuation Rate Missing"))
return valuation_rate