Merge pull request #12587 from shreyashah115/leave-workflow

Refactor Leave Application
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
index 504478a..e59c3ee 100644
--- a/erpnext/demo/user/hr.py
+++ b/erpnext/demo/user/hr.py
@@ -192,7 +192,7 @@
 				"attendance_date": attendance_date
 			})
 			leave = frappe.db.sql("""select name from `tabLeave Application`
-				where employee = %s and %s between from_date and to_date and status = 'Approved'
+				where employee = %s and %s between from_date and to_date and workflow_state = 'Approved'
 				and docstatus = 1""", (employee.name, attendance_date))
 
 			if leave:
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 458b2dd..fd7344a 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -21,7 +21,7 @@
 
 	def check_leave_record(self):
 		leave_record = frappe.db.sql("""select leave_type, half_day from `tabLeave Application`
-			where employee = %s and %s between from_date and to_date and status = 'Approved'
+			where employee = %s and %s between from_date and to_date and workflow_state = 'Approved'
 			and docstatus = 1""", (self.employee, self.attendance_date), as_dict=True)
 		if leave_record:
 			if leave_record[0].half_day:
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 2eb155d..c2d8326 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -29,7 +29,7 @@
 
 	refresh: function(frm) {
 		if (frm.is_new()) {
-			frm.set_value("status", "Open");
+			frm.set_value("workflow_state", "Open");
 			frm.trigger("calculate_total_days");
 		}
 	},
diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json
index 7afbc4d..88b5a55 100644
--- a/erpnext/hr/doctype/leave_application/leave_application.json
+++ b/erpnext/hr/doctype/leave_application/leave_application.json
@@ -50,66 +50,6 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "default": "Open", 
-   "fieldname": "status", 
-   "fieldtype": "Select", 
-   "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": "Status", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "Open\nApproved\nRejected", 
-   "permlevel": 1, 
-   "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, 
-   "fieldname": "column_break_12", 
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fieldname": "leave_type", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -796,7 +736,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 3, 
- "modified": "2017-06-13 14:28:52.426044", 
+ "modified": "2018-01-22 12:10:40.757274", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Leave Application", 
@@ -887,9 +827,9 @@
   {
    "amend": 1, 
    "apply_user_permissions": 0, 
-   "cancel": 0, 
+   "cancel": 1, 
    "create": 0, 
-   "delete": 0, 
+   "delete": 1, 
    "email": 1, 
    "export": 0, 
    "if_owner": 0, 
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index bfa163a..5be44af 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -21,9 +21,10 @@
 from frappe.model.document import Document
 class LeaveApplication(Document):
 	def get_feed(self):
-		return _("{0}: From {0} of type {1}").format(self.status, self.employee_name, self.leave_type)
+		return _("{0}: From {0} of type {1}").format(self.workflow_state, self.employee_name, self.leave_type)
 
 	def validate(self):
+		if self.get("__islocal"): self.workflow_state = 'Open'
 		if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name):
 			self.previous_doc = frappe.get_value(self.doctype, self.name, "leave_approver", as_dict=True)
 		else:
@@ -43,18 +44,16 @@
 
 	def on_update(self):
 		if (not self.previous_doc and self.leave_approver) or (self.previous_doc and \
-				self.status == "Open" and self.previous_doc.leave_approver != self.leave_approver):
+				self.workflow_state == "Open" and self.previous_doc.leave_approver != self.leave_approver):
 			# notify leave approver about creation
 			self.notify_leave_approver()
 
 	def on_submit(self):
-		if self.status == "Open":
-			frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
 
 		self.validate_back_dated_application()
 
 		# notify leave applier about approval
-		self.notify_employee(self.status)
+		self.notify_employee(self.workflow_state)
 
 	def on_cancel(self):
 		# notify leave applier about cancellation
@@ -71,10 +70,10 @@
 				frappe.throw(_("Half Day Date should be between From Date and To Date"))
 
 		if not is_lwp(self.leave_type):
-			self.validate_dates_acorss_allocation()
+			self.validate_dates_across_allocation()
 			self.validate_back_dated_application()
 
-	def validate_dates_acorss_allocation(self):
+	def validate_dates_across_allocation(self):
 		def _get_leave_alloction_record(date):
 			allocation = frappe.db.sql("""select name from `tabLeave Allocation`
 				where employee=%s and leave_type=%s and docstatus=1
@@ -89,7 +88,7 @@
 			frappe.throw(_("Application period cannot be outside leave allocation period"))
 
 		elif allocation_based_on_from_date != allocation_based_on_to_date:
-			frappe.throw(_("Application period cannot be across two alocation records"))
+			frappe.throw(_("Application period cannot be across two allocation records"))
 
 	def validate_back_dated_application(self):
 		future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
@@ -129,7 +128,7 @@
 		block_dates = get_applicable_block_dates(self.from_date, self.to_date,
 			self.employee, self.company)
 
-		if block_dates and self.status == "Approved":
+		if block_dates and self.workflow_state == "Approved":
 			frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
 
 	def validate_balance_leaves(self):
@@ -144,7 +143,7 @@
 				self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date,
 					consider_all_leaves_in_the_allocation_period=True)
 
-				if self.status != "Rejected" and self.leave_balance < self.total_leave_days:
+				if self.workflow_state != "Rejected" and self.leave_balance < self.total_leave_days:
 					if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
 						frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}")
 							.format(self.leave_type))
@@ -161,7 +160,7 @@
 			select
 				name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
 			from `tabLeave Application`
-			where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
+			where employee = %(employee)s and docstatus < 2 and workflow_state in ("Open", "Approved")
 			and to_date >= %(from_date)s and from_date <= %(to_date)s
 			and name != %(name)s""", {
 				"employee": self.employee,
@@ -182,16 +181,16 @@
 				self.throw_overlap_error(d)
 
 	def throw_overlap_error(self, d):
-		msg = _("Employee {0} has already applied for {1} between {2} and {3}").format(self.employee,
+		msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
 			d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
-			+ """ <br><b><a href="#Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
+			+ """ <b><a href="#Form/Leave Application/{0}">{0}</a></b>""".format(d["name"])
 		frappe.throw(msg, OverlapError)
 
 	def get_total_leaves_on_half_day(self):
 		leave_count_on_half_day_date = frappe.db.sql("""select count(name) from `tabLeave Application`
 			where employee = %(employee)s
 			and docstatus < 2
-			and status in ("Open", "Approved")
+			and workflow_state in ("Open", "Approved")
 			and half_day = 1
 			and half_day_date = %(half_day_date)s
 			and name != %(name)s""", {
@@ -232,7 +231,7 @@
 			frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
 				AttendanceAlreadyMarkedError)
 
-	def notify_employee(self, status):
+	def notify_employee(self, workflow_state):
 		employee = frappe.get_doc("Employee", self.employee)
 		if not employee.user_id:
 			return
@@ -247,14 +246,14 @@
 			message += "Leave Type: {leave_type}".format(leave_type=self.leave_type)+"<br>"
 			message += "From Date: {from_date}".format(from_date=self.from_date)+"<br>"
 			message += "To Date: {to_date}".format(to_date=self.to_date)+"<br>"
-			message += "Status: {status}".format(status=_(status))
+			message += "Status: {workflow_state}".format(workflow_state=_(workflow_state))
 			return message
 
 		self.notify({
 			# for post in messages
 			"message": _get_message(url=True),
 			"message_to": employee.user_id,
-			"subject": (_("Leave Application") + ": %s - %s") % (self.name, _(status))
+			"subject": (_("Leave Application") + ": %s - %s") % (self.name, _(workflow_state))
 		})
 
 	def notify_leave_approver(self):
@@ -372,7 +371,7 @@
 		select employee, leave_type, from_date, to_date, total_leave_days
 		from `tabLeave Application`
 		where employee=%(employee)s and leave_type=%(leave_type)s
-			and status="Approved" and docstatus=1
+			and workflow_state="Approved" and docstatus=1
 			and (from_date between %(from_date)s and %(to_date)s
 				or to_date between %(from_date)s and %(to_date)s
 				or (from_date < %(from_date)s and to_date > %(to_date)s))
@@ -472,11 +471,11 @@
 
 def add_leaves(events, start, end, match_conditions=None):
 	query = """select name, from_date, to_date, employee_name, half_day,
-		status, employee, docstatus
+		workflow_state, employee, docstatus
 		from `tabLeave Application` where
 		from_date <= %(end)s and to_date >= %(start)s <= to_date
 		and docstatus < 2
-		and status!="Rejected" """
+		and workflow_state!="Rejected" """
 	if match_conditions:
 		query += match_conditions
 
@@ -486,7 +485,7 @@
 			"doctype": "Leave Application",
 			"from_date": d.from_date,
 			"to_date": d.to_date,
-			"status": d.status,
+			"workflow_state": d.workflow_state,
 			"title": cstr(d.employee_name) + \
 				(d.half_day and _(" (Half Day)") or ""),
 			"docstatus": d.docstatus
diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
index d55c23b..b06b40f 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
@@ -7,7 +7,7 @@
 		"end": "to_date",
 		"id": "name",
 		"title": "title",
-		"status": "status",
+		"workflow_state": "workflow_state",
 	},
 	options: {
 		header: {
diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js
index 966d1aa..7798ae7 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_list.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_list.js
@@ -1,8 +1,7 @@
 frappe.listview_settings['Leave Application'] = {
-	add_fields: ["status", "leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
-	filters:[["status","!=", "Rejected"]],
+	add_fields: ["workflow_state", "leave_type", "employee", "employee_name", "total_leave_days", "from_date", "to_date"],
 	get_indicator: function(doc) {
-		return [__(doc.status), frappe.utils.guess_colour(doc.status),
-			"status,=," + doc.status];
+		return [__(doc.workflow_state), frappe.utils.guess_colour(doc.workflow_state),
+			"workflow_state,=," + doc.workflow_state];
 	}
 };
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js
index 6028405..6d51b71 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.js
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.js
@@ -20,29 +20,24 @@
 				{follow_via_email: 0}
 			]);
 		},
+
 		() => frappe.timeout(1),
-		// check calculated total leave days
-		() => assert.ok(!cur_frm.doc.docstatus,
-			"leave application not submitted with status as open"),
-		() => cur_frm.set_value("status", "Approved"),	// approve the application [as administrator]
-		() => frappe.timeout(0.5),
-		// save form
-		() => cur_frm.save(),
-		() => frappe.timeout(1),
-		() => cur_frm.savesubmit(),
-		() => frappe.timeout(1),
+		() => frappe.click_button('Actions'),
+		() => frappe.click_link('Approve'), // approve the application [as administrator]
 		() => frappe.click_button('Yes'),
 		() => frappe.timeout(1),
 		() => assert.ok(cur_frm.doc.docstatus,
 			"leave application submitted after approval"),
+
 		// check auto filled posting date [today]
+
 		() => assert.equal(today_date, cur_frm.doc.posting_date,
 			"posting date correctly set"),
 		() => frappe.set_route("List", "Leave Application", "List"),
 		() => frappe.timeout(1),
-		// check approved application in list
-		() => assert.deepEqual(["Test Employee 1", "Approved"], [cur_list.data[0].employee_name, cur_list.data[0].status],
-			"leave for correct employee is approved"),
+		// // check approved application in list
+		() => assert.deepEqual(["Test Employee 1", "Approved"], [cur_list.data[0].employee_name, cur_list.data[0].workflow_state]),
+		// 	"leave for correct employee is approved"),
 		() => done()
 	]);
 });
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 0ec3efb..b2f6054 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -103,7 +103,7 @@
 
 		application = self.get_application(_test_records[0])
 		application.insert()
-		application.status = "Approved"
+		application.workflow_state = "Approved"
 		self.assertRaises(LeaveDayBlockedError, application.submit)
 
 		frappe.set_user("test1@example.com")
@@ -257,7 +257,7 @@
 		application.insert()
 
 		frappe.set_user("test@example.com")
-		application.status = "Approved"
+		application.workflow_state = "Approved"
 
 		# clear permlevel access cache on change user
 		del application._has_access_to
@@ -297,7 +297,7 @@
 
 		# submit leave application by Leave Approver
 		frappe.set_user("test1@example.com")
-		application.status = "Approved"
+		application.workflow_state = "Approved"
 		del application._has_access_to
 		application.submit()
 		self.assertEqual(frappe.db.get_value("Leave Application", application.name,
@@ -339,7 +339,7 @@
 		application.insert()
 		frappe.set_user("test1@example.com")
 		del application._has_access_to
-		application.status = "Approved"
+		application.workflow_state = "Approved"
 
 		from erpnext.hr.doctype.leave_application.leave_application import LeaveApproverIdentityError
 		self.assertRaises(LeaveApproverIdentityError, application.submit)
@@ -364,7 +364,7 @@
 
 		# change to valid leave approver and try to submit leave application
 		frappe.set_user("test2@example.com")
-		application.status = "Approved"
+		application.workflow_state = "Approved"
 		del application._has_access_to
 		application.submit()
 		self.assertEqual(frappe.db.get_value("Leave Application", application.name,
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index a474569..878c6b4 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -300,7 +300,7 @@
 				where t2.name = t1.leave_type
 				and t2.is_lwp = 1
 				and t1.docstatus = 1
-				and t1.status = 'Approved'
+				and t1.workflow_state = 'Approved'
 				and t1.employee = %(employee)s
 				and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
 				WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ff29d47..ed051b0 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -489,3 +489,4 @@
 erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france
 erpnext.patches.v10_0.update_assessment_plan
 erpnext.patches.v10_0.update_assessment_result
+erpnext.patches.v10_0.workflow_leave_application
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/workflow_leave_application.py b/erpnext/patches/v10_0/workflow_leave_application.py
new file mode 100644
index 0000000..5db5dd9
--- /dev/null
+++ b/erpnext/patches/v10_0/workflow_leave_application.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc("hr", "doctype", "leave_application")
+	frappe.reload_doc("workflow", "doctype", "workflow")
+
+	if not frappe.db.exists("Workflow State", "Open"):
+		frappe.get_doc({
+			'doctype': 'Workflow State',
+			'workflow_state_name': 'Open',
+			'style': 'Warning'
+		}).insert(ignore_permissions=True)
+
+	frappe.get_doc({
+		'doctype': 'Workflow',
+		'workflow_name': 'Leave Approval',
+		'document_type': 'Leave Application',
+		'is_active': 1,
+		'workflow_state_field': 'workflow_state',
+		'states': [{
+			"state": 'Open',
+			"doc_status": 0,
+			"allow_edit": 'Employee'
+		}, {
+			"state": 'Approved',
+			"doc_status": 1,
+			"allow_edit": 'Leave Approver'
+		}, {
+			"state": 'Rejected',
+			"doc_status": 1,
+			"allow_edit": 'Leave Approver'
+		}],
+		'transitions': [{
+			"state": 'Open',
+			"action": 'Approve',
+			"next_state": 'Approved',
+			"allowed": 'Leave Approver'
+		},
+		{
+			"state": 'Open',
+			"action": 'Reject',
+			"next_state": 'Rejected',
+			"allowed": 'Leave Approver'
+		}]
+	}).insert(ignore_permissions=True)
+
+	frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")