Optionally allow overlap on Timesheet #12893 (#13018)
* give user option to ignore time overlap validation
* move settings to new Projects Settings
* clean up - remove trailing space
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 30b1eed..b19bb2b 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -42,6 +42,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -72,6 +73,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -103,6 +105,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -132,6 +135,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -163,6 +167,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -193,6 +198,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -223,6 +229,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -254,6 +261,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -285,6 +293,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -315,6 +324,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -345,6 +355,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -374,6 +385,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -404,6 +416,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -434,6 +447,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -465,6 +479,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
},
{
@@ -497,6 +512,7 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0
}
],
@@ -511,7 +527,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-01-05 15:26:10.357085",
+ "modified": "2018-02-21 16:47:38.043115",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/projects/doctype/projects_settings/__init__.py b/erpnext/projects/doctype/projects_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/__init__.py
diff --git a/erpnext/projects/doctype/projects_settings/projects_settings.js b/erpnext/projects/doctype/projects_settings/projects_settings.js
new file mode 100644
index 0000000..9902b83
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/projects_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Projects Settings', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/erpnext/projects/doctype/projects_settings/projects_settings.json b/erpnext/projects/doctype/projects_settings/projects_settings.json
new file mode 100644
index 0000000..7fa1558
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/projects_settings.json
@@ -0,0 +1,189 @@
+{
+ "allow_copy": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2018-02-21 16:42:13.882879",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "timesheet_sb",
+ "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": "Timesheets",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "ignore_workstation_time_overlap",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Ignore Workstation Time Overlap",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "ignore_user_time_overlap",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Ignore User Time Overlap",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "0",
+ "fieldname": "ignore_employee_time_overlap",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Ignore Employee Time Overlap",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 0,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2018-02-21 16:42:42.357209",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Projects Settings",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 0,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1,
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/projects_settings/projects_settings.py b/erpnext/projects/doctype/projects_settings/projects_settings.py
new file mode 100644
index 0000000..9dcac14
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/projects_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class ProjectsSettings(Document):
+ pass
diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.js b/erpnext/projects/doctype/projects_settings/test_projects_settings.js
new file mode 100644
index 0000000..f6feaa4
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/test_projects_settings.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Projects Settings", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Projects Settings
+ () => frappe.tests.make('Projects Settings', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.py b/erpnext/projects/doctype/projects_settings/test_projects_settings.py
new file mode 100644
index 0000000..d671da7
--- /dev/null
+++ b/erpnext/projects/doctype/projects_settings/test_projects_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestProjectsSettings(unittest.TestCase):
+ pass
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 2588d56..42c2308 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -77,6 +77,46 @@
self.assertEquals(ts.per_billed, 100)
self.assertEquals(ts.time_logs[0].sales_invoice, sales_invoice.name)
+ def test_timesheet_time_overlap(self):
+ settings = frappe.get_single('Projects Settings')
+ initial_setting = settings.ignore_employee_time_overlap
+ settings.ignore_employee_time_overlap = 0
+ settings.save()
+
+ update_activity_type("_Test Activity Type")
+ timesheet = frappe.new_doc("Timesheet")
+ timesheet.employee = "_T-Employee-0001"
+ timesheet.append(
+ 'time_logs',
+ {
+ "billable": 1,
+ "activity_type": "_Test Activity Type",
+ "from_type": now_datetime(),
+ "hours": 3,
+ "company": "_Test Company"
+ }
+ )
+ timesheet.append(
+ 'time_logs',
+ {
+ "billable": 1,
+ "activity_type": "_Test Activity Type",
+ "from_type": now_datetime(),
+ "hours": 3,
+ "company": "_Test Company"
+ }
+ )
+
+ self.assertRaises(frappe.ValidationError, timesheet.save)
+
+ settings.ignore_employee_time_overlap = 1
+ settings.save()
+ timesheet.save() # should not throw an error
+
+ settings.ignore_employee_time_overlap = initial_setting
+ settings.save()
+
+
def make_salary_structure(employee):
name = frappe.db.get_value('Salary Structure Employee', {'employee': employee}, 'parent')
if name:
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index 3d94b5c..57a51d2 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -180,14 +180,16 @@
self.validate_overlap(data)
def validate_overlap(self, data):
+ settings = frappe.get_single('Projects Settings')
if self.production_order:
- self.validate_overlap_for("workstation", data, data.workstation)
+ self.validate_overlap_for("workstation", data, data.workstation, settings.ignore_workstation_time_overlap)
else:
- self.validate_overlap_for("user", data, self.user)
- self.validate_overlap_for("employee", data, self.employee)
+ self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
+ self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
- def validate_overlap_for(self, fieldname, args, value):
- if not value: return
+ def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
+ if not value or ignore_validation:
+ return
existing = self.get_overlap_for(fieldname, args, value)
if existing: