Merge pull request #10982 from netchampfaris/multiple-users-pos-profile
[Enhancement] Allow Multiple users in POS Profile
diff --git a/.eslintrc b/.eslintrc
index c9cd552..4dd1216 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -132,6 +132,7 @@
"get_url_arg": true,
"get_server_fields": true,
"set_multiple": true,
- "QUnit": true
+ "QUnit": true,
+ "Chart": true
}
}
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index b245f56..2812fa7 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -4,7 +4,7 @@
import frappe
from erpnext.hooks import regional_overrides
-__version__ = '9.2.2'
+__version__ = '9.2.4'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 790003c..cc35652 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -566,17 +566,26 @@
account = get_bank_cash_account(mode_of_payment, company).get("account")
if not account:
+ '''
+ Set the default account first. If the user hasn't set any default account then, he doesn't
+ want us to set any random account. In this case set the account only if there is single
+ account (of that type), otherwise return empty dict.
+ '''
if account_type=="Bank":
account = frappe.db.get_value("Company", company, "default_bank_account")
if not account:
- account = frappe.db.get_value("Account",
- {"company": company, "account_type": "Bank", "is_group": 0})
+ account_list = frappe.get_all("Account", filters = {"company": company,
+ "account_type": "Bank", "is_group": 0})
+ if len(account_list) == 1:
+ account = account_list[0].name
elif account_type=="Cash":
account = frappe.db.get_value("Company", company, "default_cash_account")
if not account:
- account = frappe.db.get_value("Account",
- {"company": company, "account_type": "Cash", "is_group": 0})
+ account_list = frappe.get_all("Account", filters = {"company": company,
+ "account_type": "Cash", "is_group": 0})
+ if len(account_list) == 1:
+ account = account_list[0].name
if account:
account_details = frappe.db.get_value("Account", account,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 17ac1f7..52d8a2d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -648,13 +648,13 @@
set_difference_amount: function(frm) {
var unallocated_amount = 0;
+ var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
+ function(d) { return flt(d.amount) }));
+
if(frm.doc.party) {
var party_amount = frm.doc.payment_type=="Receive" ?
frm.doc.paid_amount : frm.doc.received_amount;
- var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
- function(d) { return flt(d.amount) }));
-
if(frm.doc.total_allocated_amount < party_amount) {
if(frm.doc.payment_type == "Receive") {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 56db392..ba5b7f2 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -309,14 +309,16 @@
rows = []
for d in data:
- rows.append(d[self.ageing_col_idx_start : self.ageing_col_idx_start+4])
-
- if rows:
- rows.insert(0, [[d.get("label")] for d in ageing_columns])
+ rows.append(
+ {
+ 'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+4]
+ }
+ )
return {
"data": {
- 'labels': rows
+ 'labels': [d.get("label") for d in ageing_columns],
+ 'datasets': rows
},
"type": 'percentage'
}
diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py
index a8514b2..b97e097 100644
--- a/erpnext/config/projects.py
+++ b/erpnext/config/projects.py
@@ -15,6 +15,7 @@
{
"type": "doctype",
"name": "Task",
+ "route": "Tree/Task",
"description": _("Project activity / task."),
},
{
diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.js b/erpnext/hr/doctype/employee_loan/employee_loan.js
index 33d3a85..1f38105 100644
--- a/erpnext/hr/doctype/employee_loan/employee_loan.js
+++ b/erpnext/hr/doctype/employee_loan/employee_loan.js
@@ -87,22 +87,24 @@
},
employee_loan_application: function (frm) {
- return frappe.call({
- method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application",
- args: {
- "employee_loan_application": frm.doc.employee_loan_application
- },
- callback: function (r) {
- if (!r.exc && r.message) {
- frm.set_value("loan_type", r.message.loan_type);
- frm.set_value("loan_amount", r.message.loan_amount);
- frm.set_value("repayment_method", r.message.repayment_method);
- frm.set_value("monthly_repayment_amount", r.message.repayment_amount);
- frm.set_value("repayment_periods", r.message.repayment_periods);
- frm.set_value("rate_of_interest", r.message.rate_of_interest);
- }
- }
- })
+ if(frm.doc.employee_loan_application){
+ return frappe.call({
+ method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application",
+ args: {
+ "employee_loan_application": frm.doc.employee_loan_application
+ },
+ callback: function (r) {
+ if (!r.exc && r.message) {
+ frm.set_value("loan_type", r.message.loan_type);
+ frm.set_value("loan_amount", r.message.loan_amount);
+ frm.set_value("repayment_method", r.message.repayment_method);
+ frm.set_value("monthly_repayment_amount", r.message.repayment_amount);
+ frm.set_value("repayment_periods", r.message.repayment_periods);
+ frm.set_value("rate_of_interest", r.message.rate_of_interest);
+ }
+ }
+ });
+ }
},
repayment_method: function (frm) {
diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py
index 3216f0d..76a7f6f 100644
--- a/erpnext/hub_node/__init__.py
+++ b/erpnext/hub_node/__init__.py
@@ -37,7 +37,9 @@
return response
@frappe.whitelist()
-def get_item_details(hub_sync_id):
+def get_item_details(hub_sync_id=None):
+ if not hub_sync_id:
+ return
connection = get_connection()
return connection.get_doc('Hub Item', hub_sync_id)
diff --git a/erpnext/hub_node/page/hub/hub.js b/erpnext/hub_node/page/hub/hub.js
index b065f0a..15bd97d 100644
--- a/erpnext/hub_node/page/hub/hub.js
+++ b/erpnext/hub_node/page/hub/hub.js
@@ -382,6 +382,7 @@
},
method: "erpnext.hub_node.get_item_details",
callback: (r) => {
+ if (!r || !r.message) return;
let item = r.message;
this.item_cache[item_code] = item;
this.render_item_page(item);
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js
index c3e6f20..226ebfc 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.js
+++ b/erpnext/manufacturing/doctype/production_order/production_order.js
@@ -17,6 +17,14 @@
}
});
+ frm.set_query("source_warehouse", function() {
+ return {
+ filters: {
+ 'company': frm.doc.company,
+ }
+ }
+ });
+
frm.set_query("source_warehouse", "required_items", function() {
return {
filters: {
diff --git a/erpnext/manufacturing/page/production_analytics/production_analytics.js b/erpnext/manufacturing/page/production_analytics/production_analytics.js
index 39168b7..efbd0a5 100644
--- a/erpnext/manufacturing/page/production_analytics/production_analytics.js
+++ b/erpnext/manufacturing/page/production_analytics/production_analytics.js
@@ -64,7 +64,7 @@
var chart_data = this.get_chart_data ? this.get_chart_data() : null;
- this.chart = new frappe.chart.FrappeChart({
+ this.chart = new Chart({
parent: ".chart",
data: chart_data,
type: 'line'
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index df38cfe..b8f324a 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -19,38 +19,47 @@
},
refresh: function(frm) {
- var doc = frm.doc;
- if(doc.__islocal) {
- if(!frm.doc.exp_end_date) {
- frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+ frm.fields_dict['parent_task'].get_query = function() {
+ return {
+ filters: {
+ "is_group": 1,
+ }
}
}
-
- if(!doc.__islocal) {
- if(frappe.model.can_read("Timesheet")) {
- frm.add_custom_button(__("Timesheet"), function() {
- frappe.route_options = {"project": doc.project, "task": doc.name}
- frappe.set_route("List", "Timesheet");
- }, __("View"), true);
- }
- if(frappe.model.can_read("Expense Claim")) {
- frm.add_custom_button(__("Expense Claims"), function() {
- frappe.route_options = {"project": doc.project, "task": doc.name}
- frappe.set_route("List", "Expense Claim");
- }, __("View"), true);
+ if(!frm.is_group){
+ var doc = frm.doc;
+ if(doc.__islocal) {
+ if(!frm.doc.exp_end_date) {
+ frm.set_value("exp_end_date", frappe.datetime.add_days(new Date(), 7));
+ }
}
- if(frm.perm[0].write) {
- if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
- frm.add_custom_button(__("Close"), function() {
- frm.set_value("status", "Closed");
- frm.save();
- });
- } else {
- frm.add_custom_button(__("Reopen"), function() {
- frm.set_value("status", "Open");
- frm.save();
- });
+ if(!doc.__islocal) {
+ if(frappe.model.can_read("Timesheet")) {
+ frm.add_custom_button(__("Timesheet"), function() {
+ frappe.route_options = {"project": doc.project, "task": doc.name}
+ frappe.set_route("List", "Timesheet");
+ }, __("View"), true);
+ }
+ if(frappe.model.can_read("Expense Claim")) {
+ frm.add_custom_button(__("Expense Claims"), function() {
+ frappe.route_options = {"project": doc.project, "task": doc.name}
+ frappe.set_route("List", "Expense Claim");
+ }, __("View"), true);
+ }
+
+ if(frm.perm[0].write) {
+ if(frm.doc.status!=="Closed" && frm.doc.status!=="Cancelled") {
+ frm.add_custom_button(__("Close"), function() {
+ frm.set_value("status", "Closed");
+ frm.save();
+ });
+ } else {
+ frm.add_custom_button(__("Reopen"), function() {
+ frm.set_value("status", "Open");
+ frm.save();
+ });
+ }
}
}
}
@@ -71,6 +80,21 @@
}
},
+ is_group: function(frm) {
+ frappe.call({
+ method:"erpnext.projects.doctype.task.task.check_if_child_exists",
+ args: {
+ name: frm.doc.name
+ },
+ callback: function(r){
+ if(r.message){
+ frappe.msgprint(__('Cannot convert it to non-group. Child Tasks exist.'));
+ frm.reload_doc();
+ }
+ }
+ })
+ },
+
validate: function(frm) {
frm.doc.project && frappe.model.remove_from_locals("Project",
frm.doc.project);
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index e4ab5a7..41950a3 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -3,7 +3,7 @@
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
- "autoname": "TASK.#####",
+ "autoname": "field:subject",
"beta": 0,
"creation": "2013-01-29 19:25:50",
"custom": 0,
@@ -30,9 +30,8 @@
"label": "Subject",
"length": 0,
"no_copy": 0,
- "oldfieldname": "subject",
- "oldfieldtype": "Data",
"permlevel": 0,
+ "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -78,6 +77,37 @@
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
+ "bold": 1,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "is_group",
+ "fieldtype": "Check",
+ "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": "Is Group",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@@ -173,9 +203,42 @@
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
- "bold": 0,
+ "bold": 1,
"collapsible": 0,
"columns": 0,
+ "fieldname": "parent_task",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Parent Task",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Task",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 1,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "collapsible_depends_on": "",
+ "columns": 0,
+ "depends_on": "",
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
@@ -205,6 +268,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "exp_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -237,6 +301,7 @@
"collapsible": 0,
"columns": 0,
"default": "0",
+ "depends_on": "",
"description": "",
"fieldname": "expected_time",
"fieldtype": "Float",
@@ -269,6 +334,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "task_weight",
"fieldtype": "Float",
"hidden": 0,
@@ -328,6 +394,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "exp_end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -359,6 +426,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "progress",
"fieldtype": "Percent",
"hidden": 0,
@@ -389,6 +457,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "is_milestone",
"fieldtype": "Check",
"hidden": 0,
@@ -418,7 +487,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
@@ -449,6 +520,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -481,7 +553,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -512,6 +586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "depends_on",
"fieldtype": "Table",
"hidden": 0,
@@ -543,6 +618,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "depends_on_tasks",
"fieldtype": "Data",
"hidden": 1,
@@ -572,7 +648,9 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
+ "collapsible_depends_on": "",
"columns": 0,
+ "depends_on": "",
"description": "",
"fieldname": "actual",
"fieldtype": "Section Break",
@@ -606,6 +684,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "act_start_date",
"fieldtype": "Date",
"hidden": 0,
@@ -638,6 +717,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
+ "depends_on": "",
"description": "",
"fieldname": "actual_time",
"fieldtype": "Float",
@@ -699,6 +779,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "act_end_date",
"fieldtype": "Date",
"hidden": 0,
@@ -730,6 +811,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "section_break_17",
"fieldtype": "Section Break",
"hidden": 0,
@@ -759,6 +841,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -791,6 +874,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_expense_claim",
"fieldtype": "Currency",
"hidden": 0,
@@ -851,6 +935,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "depends_on": "",
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -1025,6 +1110,96 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "lft",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "lft",
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rgt",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "rgt",
+ "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,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "old_parent",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "ignore_user_permissions": 1,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Old Parent",
+ "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,
+ "unique": 0
}
],
"has_web_view": 0,
@@ -1039,7 +1214,7 @@
"istable": 0,
"max_attachments": 5,
"menu_index": 0,
- "modified": "2017-05-23 11:28:28.161600",
+ "modified": "2017-10-06 03:57:37.901446",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 52ae132..5b1bcaf 100644
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -5,13 +5,14 @@
import frappe, json
from frappe.utils import getdate, date_diff, add_days, cstr
-from frappe import _
-
-from frappe.model.document import Document
+from frappe import _, throw
+from frappe.utils.nestedset import NestedSet, rebuild_tree
class CircularReferenceError(frappe.ValidationError): pass
-class Task(Document):
+class Task(NestedSet):
+ nsm_parent_field = 'parent_task'
+
def get_feed(self):
return '{0}: {1}'.format(_(self.status), self.subject)
@@ -59,7 +60,11 @@
depends_on_tasks += d.task + ","
self.depends_on_tasks = depends_on_tasks
+ def update_nsm_model(self):
+ frappe.utils.nestedset.update_nsm(self)
+
def on_update(self):
+ self.update_nsm_model()
self.check_recursion()
self.reschedule_dependent_tasks()
self.update_project()
@@ -105,16 +110,20 @@
frappe.throw(_("Circular Reference Error"), CircularReferenceError)
if b[0]:
task_list.append(b[0])
+
if count == 15:
break
def reschedule_dependent_tasks(self):
end_date = self.exp_end_date or self.act_end_date
if end_date:
- for task_name in frappe.db.sql("""select name from `tabTask` as parent where parent.project = %(project)s and parent.name in \
- (select parent from `tabTask Depends On` as child where child.task = %(task)s and child.project = %(project)s)""",
- {'project': self.project, 'task':self.name }, as_dict=1):
-
+ for task_name in frappe.db.sql("""
+ select name from `tabTask` as parent
+ where parent.project = %(project)s
+ and parent.name in (
+ select parent from `tabTask Depends On` as child
+ where child.task = %(task)s and child.project = %(project)s)
+ """, {'project': self.project, 'task':self.name }, as_dict=1):
task = frappe.get_doc("Task", task_name.name)
if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open":
task_duration = date_diff(task.exp_end_date, task.exp_start_date)
@@ -128,6 +137,17 @@
if project_user:
return True
+ def on_trash(self):
+ if check_if_child_exists(self.name):
+ throw(_("Child Task exists for this Task. You can not delete this Task."))
+
+ self.update_nsm_model()
+
+@frappe.whitelist()
+def check_if_child_exists(name):
+ return frappe.db.sql("""select name from `tabTask`
+ where parent_task = %s""", name)
+
@frappe.whitelist()
def get_events(start, end, filters=None):
"""Returns events for Gantt / Calendar view rendering.
@@ -177,4 +197,48 @@
and exp_end_date < CURDATE()
and `status` not in ('Closed', 'Cancelled')""")
+@frappe.whitelist()
+def get_children():
+ doctype = frappe.local.form_dict.get('doctype')
+ parent_field = 'parent_' + doctype.lower().replace(' ', '_')
+ parent = frappe.form_dict.get("parent") or ""
+
+ if parent == "task":
+ parent = ""
+
+ tasks = frappe.db.sql("""select name as value,
+ is_group as expandable
+ from `tab{doctype}`
+ where docstatus < 2
+ and ifnull(`{parent_field}`,'') = %s
+ order by name""".format(doctype=frappe.db.escape(doctype),
+ parent_field=frappe.db.escape(parent_field)), (parent), as_dict=1)
+
+ # return tasks
+ return tasks
+
+@frappe.whitelist()
+def add_node():
+ from frappe.desk.treeview import make_tree_args
+ args = frappe.form_dict
+ args.update({
+ "name_field": "subject"
+ })
+ args = make_tree_args(**args)
+
+ if args.parent_task == 'task':
+ args.parent_task = None
+
+ frappe.get_doc(args).insert()
+
+@frappe.whitelist()
+def add_multiple_tasks(data, parent):
+ data = json.loads(data)['tasks']
+ tasks = data.split('\n')
+ new_doc = {'doctype': 'Task', 'parent_task': parent}
+
+ for d in tasks:
+ new_doc['subject'] = d
+ new_task = frappe.get_doc(new_doc)
+ new_task.insert()
diff --git a/erpnext/projects/doctype/task/task_tree.js b/erpnext/projects/doctype/task/task_tree.js
new file mode 100644
index 0000000..f11c34f
--- /dev/null
+++ b/erpnext/projects/doctype/task/task_tree.js
@@ -0,0 +1,59 @@
+frappe.provide("frappe.treeview_settings");
+
+frappe.treeview_settings['Task'] = {
+ get_tree_nodes: "erpnext.projects.doctype.task.task.get_children",
+ add_tree_node: "erpnext.projects.doctype.task.task.add_node",
+ filters: [
+ {
+ fieldname: "task",
+ fieldtype:"Link",
+ options: "Task",
+ label: __("Task"),
+ get_query: function(){
+ return {
+ filters: [["Task", 'is_group', '=', 1]]
+ };
+ }
+ }
+ ],
+ title: "Task",
+ breadcrumb: "Projects",
+ get_tree_root: false,
+ root_label: "task",
+ ignore_fields:["parent_task"],
+ get_label: function(node) {
+ return node.data.value;
+ },
+ onload: function(me){
+ me.make_tree();
+ me.set_root = true;
+ },
+ toolbar: [
+ {
+ label:__("Add Multiple"),
+ condition: function(node) {
+ return node.expandable;
+ },
+ click: function(node) {
+ var d = new frappe.ui.Dialog({
+ 'fields': [
+ {'fieldname': 'tasks', 'label': 'Tasks', 'fieldtype': 'Text'},
+ ],
+ primary_action: function(){
+ d.hide();
+ return frappe.call({
+ method: "erpnext.projects.doctype.task.task.add_multiple_tasks",
+ args: {
+ data: d.get_values(),
+ parent: node.data.value
+ },
+ callback: function() { }
+ });
+ }
+ });
+ d.show();
+ }
+ }
+ ],
+ extend_toolbar: true
+};
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_records.json b/erpnext/projects/doctype/task/test_records.json
deleted file mode 100644
index 42ca0e7..0000000
--- a/erpnext/projects/doctype/task/test_records.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[
- {
- "status": "Open",
- "subject": "_Test Task",
- "name": "task001"
- },
- {
- "status": "Open",
- "subject": "_Test Task 1"
- },
- {
- "status": "Open",
- "subject": "_Test Task 2"
- }
-]
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 2e64b73..1d94618 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -5,137 +5,61 @@
import unittest
from frappe.utils import getdate, nowdate, add_days
-# test_records = frappe.get_test_records('Task')
-
from erpnext.projects.doctype.task.task import CircularReferenceError
class TestTask(unittest.TestCase):
def test_circular_reference(self):
+ task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
+ task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
+ task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
- task1 = frappe.new_doc('Task')
- task1.update({
- "status": "Open",
- "subject": "_Test Task 1",
- "project": "_Test Project",
- "exp_start_date": "2015-1-1",
- "exp_end_date": "2015-1-10"
- })
- task1.save()
-
- task2 = frappe.new_doc('Task')
- task2.update({
- "status": "Open",
- "subject": "_Test Task 2",
- "project": "_Test Project",
- "exp_start_date": "2015-1-11",
- "exp_end_date": "2015-1-15",
- "depends_on":[
- {
- "task": task1.name
- }
- ]
- })
- task2.save()
-
- task3 = frappe.new_doc('Task')
- task3.update({
- "status": "Open",
- "subject": "_Test Task 2",
- "project": "_Test Project",
- "exp_start_date": "2015-1-11",
- "exp_end_date": "2015-1-15",
- "depends_on":[
- {
- "task": task2.name
- }
- ]
- })
- task3.save()
-
+ task1.reload()
task1.append("depends_on", {
"task": task3.name
})
+
self.assertRaises(CircularReferenceError, task1.save)
task1.set("depends_on", [])
task1.save()
- task4 = frappe.new_doc('Task')
- task4.update({
- "status": "Open",
- "subject": "_Test Task 1",
- "exp_start_date": "2015-1-1",
- "exp_end_date": "2015-1-15",
- "depends_on":[
- {
- "task": task1.name
- }
- ]
- })
- task4.save()
+ task4 = create_task("_Test Task 4", nowdate(), add_days(nowdate(), 15), task1.name)
task3.append("depends_on", {
"task": task4.name
})
def test_reschedule_dependent_task(self):
- task1 = frappe.new_doc('Task')
- task1.update({
- "status": "Open",
- "subject": "_Test Task 1",
- "project": "_Test Project",
- "exp_start_date": "2015-1-1",
- "exp_end_date": "2015-1-10"
- })
- task1.save()
+ task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
- task2 = frappe.new_doc('Task')
- task2.update({
- "status": "Open",
- "subject": "_Test Task 2",
- "project": "_Test Project",
- "exp_start_date": "2015-1-11",
- "exp_end_date": "2015-1-15",
- "depends_on":[
- {
- "task": task1.name,
- "project": "_Test Project"
- }
- ]
- })
+ task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
+ task2.get("depends_on")[0].project = "_Test Project"
task2.save()
- task3 = frappe.new_doc('Task')
- task3.update({
- "status": "Open",
- "subject": "_Test Task 3",
- "project": "_Test Project",
- "exp_start_date": "2015-1-16",
- "exp_end_date": "2015-1-18",
- "depends_on":[
- {
- "task": task2.name,
- "project": "_Test Project"
- }
- ]
- })
+ task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
+ task3.get("depends_on")[0].project = "_Test Project"
task3.save()
task1.update({
- "exp_end_date": "2015-1-20"
+ "exp_end_date": add_days(nowdate(), 20)
})
task1.save()
- self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
- self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"),
+ getdate(add_days(nowdate(), 21)))
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"),
+ getdate(add_days(nowdate(), 25)))
- self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
- self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"),
+ getdate(add_days(nowdate(), 26)))
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"),
+ getdate(add_days(nowdate(), 30)))
def test_close_assignment(self):
- task = frappe.new_doc("Task")
- task.subject = "Test Close Assignment"
- task.insert()
+ if not frappe.db.exists("Task", "Test Close Assignment"):
+ task = frappe.new_doc("Task")
+ task.subject = "Test Close Assignment"
+ task.insert()
def assign():
from frappe.desk.form import assign_to
@@ -147,8 +71,10 @@
})
def get_owner_and_status():
- return frappe.db.get_value("ToDo", filters={"reference_type": task.doctype, "reference_name": task.name,
- "description": "Close this task"}, fieldname=("owner", "status"), as_dict=True)
+ return frappe.db.get_value("ToDo",
+ filters={"reference_type": task.doctype, "reference_name": task.name,
+ "description": "Close this task"},
+ fieldname=("owner", "status"), as_dict=True)
assign()
todo = get_owner_and_status()
@@ -164,16 +90,29 @@
self.assertEquals(todo.status, "Closed")
def test_overdue(self):
- task = frappe.get_doc({
- "doctype":"Task",
- "subject": "Testing Overdue",
- "status": "Open",
- "exp_end_date": add_days(nowdate(), -1)
- })
-
- task.insert()
+ task = create_task("Testing Overdue", add_days(nowdate(), -10), add_days(nowdate(), -5))
from erpnext.projects.doctype.task.task import set_tasks_as_overdue
set_tasks_as_overdue()
self.assertEquals(frappe.db.get_value("Task", task.name, "status"), "Overdue")
+
+def create_task(subject, start=None, end=None, depends_on=None, project=None):
+ if not frappe.db.exists("Task", subject):
+ task = frappe.new_doc('Task')
+ task.status = "Open"
+ task.subject = subject
+ task.exp_start_date = start or nowdate()
+ task.exp_end_date = end or nowdate()
+ task.project = project or "_Test Project"
+ task.save()
+ else:
+ task = frappe.get_doc("Task", subject)
+
+ if depends_on:
+ task.append("depends_on", {
+ "task": depends_on
+ })
+ task.save()
+
+ return task
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/test_task.js b/erpnext/projects/doctype/task/tests/test_task.js
similarity index 100%
rename from erpnext/projects/doctype/task/test_task.js
rename to erpnext/projects/doctype/task/tests/test_task.js
diff --git a/erpnext/projects/doctype/task/tests/test_task_tree.js b/erpnext/projects/doctype/task/tests/test_task_tree.js
new file mode 100644
index 0000000..9cbcf85
--- /dev/null
+++ b/erpnext/projects/doctype/task/tests/test_task_tree.js
@@ -0,0 +1,99 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Task Tree", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(5);
+
+ frappe.run_serially([
+ // insert a new Task
+ () => frappe.set_route('Tree', 'Task'),
+ () => frappe.timeout(0.5),
+
+ // Checking adding child without selecting any Node
+ () => frappe.tests.click_button('New'),
+ () => frappe.timeout(0.5),
+ () => {assert.equal($(`.msgprint`).text(), "Select a group node first.", "Error message success");},
+ () => frappe.tests.click_button('Close'),
+ () => frappe.timeout(0.5),
+
+ // Creating child nodes
+ () => frappe.tests.click_link('task'),
+ () => frappe.map_group.make('Test-1'),
+ () => frappe.map_group.make('Test-2'),
+ () => frappe.map_group.make('Test-3', 1),
+ () => frappe.timeout(1),
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.map_group.make('Test-4', 0),
+
+ // Checking Edit button
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-1'),
+ () => frappe.tests.click_button('Edit'),
+ () => frappe.timeout(0.5),
+ () => {assert.deepEqual(frappe.get_route(), ["Form", "Task", "Test-1"], "Edit route checks");},
+
+ // Deleting child Node
+ () => frappe.set_route('Tree', 'Task'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-1'),
+ () => frappe.tests.click_button('Delete'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_button('Yes'),
+
+ // Deleting Group Node that has child nodes in it
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.tests.click_button('Delete'),
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_button('Yes'),
+ () => frappe.timeout(1),
+ () => {assert.equal(cur_dialog.title, 'Message', 'Error thrown correctly');},
+ () => frappe.tests.click_button('Close'),
+
+ // Renaming Child node
+ () => frappe.timeout(0.5),
+ () => frappe.tests.click_link('Test-2'),
+ () => frappe.tests.click_button('Rename'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('new_name', 'Test-5'),
+ () => frappe.timeout(1.5),
+ () => cur_dialog.get_primary_btn().click(),
+ () => frappe.timeout(1),
+ () => {assert.equal($(`a:contains("Test-5"):visible`).length, 1, 'Rename successfull');},
+
+ // Add multiple child tasks
+ () => frappe.tests.click_link('Test-3'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Add Multiple'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('tasks', 'Test-6\nTest-7'),
+ () => frappe.timeout(0.5),
+ () => frappe.click_button('Submit'),
+ () => frappe.timeout(2),
+ () => frappe.click_button('Expand All'),
+ () => frappe.timeout(1),
+ () => {
+ let count = $(`a:contains("Test-6"):visible`).length + $(`a:contains("Test-7"):visible`).length;
+ assert.equal(count, 2, "Multiple Tasks added successfully");
+ },
+
+ () => done()
+ ]);
+});
+
+frappe.map_group = {
+ make:function(subject, is_group = 0){
+ return frappe.run_serially([
+ () => frappe.click_button('Add Child'),
+ () => frappe.timeout(1),
+ () => cur_dialog.set_value('is_group', is_group),
+ () => cur_dialog.set_value('subject', subject),
+ () => frappe.click_button('Create New'),
+ () => frappe.timeout(1.5)
+ ]);
+ }
+};
diff --git a/erpnext/public/js/pos/pos_selected_item.html b/erpnext/public/js/pos/pos_selected_item.html
index 6f2772b..085e048 100644
--- a/erpnext/public/js/pos/pos_selected_item.html
+++ b/erpnext/public/js/pos/pos_selected_item.html
@@ -8,7 +8,7 @@
<input type="tel" class="form-control cell" disabled value="{%= price_list_rate %}"/>
</div>
<div class="pos-list-row">
- <div class="cell">{{ __("Discount") }}:</div>
+ <div class="cell">{{ __("Discount") }}: %</div>
<input type="tel" class="form-control cell pos-item-disc" value="{%= discount_percentage %}">
</div>
<div class="pos-list-row">
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index efb04d1..47cda19 100644
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -38,7 +38,7 @@
},
stale_rate_allowed: () => {
- return cint(frappe.boot.sysdefaults.allow_stale) || 1;
+ return cint(frappe.boot.sysdefaults.allow_stale);
},
setup_serial_no: function() {
diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.py b/erpnext/schools/doctype/student_applicant/student_applicant.py
index 465b4e4..d0db658 100644
--- a/erpnext/schools/doctype/student_applicant/student_applicant.py
+++ b/erpnext/schools/doctype/student_applicant/student_applicant.py
@@ -13,9 +13,12 @@
from frappe.model.naming import set_name_by_naming_series
if self.student_admission:
if self.program:
+ # set the naming series from the student admission if provided.
student_admission = get_student_admission_data(self.student_admission, self.program)
if student_admission:
naming_series = student_admission.get("applicant_naming_series")
+ else:
+ naming_series = None
else:
frappe.throw(_("Select the program first"))
@@ -40,15 +43,16 @@
def validation_from_student_admission(self):
student_admission = get_student_admission_data(self.student_admission, self.program)
- if student_admission:
- if ((
- student_admission.minimum_age
- and getdate(student_admission.minimum_age) > getdate(self.date_of_birth)
- ) or (
- student_admission.maximum_age
- and getdate(student_admission.maximum_age) < getdate(self.date_of_birth)
- )):
- frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+ # different validation for minimum and maximum age so that either min/max can also work independently.
+ if student_admission and student_admission.minimum_age and \
+ getdate(student_admission.minimum_age) < getdate(self.date_of_birth):
+ frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
+ if student_admission and student_admission.maximum_age and \
+ getdate(student_admission.maximum_age) > getdate(self.date_of_birth):
+ frappe.throw(_("Not eligible for the admission in this program as per DOB"))
+
def on_payment_authorized(self, *args, **kwargs):
self.db_set('paid', 1)
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index fa21a3d..747a3c0 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -477,9 +477,11 @@
target.qty = flt(source.qty) - flt(source.delivered_qty)
item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
- target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
- or item.selling_cost_center \
- or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
+
+ if item:
+ target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
+ or item.selling_cost_center \
+ or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
target_doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py
index 2080224..e940f94 100644
--- a/erpnext/startup/boot.py
+++ b/erpnext/startup/boot.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
+from frappe.utils import cint
def boot_session(bootinfo):
"""boot session - send website info if guest"""
@@ -19,8 +20,8 @@
'territory')
bootinfo.sysdefaults.customer_group = frappe.db.get_single_value('Selling Settings',
'customer_group')
- bootinfo.sysdefaults.allow_stale = frappe.db.get_single_value('Accounts Settings',
- 'allow_stale') or 1
+ bootinfo.sysdefaults.allow_stale = cint(frappe.db.get_single_value('Accounts Settings',
+ 'allow_stale'))
bootinfo.notification_settings = frappe.get_doc("Notification Control",
"Notification Control")
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 4b4f15b..bf8eaba 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -57,7 +57,7 @@
if not self.description:
self.description = self.item_name
- if self.is_sales_item and not self.is_item_from_hub:
+ if self.is_sales_item and not self.get('is_item_from_hub'):
self.publish_in_hub = 1
def after_insert(self):
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index ab73b34..6a18f11 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -32,7 +32,7 @@
if email_id:
if not self.lead:
self.lead = frappe.db.get_value("Lead", {"email_id": email_id})
- if not self.contact:
+ if not self.contact and not self.customer:
self.contact = frappe.db.get_value("Contact", {"email_id": email_id})
if self.contact:
diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt
index e7de604..24858f3 100644
--- a/erpnext/tests/ui/tests.txt
+++ b/erpnext/tests/ui/tests.txt
@@ -133,3 +133,4 @@
erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.js
erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.js
erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
+erpnext/projects/doctype/task/tests/test_task_tree.js