Merge branch 'develop' into exit-interview
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index a4c0af0..a1247d9 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -21,7 +21,11 @@
},
{
'label': _('Lifecycle'),
- 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Separation', 'Employee Grievance']
+ 'items': ['Employee Transfer', 'Employee Promotion', 'Employee Grievance']
+ },
+ {
+ 'label': _('Exit'),
+ 'items': ['Employee Separation', 'Exit Interview', 'Full and Final Statement']
},
{
'label': _('Shift'),
diff --git a/erpnext/hr/doctype/exit_interview/__init__.py b/erpnext/hr/doctype/exit_interview/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/__init__.py
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.js b/erpnext/hr/doctype/exit_interview/exit_interview.js
new file mode 100644
index 0000000..502af42
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.js
@@ -0,0 +1,38 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Exit Interview', {
+ refresh: function(frm) {
+ if (!frm.doc.__islocal && !frm.doc.questionnaire_email_sent && frappe.boot.user.can_write.includes('Exit Interview')) {
+ frm.add_custom_button(__('Send Exit Questionnaire'), function () {
+ frm.trigger('send_exit_questionnaire');
+ });
+ }
+ },
+
+ employee: function(frm) {
+ frappe.db.get_value('Employee', frm.doc.employee, 'relieving_date', (message) => {
+ if (!message.relieving_date) {
+ frappe.throw({
+ message: __('Please set the relieving date for employee {0}',
+ ['<a href="/app/employee/' + frm.doc.employee +'">' + frm.doc.employee + '</a>']),
+ title: __('Relieving Date Missing')
+ });
+ }
+ });
+ },
+
+ send_exit_questionnaire: function(frm) {
+ frappe.call({
+ method: 'erpnext.hr.doctype.exit_interview.exit_interview.send_exit_questionnaire',
+ args: {
+ 'interviews': [frm.doc]
+ },
+ callback: function(r) {
+ if (!r.exc) {
+ frm.refresh_field('questionnaire_email_sent');
+ }
+ }
+ });
+ }
+});
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.json b/erpnext/hr/doctype/exit_interview/exit_interview.json
new file mode 100644
index 0000000..989a1b8
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.json
@@ -0,0 +1,246 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "naming_series:",
+ "creation": "2021-12-05 13:56:36.241690",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "email_append_to": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "naming_series",
+ "employee",
+ "employee_name",
+ "email",
+ "column_break_5",
+ "company",
+ "status",
+ "date",
+ "employee_details_section",
+ "department",
+ "designation",
+ "reports_to",
+ "column_break_9",
+ "date_of_joining",
+ "relieving_date",
+ "exit_questionnaire_section",
+ "ref_doctype",
+ "questionnaire_email_sent",
+ "column_break_10",
+ "reference_document_name",
+ "interview_summary_section",
+ "interviewers",
+ "interview_summary",
+ "employee_status_section",
+ "employee_status",
+ "amended_from"
+ ],
+ "fields": [
+ {
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Employee",
+ "options": "Employee",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "employee.employee_name",
+ "fieldname": "employee_name",
+ "fieldtype": "Data",
+ "label": "Employee Name",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "label": "Department",
+ "options": "Department",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.relieving_date",
+ "fieldname": "relieving_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Relieving Date",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ },
+ {
+ "fieldname": "date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Date",
+ "mandatory_depends_on": "eval:doc.status==='Scheduled';"
+ },
+ {
+ "fieldname": "exit_questionnaire_section",
+ "fieldtype": "Section Break",
+ "label": "Exit Questionnaire"
+ },
+ {
+ "fieldname": "ref_doctype",
+ "fieldtype": "Link",
+ "label": "Reference Document Type",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "reference_document_name",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Reference Document Name",
+ "options": "ref_doctype"
+ },
+ {
+ "fieldname": "interview_summary_section",
+ "fieldtype": "Section Break",
+ "label": "Interview Details"
+ },
+ {
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "interviewers",
+ "fieldtype": "Table MultiSelect",
+ "label": "Interviewers",
+ "mandatory_depends_on": "eval:doc.status==='Scheduled';",
+ "options": "Interviewer"
+ },
+ {
+ "fetch_from": "employee.date_of_joining",
+ "fieldname": "date_of_joining",
+ "fieldtype": "Date",
+ "label": "Date of Joining",
+ "read_only": 1
+ },
+ {
+ "fetch_from": "employee.reports_to",
+ "fieldname": "reports_to",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Reports To",
+ "options": "Employee",
+ "read_only": 1
+ },
+ {
+ "fieldname": "employee_details_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Details"
+ },
+ {
+ "fetch_from": "employee.designation",
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "label": "Designation",
+ "options": "Designation",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_9",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "naming_series",
+ "fieldtype": "Select",
+ "label": "Naming Series",
+ "options": "HR-EXIT-INT-"
+ },
+ {
+ "default": "0",
+ "fieldname": "questionnaire_email_sent",
+ "fieldtype": "Check",
+ "in_standard_filter": 1,
+ "label": "Questionnaire Email Sent",
+ "no_copy": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "label": "Email ID",
+ "options": "Email",
+ "read_only": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "Pending\nScheduled\nCompleted\nCancelled",
+ "reqd": 1
+ },
+ {
+ "fieldname": "employee_status_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "employee_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Final Decision",
+ "mandatory_depends_on": "eval:doc.status==='Completed';",
+ "options": "\nEmployee Retained\nExit Confirmed"
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Exit Interview",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "interview_summary",
+ "fieldtype": "Text Editor",
+ "label": "Interview Summary"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-12-07 23:39:22.645401",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Exit Interview",
+ "naming_rule": "By \"Naming Series\" field",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sender_field": "email",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.py b/erpnext/hr/doctype/exit_interview/exit_interview.py
new file mode 100644
index 0000000..30e19f1
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import get_link_to_form
+
+from erpnext.hr.doctype.employee.employee import get_employee_email
+
+
+class ExitInterview(Document):
+ def validate(self):
+ self.validate_relieving_date()
+ self.validate_duplicate_interview()
+ self.set_employee_email()
+
+ def validate_relieving_date(self):
+ if not frappe.db.get_value('Employee', self.employee, 'relieving_date'):
+ frappe.throw(_('Please set the relieving date for employee {0}').format(
+ get_link_to_form('Employee', self.employee)),
+ title=_('Relieving Date Missing'))
+
+ def validate_duplicate_interview(self):
+ doc = frappe.db.exists('Exit Interview', {
+ 'employee': self.employee,
+ 'name': ('!=', self.name),
+ 'docstatus': ('!=', 2)
+ })
+ if doc:
+ frappe.throw(_('Exit Interview {0} already exists for Employee: {1}').format(
+ get_link_to_form('Exit Interview', doc), frappe.bold(self.employee)),
+ frappe.DuplicateEntryError)
+
+ def set_employee_email(self):
+ employee = frappe.get_doc('Employee', self.employee)
+ self.email = get_employee_email(employee)
+
+ def on_submit(self):
+ if self.status != 'Completed':
+ frappe.throw(_('Only Completed documents can be submitted'))
+
+ self.update_interview_date_in_employee()
+
+ def on_cancel(self):
+ self.update_interview_date_in_employee()
+ self.db_set('status', 'Cancelled')
+
+ def update_interview_date_in_employee(self):
+ if self.docstatus == 1:
+ frappe.db.set_value('Employee', self.employee, 'held_on', self.date)
+ elif self.docstatus == 2:
+ frappe.db.set_value('Employee', self.employee, 'held_on', None)
+
+
+@frappe.whitelist()
+def send_exit_questionnaire(interviews):
+ interviews = get_interviews(interviews)
+ validate_questionnaire_settings()
+
+ email_success = []
+ email_failure = []
+
+ for exit_interview in interviews:
+ interview = frappe.get_doc('Exit Interview', exit_interview.get('name'))
+ if interview.get('questionnaire_email_sent'):
+ continue
+
+ employee = frappe.get_doc('Employee', interview.employee)
+ email = get_employee_email(employee)
+
+ context = interview.as_dict()
+ context.update(employee.as_dict())
+ template_name = frappe.db.get_single_value('HR Settings', 'exit_questionnaire_notification_template')
+ template = frappe.get_doc('Email Template', template_name)
+
+ if email:
+ frappe.sendmail(
+ recipients=email,
+ subject=template.subject,
+ message=frappe.render_template(template.response, context),
+ reference_doctype=interview.doctype,
+ reference_name=interview.name
+ )
+ interview.db_set('questionnaire_email_sent', True)
+ interview.notify_update()
+ email_success.append(email)
+ else:
+ email_failure.append(get_link_to_form('Employee', employee.name))
+
+ show_email_summary(email_success, email_failure)
+
+
+def get_interviews(interviews):
+ import json
+
+ if isinstance(interviews, str):
+ interviews = json.loads(interviews)
+
+ if not len(interviews):
+ frappe.throw(_('Atleast one interview has to be selected.'))
+
+ return interviews
+
+
+def validate_questionnaire_settings():
+ settings = frappe.db.get_value('HR Settings', 'HR Settings',
+ ['exit_questionnaire_web_form', 'exit_questionnaire_notification_template'], as_dict=True)
+
+ if not settings.exit_questionnaire_web_form or not settings.exit_questionnaire_notification_template:
+ frappe.throw(
+ _('Please set {0} and {1} in {2}.').format(
+ frappe.bold('Exit Questionnaire Web Form'),
+ frappe.bold('Notification Template'),
+ get_link_to_form('HR Settings', 'HR Settings')),
+ title=_('Settings Missing')
+ )
+
+
+def show_email_summary(email_success, email_failure):
+ message = ''
+ if email_success:
+ message += _('{0}: {1}').format(
+ frappe.bold('Sent Successfully'), ', '.join(email_success))
+ if message and email_failure:
+ message += '<br><br>'
+ if email_failure:
+ message += _('{0} due to missing email information for employee(s): {1}').format(
+ frappe.bold('Sending Failed'), ', '.join(email_failure))
+
+ frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)
\ No newline at end of file
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview_list.js b/erpnext/hr/doctype/exit_interview/exit_interview_list.js
new file mode 100644
index 0000000..93d7b21
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/exit_interview_list.js
@@ -0,0 +1,27 @@
+frappe.listview_settings['Exit Interview'] = {
+ has_indicator_for_draft: 1,
+ get_indicator: function(doc) {
+ let status_color = {
+ 'Pending': 'orange',
+ 'Scheduled': 'yellow',
+ 'Completed': 'green',
+ 'Cancelled': 'red',
+ };
+ return [__(doc.status), status_color[doc.status], 'status,=,'+doc.status];
+ },
+
+ onload: function(listview) {
+ if (frappe.boot.user.can_write.includes('Exit Interview')) {
+ listview.page.add_action_item(__('Send Exit Questionnaires'), function() {
+ const interviews = listview.get_checked_items();
+ frappe.call({
+ method: 'erpnext.hr.doctype.exit_interview.exit_interview.send_exit_questionnaire',
+ freeze: true,
+ args: {
+ 'interviews': interviews
+ }
+ });
+ });
+ }
+ }
+};
diff --git a/erpnext/hr/doctype/exit_interview/exit_questionnaire_notification_template.html b/erpnext/hr/doctype/exit_interview/exit_questionnaire_notification_template.html
new file mode 100644
index 0000000..0317b1a
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/exit_questionnaire_notification_template.html
@@ -0,0 +1,16 @@
+<h2>Exit Questionnaire</h2>
+<br>
+
+<p>
+ Dear {{ employee_name }},
+ <br><br>
+
+ Thank you for the contribution you have made during your time at {{ company }}. We value your opinion and welcome the feedback on your experience working with us.
+ Request you to take out a few minutes to fill up this Exit Questionnaire.
+
+ {% set web_form = frappe.db.get_value('HR Settings', 'HR Settings', 'exit_questionnaire_web_form') %}
+ {% set web_form_link = frappe.utils.get_url(uri=frappe.db.get_value('Web Form', web_form, 'route')) %}
+
+ <br><br>
+ <a class="btn btn-primary" href="{{ web_form_link }}" target="_blank">{{ _('Submit Now') }}</a>
+</p>
diff --git a/erpnext/hr/doctype/exit_interview/test_exit_interview.py b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
new file mode 100644
index 0000000..b31d593
--- /dev/null
+++ b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+from frappe import _
+from frappe.core.doctype.user_permission.test_user_permission import create_user
+from frappe.tests.test_webform import create_custom_doctype, create_webform
+from frappe.utils import getdate
+
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.exit_interview.exit_interview import send_exit_questionnaire
+
+
+class TestExitInterview(unittest.TestCase):
+ def setUp(self):
+ frappe.db.sql('delete from `tabExit Interview`')
+
+ def test_duplicate_interview(self):
+ employee = make_employee('employeeexit1@example.com')
+ frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ interview = create_exit_interview(employee)
+
+ doc = frappe.copy_doc(interview)
+ self.assertRaises(frappe.DuplicateEntryError, doc.save)
+
+ def test_relieving_date_validation(self):
+ employee = make_employee('employeeexit2@example.com')
+ # unset relieving date
+ frappe.db.set_value('Employee', employee, 'relieving_date', None)
+
+ interview = create_exit_interview(employee, save=False)
+ self.assertRaises(frappe.ValidationError, interview.save)
+
+ # set relieving date
+ frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ interview = create_exit_interview(employee)
+ self.assertTrue(interview.name)
+
+ def test_interview_date_updated_in_employee_master(self):
+ employee = make_employee('employeeexit3@example.com')
+ frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+
+ interview = create_exit_interview(employee)
+ interview.status = 'Completed'
+ interview.employee_status = 'Exit Confirmed'
+
+ # exit interview date updated on submit
+ interview.submit()
+ self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), interview.date)
+
+ # exit interview reset on cancel
+ interview.reload()
+ interview.cancel()
+ self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), None)
+
+ def test_send_exit_questionnaire(self):
+ create_custom_doctype()
+ create_webform()
+ template = create_notification_template()
+
+ webform = frappe.db.get_all('Web Form', limit=1)
+ frappe.db.set_value('HR Settings', 'HR Settings', {
+ 'exit_questionnaire_web_form': webform[0].name,
+ 'exit_questionnaire_notification_template': template
+ })
+
+ employee = make_employee('employeeexit3@example.com')
+ frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+
+ interview = create_exit_interview(employee)
+ send_exit_questionnaire([interview])
+
+ email_queue = frappe.db.get_all('Email Queue', ['name', 'message'], limit=1)
+ self.assertTrue('Subject: Exit Questionnaire Notification' in email_queue[0].message)
+
+ def tearDown(self):
+ frappe.db.rollback()
+
+
+def create_exit_interview(employee, save=True):
+ interviewer = create_user('test_interviewer1@example.com')
+
+ doc = frappe.get_doc({
+ 'doctype': 'Exit Interview',
+ 'employee': employee,
+ 'company': '_Test Company',
+ 'status': 'Pending',
+ 'date': getdate(),
+ 'interviewers': [{
+ 'interviewer': interviewer.name
+ }],
+ 'interview_summary': 'Test'
+ })
+
+ if save:
+ return doc.insert()
+ return doc
+
+
+def create_notification_template():
+ template = frappe.db.exists('Email Template', _('Exit Questionnaire Notification'))
+ if not template:
+ base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
+ response = frappe.read_file(os.path.join(base_path, 'exit_interview/exit_questionnaire_notification_template.html'))
+
+ template = frappe.get_doc({
+ 'doctype': 'Email Template',
+ 'name': _('Exit Questionnaire Notification'),
+ 'response': response,
+ 'subject': _('Exit Questionnaire Notification'),
+ 'owner': frappe.session.user,
+ }).insert(ignore_permissions=True)
+ template = template.name
+
+ return template
\ No newline at end of file
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json
index 5148435..f9a3e05 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.json
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.json
@@ -36,7 +36,11 @@
"remind_before",
"column_break_4",
"send_interview_feedback_reminder",
- "feedback_reminder_notification_template"
+ "feedback_reminder_notification_template",
+ "employee_exit_section",
+ "exit_questionnaire_web_form",
+ "column_break_34",
+ "exit_questionnaire_notification_template"
],
"fields": [
{
@@ -226,13 +230,34 @@
"fieldname": "check_vacancies",
"fieldtype": "Check",
"label": "Check Vacancies On Job Offer Creation"
+ },
+ {
+ "fieldname": "employee_exit_section",
+ "fieldtype": "Section Break",
+ "label": "Employee Exit Settings"
+ },
+ {
+ "fieldname": "exit_questionnaire_web_form",
+ "fieldtype": "Link",
+ "label": "Exit Questionnaire Web Form",
+ "options": "Web Form"
+ },
+ {
+ "fieldname": "exit_questionnaire_notification_template",
+ "fieldtype": "Link",
+ "label": "Exit Questionnaire Notification Template",
+ "options": "Email Template"
+ },
+ {
+ "fieldname": "column_break_34",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2021-10-01 23:46:11.098236",
+ "modified": "2021-12-05 14:48:10.884253",
"modified_by": "Administrator",
"module": "HR",
"name": "HR Settings",
diff --git a/erpnext/hr/notification/exit_interview_scheduled/__init__.py b/erpnext/hr/notification/exit_interview_scheduled/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/notification/exit_interview_scheduled/__init__.py
diff --git a/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.json b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.json
new file mode 100644
index 0000000..8323ef0
--- /dev/null
+++ b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.json
@@ -0,0 +1,29 @@
+{
+ "attach_print": 0,
+ "channel": "Email",
+ "condition": "doc.date and doc.email and doc.docstatus != 2 and doc.status == 'Scheduled'",
+ "creation": "2021-12-05 22:11:47.263933",
+ "date_changed": "date",
+ "days_in_advance": 1,
+ "docstatus": 0,
+ "doctype": "Notification",
+ "document_type": "Exit Interview",
+ "enabled": 1,
+ "event": "Days Before",
+ "idx": 0,
+ "is_standard": 1,
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n\t<tr height=\"10\"></tr>\n\t<tr>\n\t\t<td width=\"15\"></td>\n\t\t<td>\n\t\t\t<div class=\"text-medium text-muted\">\n\t\t\t\t<span>{{_(\"Exit Interview Scheduled:\")}} {{ doc.name }}</span>\n\t\t\t</div>\n\t\t</td>\n\t\t<td width=\"15\"></td>\n\t</tr>\n\t<tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n\t<tr height=\"10\"></tr>\n\t<tr>\n\t\t<td width=\"15\"></td>\n\t\t<td>\n\t\t\t<div>\n\t\t\t\t<ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n\t\t\t\t\t<li>{{_(\"Employee\")}}: <b>{{ doc.employee }} - {{ doc.employee_name }}</b></li>\n\t\t\t\t\t<li>{{_(\"Date\")}}: <b>{{ doc.date }}</b></li>\n\t\t\t\t\t<li> {{_(\"Interviewers\")}}: </li>\n\t\t\t\t\t{% for entry in doc.interviewers %}\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li>{{ entry.user }}</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t{% endfor %}\n\t\t\t\t\t<li>{{ _(\"Interview Document\") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</td>\n\t\t<td width=\"15\"></td>\n\t</tr>\n\t<tr height=\"10\"></tr>\n</table>\n",
+ "modified": "2021-12-05 22:26:57.096159",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Exit Interview Scheduled",
+ "owner": "Administrator",
+ "recipients": [
+ {
+ "receiver_by_document_field": "email"
+ }
+ ],
+ "send_system_notification": 0,
+ "send_to_all_assignees": 1,
+ "subject": "Exit Interview Scheduled: {{ doc.name }}"
+}
\ No newline at end of file
diff --git a/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.md b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.md
new file mode 100644
index 0000000..6d6db40
--- /dev/null
+++ b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.md
@@ -0,0 +1,37 @@
+<table class="panel-header" border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tr height="10"></tr>
+ <tr>
+ <td width="15"></td>
+ <td>
+ <div class="text-medium text-muted">
+ <h2>{{_("Exit Interview Scheduled:")}} {{ doc.name }}</h2>
+ </div>
+ </td>
+ <td width="15"></td>
+ </tr>
+ <tr height="10"></tr>
+</table>
+
+<table class="panel-body" border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tr height="10"></tr>
+ <tr>
+ <td width="15"></td>
+ <td>
+ <div>
+ <ul class="list-unstyled" style="line-height: 1.7">
+ <li><b>{{_("Employee")}}: </b>{{ doc.employee }} - {{ doc.employee_name }}</li>
+ <li><b>{{_("Date")}}: </b>{{ frappe.utils.formatdate(doc.date) }}</li>
+ <li><b>{{_("Interviewers")}}:</b> </li>
+ {% for entry in doc.interviewers %}
+ <ul>
+ <li>{{ entry.user }}</li>
+ </ul>
+ {% endfor %}
+ <li><b>{{ _("Interview Document") }}:</b> {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+ </ul>
+ </div>
+ </td>
+ <td width="15"></td>
+ </tr>
+ <tr height="10"></tr>
+</table>
diff --git a/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.py b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.py
new file mode 100644
index 0000000..5f697c9
--- /dev/null
+++ b/erpnext/hr/notification/exit_interview_scheduled/exit_interview_scheduled.py
@@ -0,0 +1,6 @@
+# import frappe
+
+
+def get_context(context):
+ # do your magic here
+ pass
diff --git a/erpnext/hr/report/employee_exits/__init__.py b/erpnext/hr/report/employee_exits/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/__init__.py
diff --git a/erpnext/hr/report/employee_exits/employee_exits.js b/erpnext/hr/report/employee_exits/employee_exits.js
new file mode 100644
index 0000000..ac677d8
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.js
@@ -0,0 +1,77 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Employee Exits"] = {
+ filters: [
+ {
+ "fieldname": "from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12)
+ },
+ {
+ "fieldname": "to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "default": frappe.datetime.nowdate()
+ },
+ {
+ "fieldname": "company",
+ "label": __("Company"),
+ "fieldtype": "Link",
+ "options": "Company"
+ },
+ {
+ "fieldname": "department",
+ "label": __("Department"),
+ "fieldtype": "Link",
+ "options": "Department"
+ },
+ {
+ "fieldname": "designation",
+ "label": __("Designation"),
+ "fieldtype": "Link",
+ "options": "Designation"
+ },
+ {
+ "fieldname": "employee",
+ "label": __("Employee"),
+ "fieldtype": "Link",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "reports_to",
+ "label": __("Reports To"),
+ "fieldtype": "Link",
+ "options": "Employee"
+ },
+ {
+ "fieldname": "interview_status",
+ "label": __("Interview Status"),
+ "fieldtype": "Select",
+ "options": ["", "Pending", "Scheduled", "Completed"]
+ },
+ {
+ "fieldname": "final_decision",
+ "label": __("Final Decision"),
+ "fieldtype": "Select",
+ "options": ["", "Employee Retained", "Exit Confirmed"]
+ },
+ {
+ "fieldname": "exit_interview_pending",
+ "label": __("Exit Interview Pending"),
+ "fieldtype": "Check"
+ },
+ {
+ "fieldname": "questionnaire_pending",
+ "label": __("Exit Questionnaire Pending"),
+ "fieldtype": "Check"
+ },
+ {
+ "fieldname": "fnf_pending",
+ "label": __("FnF Pending"),
+ "fieldtype": "Check"
+ }
+ ]
+};
diff --git a/erpnext/hr/report/employee_exits/employee_exits.json b/erpnext/hr/report/employee_exits/employee_exits.json
new file mode 100644
index 0000000..4fe9a85
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-12-05 19:47:18.332319",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "Test",
+ "modified": "2021-12-05 19:47:18.332319",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Employee Exits",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Exit Interview",
+ "report_name": "Employee Exits",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "System Manager"
+ },
+ {
+ "role": "HR Manager"
+ },
+ {
+ "role": "HR User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py
new file mode 100644
index 0000000..d0e1ef9
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/employee_exits.py
@@ -0,0 +1,230 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# License: MIT. See LICENSE
+
+import frappe
+from frappe import _
+from frappe.utils import getdate
+
+
+def execute(filters=None):
+ columns = get_columns()
+ data = get_data(filters)
+ chart = get_chart_data(data)
+ report_summary = get_report_summary(data)
+
+ return columns, data, None, chart, report_summary
+
+def get_columns():
+ return [
+ {
+ 'label': _('Employee'),
+ 'fieldname': 'employee',
+ 'fieldtype': 'Link',
+ 'options': 'Employee',
+ 'width': 150
+ },
+ {
+ 'label': _('Employee Name'),
+ 'fieldname': 'employee_name',
+ 'fieldtype': 'Data',
+ 'width': 150
+ },
+ {
+ 'label': _('Date of Joining'),
+ 'fieldname': 'date_of_joining',
+ 'fieldtype': 'Date',
+ 'width': 120
+ },
+ {
+ 'label': _('Relieving Date'),
+ 'fieldname': 'relieving_date',
+ 'fieldtype': 'Date',
+ 'width': 120
+ },
+ {
+ 'label': _('Exit Interview'),
+ 'fieldname': 'exit_interview',
+ 'fieldtype': 'Link',
+ 'options': 'Exit Interview',
+ 'width': 150
+ },
+ {
+ 'label': _('Interview Status'),
+ 'fieldname': 'interview_status',
+ 'fieldtype': 'Data',
+ 'width': 130
+ },
+ {
+ 'label': _('Final Decision'),
+ 'fieldname': 'employee_status',
+ 'fieldtype': 'Data',
+ 'width': 150
+ },
+ {
+ 'label': _('Full and Final Statement'),
+ 'fieldname': 'full_and_final_statement',
+ 'fieldtype': 'Link',
+ 'options': 'Full and Final Statement',
+ 'width': 180
+ },
+ {
+ 'label': _('Department'),
+ 'fieldname': 'department',
+ 'fieldtype': 'Link',
+ 'options': 'Department',
+ 'width': 120
+ },
+ {
+ 'label': _('Designation'),
+ 'fieldname': 'designation',
+ 'fieldtype': 'Link',
+ 'options': 'Designation',
+ 'width': 120
+ },
+ {
+ 'label': _('Reports To'),
+ 'fieldname': 'reports_to',
+ 'fieldtype': 'Link',
+ 'options': 'Employee',
+ 'width': 120
+ }
+ ]
+
+def get_data(filters):
+ employee = frappe.qb.DocType('Employee')
+ interview = frappe.qb.DocType('Exit Interview')
+ fnf = frappe.qb.DocType('Full and Final Statement')
+
+ query = (
+ frappe.qb.from_(employee)
+ .left_join(interview).on(interview.employee == employee.name)
+ .left_join(fnf).on(fnf.employee == employee.name)
+ .select(
+ employee.name.as_('employee'), employee.employee_name.as_('employee_name'),
+ employee.date_of_joining.as_('date_of_joining'), employee.relieving_date.as_('relieving_date'),
+ employee.department.as_('department'), employee.designation.as_('designation'),
+ employee.reports_to.as_('reports_to'), interview.name.as_('exit_interview'),
+ interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
+ interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
+ .distinct()
+ .orderby(employee.relieving_date)
+ .where(
+ ((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
+ & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
+ & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
+ )
+ )
+
+ query = get_conditions(filters, query, employee, interview, fnf)
+ result = query.run(as_dict=True)
+
+ return result
+
+
+def get_conditions(filters, query, employee, interview, fnf):
+ if filters.get('from_date') and filters.get('to_date'):
+ query = query.where(employee.relieving_date[getdate(filters.get('from_date')):getdate(filters.get('to_date'))])
+
+ elif filters.get('from_date'):
+ query = query.where(employee.relieving_date >= filters.get('from_date'))
+
+ elif filters.get('to_date'):
+ query = query.where(employee.relieving_date <= filters.get('to_date'))
+
+ if filters.get('company'):
+ query = query.where(employee.company == filters.get('company'))
+
+ if filters.get('department'):
+ query = query.where(employee.department == filters.get('department'))
+
+ if filters.get('designation'):
+ query = query.where(employee.designation == filters.get('designation'))
+
+ if filters.get('employee'):
+ query = query.where(employee.name == filters.get('employee'))
+
+ if filters.get('reports_to'):
+ query = query.where(employee.reports_to == filters.get('reports_to'))
+
+ if filters.get('interview_status'):
+ query = query.where(interview.status == filters.get('interview_status'))
+
+ if filters.get('final_decision'):
+ query = query.where(interview.employee_status == filters.get('final_decision'))
+
+ if filters.get('exit_interview_pending'):
+ query = query.where((interview.name == '') | (interview.name.isnull()))
+
+ if filters.get('questionnaire_pending'):
+ query = query.where((interview.reference_document_name == '') | (interview.reference_document_name.isnull()))
+
+ if filters.get('fnf_pending'):
+ query = query.where((fnf.name == '') | (fnf.name.isnull()))
+
+ return query
+
+
+def get_chart_data(data):
+ if not data:
+ return None
+
+ retained = 0
+ exit_confirmed = 0
+ pending = 0
+
+ for entry in data:
+ if entry.employee_status == 'Employee Retained':
+ retained += 1
+ elif entry.employee_status == 'Exit Confirmed':
+ exit_confirmed += 1
+ else:
+ pending += 1
+
+ chart = {
+ 'data': {
+ 'labels': [_('Retained'), _('Exit Confirmed'), _('Decision Pending')],
+ 'datasets': [{'name': _('Employee Status'), 'values': [retained, exit_confirmed, pending]}]
+ },
+ 'type': 'donut',
+ 'colors': ['green', 'red', 'blue'],
+ }
+
+ return chart
+
+
+def get_report_summary(data):
+ if not data:
+ return None
+
+ total_resignations = len(data)
+ interviews_pending = len([entry.name for entry in data if not entry.exit_interview])
+ fnf_pending = len([entry.name for entry in data if not entry.full_and_final_statement])
+ questionnaires_pending = len([entry.name for entry in data if not entry.questionnaire])
+
+ return [
+ {
+ 'value': total_resignations,
+ 'label': _('Total Resignations'),
+ 'indicator': 'Red' if total_resignations > 0 else 'Green',
+ 'datatype': 'Int',
+ },
+ {
+ 'value': interviews_pending,
+ 'label': _('Pending Interviews'),
+ 'indicator': 'Blue' if interviews_pending > 0 else 'Green',
+ 'datatype': 'Int',
+ },
+ {
+ 'value': fnf_pending,
+ 'label': _('Pending FnF'),
+ 'indicator': 'Blue' if fnf_pending > 0 else 'Green',
+ 'datatype': 'Int',
+ },
+ {
+ 'value': questionnaires_pending,
+ 'label': _('Pending Questionnaires'),
+ 'indicator': 'Blue' if questionnaires_pending > 0 else 'Green',
+ 'datatype': 'Int'
+ },
+ ]
+
diff --git a/erpnext/hr/report/employee_exits/test_employee_exits.py b/erpnext/hr/report/employee_exits/test_employee_exits.py
new file mode 100644
index 0000000..d7e95a6
--- /dev/null
+++ b/erpnext/hr/report/employee_exits/test_employee_exits.py
@@ -0,0 +1,242 @@
+import unittest
+
+import frappe
+from frappe.utils import add_days, getdate
+
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.exit_interview.test_exit_interview import create_exit_interview
+from erpnext.hr.doctype.full_and_final_statement.test_full_and_final_statement import (
+ create_full_and_final_statement,
+)
+from erpnext.hr.report.employee_exits.employee_exits import execute
+
+
+class TestEmployeeExits(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ create_company()
+ frappe.db.sql("delete from `tabEmployee` where company='Test Company'")
+ frappe.db.sql("delete from `tabFull and Final Statement` where company='Test Company'")
+ frappe.db.sql("delete from `tabExit Interview` where company='Test Company'")
+
+ cls.create_records()
+
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.rollback()
+
+ @classmethod
+ def create_records(cls):
+ cls.emp1 = make_employee(
+ 'employeeexit1@example.com',
+ company='Test Company',
+ date_of_joining=getdate('01-10-2021'),
+ relieving_date=add_days(getdate(), 14),
+ designation='Accountant'
+ )
+ cls.emp2 = make_employee(
+ 'employeeexit2@example.com',
+ company='Test Company',
+ date_of_joining=getdate('01-12-2021'),
+ relieving_date=add_days(getdate(), 15),
+ designation='Accountant'
+ )
+
+ cls.emp3 = make_employee(
+ 'employeeexit3@example.com',
+ company='Test Company',
+ date_of_joining=getdate('02-12-2021'),
+ relieving_date=add_days(getdate(), 29),
+ designation='Engineer'
+ )
+ cls.emp4 = make_employee(
+ 'employeeexit4@example.com',
+ company='Test Company',
+ date_of_joining=getdate('01-12-2021'),
+ relieving_date=add_days(getdate(), 30),
+ designation='Engineer'
+ )
+
+ # exit interview for 3 employees only
+ cls.interview1 = create_exit_interview(cls.emp1)
+ cls.interview2 = create_exit_interview(cls.emp2)
+ cls.interview3 = create_exit_interview(cls.emp3)
+
+ # create fnf for some records
+ cls.fnf1 = create_full_and_final_statement(cls.emp1)
+ cls.fnf2 = create_full_and_final_statement(cls.emp2)
+
+ # link questionnaire for a few records
+ # setting employee doctype as reference instead of creating a questionnaire
+ # since this is just for a test
+ frappe.db.set_value('Exit Interview', cls.interview1.name, {
+ 'ref_doctype': 'Employee',
+ 'reference_document_name': cls.emp1
+ })
+
+ frappe.db.set_value('Exit Interview', cls.interview2.name, {
+ 'ref_doctype': 'Employee',
+ 'reference_document_name': cls.emp2
+ })
+
+ frappe.db.set_value('Exit Interview', cls.interview3.name, {
+ 'ref_doctype': 'Employee',
+ 'reference_document_name': cls.emp3
+ })
+
+
+ def test_employee_exits_summary(self):
+ filters = {
+ 'company': 'Test Company',
+ 'from_date': getdate(),
+ 'to_date': add_days(getdate(), 15),
+ 'designation': 'Accountant'
+ }
+
+ report = execute(filters)
+
+ employee1 = frappe.get_doc('Employee', self.emp1)
+ employee2 = frappe.get_doc('Employee', self.emp2)
+ expected_data = [
+ {
+ 'employee': employee1.name,
+ 'employee_name': employee1.employee_name,
+ 'date_of_joining': employee1.date_of_joining,
+ 'relieving_date': employee1.relieving_date,
+ 'department': employee1.department,
+ 'designation': employee1.designation,
+ 'reports_to': None,
+ 'exit_interview': self.interview1.name,
+ 'interview_status': self.interview1.status,
+ 'employee_status': '',
+ 'questionnaire': employee1.name,
+ 'full_and_final_statement': self.fnf1.name
+ },
+ {
+ 'employee': employee2.name,
+ 'employee_name': employee2.employee_name,
+ 'date_of_joining': employee2.date_of_joining,
+ 'relieving_date': employee2.relieving_date,
+ 'department': employee2.department,
+ 'designation': employee2.designation,
+ 'reports_to': None,
+ 'exit_interview': self.interview2.name,
+ 'interview_status': self.interview2.status,
+ 'employee_status': '',
+ 'questionnaire': employee2.name,
+ 'full_and_final_statement': self.fnf2.name
+ }
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+
+ def test_pending_exit_interviews_summary(self):
+ filters = {
+ 'company': 'Test Company',
+ 'from_date': getdate(),
+ 'to_date': add_days(getdate(), 30),
+ 'exit_interview_pending': 1
+ }
+
+ report = execute(filters)
+
+ employee4 = frappe.get_doc('Employee', self.emp4)
+ expected_data = [{
+ 'employee': employee4.name,
+ 'employee_name': employee4.employee_name,
+ 'date_of_joining': employee4.date_of_joining,
+ 'relieving_date': employee4.relieving_date,
+ 'department': employee4.department,
+ 'designation': employee4.designation,
+ 'reports_to': None,
+ 'exit_interview': None,
+ 'interview_status': None,
+ 'employee_status': None,
+ 'questionnaire': None,
+ 'full_and_final_statement': None
+ }]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+ def test_pending_exit_questionnaire_summary(self):
+ filters = {
+ 'company': 'Test Company',
+ 'from_date': getdate(),
+ 'to_date': add_days(getdate(), 30),
+ 'questionnaire_pending': 1
+ }
+
+ report = execute(filters)
+
+ employee4 = frappe.get_doc('Employee', self.emp4)
+ expected_data = [{
+ 'employee': employee4.name,
+ 'employee_name': employee4.employee_name,
+ 'date_of_joining': employee4.date_of_joining,
+ 'relieving_date': employee4.relieving_date,
+ 'department': employee4.department,
+ 'designation': employee4.designation,
+ 'reports_to': None,
+ 'exit_interview': None,
+ 'interview_status': None,
+ 'employee_status': None,
+ 'questionnaire': None,
+ 'full_and_final_statement': None
+ }]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+
+ def test_pending_fnf_summary(self):
+ filters = {
+ 'company': 'Test Company',
+ 'fnf_pending': 1
+ }
+
+ report = execute(filters)
+
+ employee3 = frappe.get_doc('Employee', self.emp3)
+ employee4 = frappe.get_doc('Employee', self.emp4)
+ expected_data = [
+ {
+ 'employee': employee3.name,
+ 'employee_name': employee3.employee_name,
+ 'date_of_joining': employee3.date_of_joining,
+ 'relieving_date': employee3.relieving_date,
+ 'department': employee3.department,
+ 'designation': employee3.designation,
+ 'reports_to': None,
+ 'exit_interview': self.interview3.name,
+ 'interview_status': self.interview3.status,
+ 'employee_status': '',
+ 'questionnaire': employee3.name,
+ 'full_and_final_statement': None
+ },
+ {
+ 'employee': employee4.name,
+ 'employee_name': employee4.employee_name,
+ 'date_of_joining': employee4.date_of_joining,
+ 'relieving_date': employee4.relieving_date,
+ 'department': employee4.department,
+ 'designation': employee4.designation,
+ 'reports_to': None,
+ 'exit_interview': None,
+ 'interview_status': None,
+ 'employee_status': None,
+ 'questionnaire': None,
+ 'full_and_final_statement': None
+ }
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+
+def create_company():
+ if not frappe.db.exists('Company', 'Test Company'):
+ frappe.get_doc({
+ 'doctype': 'Company',
+ 'company_name': 'Test Company',
+ 'default_currency': 'INR',
+ 'country': 'India'
+ }).insert()
\ No newline at end of file
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index 7408d63..85e641c 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -5,7 +5,7 @@
"label": "Outgoing Salary"
}
],
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Human Resource\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Employee\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Leave Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Job Applicant\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee Lifecycle\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Shift Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Leaves\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Expense Claims\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fleet Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Recruitment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loans\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Training\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Performance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]",
+ "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Human Resource\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Outgoing Salary\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Employee\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leave Application\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Attendance\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Applicant\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Monthly Attendance Sheet\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Lifecycle\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Employee Exit\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Shift Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Leaves\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Expense Claims\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loans\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Recruitment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Performance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fleet Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Training\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:48:58.322521",
"docstatus": 0,
"doctype": "Workspace",
@@ -16,14 +16,6 @@
"label": "HR",
"links": [
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Employee",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -112,14 +104,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Employee Lifecycle",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Job Applicant",
"hidden": 0,
"is_query_report": 0,
@@ -228,14 +212,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Shift Management",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -269,14 +245,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Leaves",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -387,14 +355,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Attendance",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
@@ -450,14 +410,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Expense Claims",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Employee",
"hidden": 0,
"is_query_report": 0,
@@ -490,14 +442,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Settings",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -533,14 +477,6 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Fleet Management",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
"label": "Driver",
"link_count": 0,
"link_to": "Driver",
@@ -582,14 +518,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Recruitment",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
@@ -809,14 +737,6 @@
"type": "Link"
},
{
- "hidden": 0,
- "is_query_report": 0,
- "label": "Key Reports",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
"dependencies": "Attendance",
"hidden": 0,
"is_query_report": 1,
@@ -933,9 +853,796 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Lifecycle",
+ "link_count": 7,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Job Applicant",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Onboarding",
+ "link_count": 0,
+ "link_to": "Employee Onboarding",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Skill Map",
+ "link_count": 0,
+ "link_to": "Employee Skill Map",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Promotion",
+ "link_count": 0,
+ "link_to": "Employee Promotion",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Transfer",
+ "link_count": 0,
+ "link_to": "Employee Transfer",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Grievance Type",
+ "link_count": 0,
+ "link_to": "Grievance Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Grievance",
+ "link_count": 0,
+ "link_to": "Employee Grievance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Onboarding Template",
+ "link_count": 0,
+ "link_to": "Employee Onboarding Template",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Exit",
+ "link_count": 4,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Separation Template",
+ "link_count": 0,
+ "link_to": "Employee Separation Template",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Separation",
+ "link_count": 0,
+ "link_to": "Employee Separation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Full and Final Statement",
+ "link_count": 0,
+ "link_to": "Full and Final Statement",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Exit Interview",
+ "link_count": 0,
+ "link_to": "Exit Interview",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee",
+ "link_count": 8,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee",
+ "link_count": 0,
+ "link_to": "Employee",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employment Type",
+ "link_count": 0,
+ "link_to": "Employment Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Branch",
+ "link_count": 0,
+ "link_to": "Branch",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Department",
+ "link_count": 0,
+ "link_to": "Department",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Designation",
+ "link_count": 0,
+ "link_to": "Designation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Grade",
+ "link_count": 0,
+ "link_to": "Employee Grade",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Group",
+ "link_count": 0,
+ "link_to": "Employee Group",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Health Insurance",
+ "link_count": 0,
+ "link_to": "Employee Health Insurance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Key Reports",
+ "link_count": 7,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Attendance",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Monthly Attendance Sheet",
+ "link_count": 0,
+ "link_to": "Monthly Attendance Sheet",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Staffing Plan",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Recruitment Analytics",
+ "link_count": 0,
+ "link_to": "Recruitment Analytics",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Analytics",
+ "link_count": 0,
+ "link_to": "Employee Analytics",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Leave Balance",
+ "link_count": 0,
+ "link_to": "Employee Leave Balance",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Leave Balance Summary",
+ "link_count": 0,
+ "link_to": "Employee Leave Balance Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee Advance",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Employee Advance Summary",
+ "link_count": 0,
+ "link_to": "Employee Advance Summary",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Exits",
+ "link_count": 0,
+ "link_to": "Employee Exits",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Recruitment",
+ "link_count": 11,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Job Opening",
+ "link_count": 0,
+ "link_to": "Job Opening",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Referral",
+ "link_count": 0,
+ "link_to": "Employee Referral",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Job Applicant",
+ "link_count": 0,
+ "link_to": "Job Applicant",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Job Offer",
+ "link_count": 0,
+ "link_to": "Job Offer",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Staffing Plan",
+ "link_count": 0,
+ "link_to": "Staffing Plan",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Appointment Letter",
+ "link_count": 0,
+ "link_to": "Appointment Letter",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Appointment Letter Template",
+ "link_count": 0,
+ "link_to": "Appointment Letter Template",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Interview Type",
+ "link_count": 0,
+ "link_to": "Interview Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Interview Round",
+ "link_count": 0,
+ "link_to": "Interview Round",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Interview",
+ "link_count": 0,
+ "link_to": "Interview",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Interview Feedback",
+ "link_count": 0,
+ "link_to": "Interview Feedback",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Fleet Management",
+ "link_count": 4,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Driver",
+ "link_count": 0,
+ "link_to": "Driver",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Vehicle",
+ "link_count": 0,
+ "link_to": "Vehicle",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Vehicle Log",
+ "link_count": 0,
+ "link_to": "Vehicle Log",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Vehicle",
+ "hidden": 0,
+ "is_query_report": 1,
+ "label": "Vehicle Expenses",
+ "link_count": 0,
+ "link_to": "Vehicle Expenses",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Settings",
+ "link_count": 3,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "HR Settings",
+ "link_count": 0,
+ "link_to": "HR Settings",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Daily Work Summary Group",
+ "link_count": 0,
+ "link_to": "Daily Work Summary Group",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Team Updates",
+ "link_count": 0,
+ "link_to": "team-updates",
+ "link_type": "Page",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Expense Claims",
+ "link_count": 3,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Expense Claim",
+ "link_count": 0,
+ "link_to": "Expense Claim",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Advance",
+ "link_count": 0,
+ "link_to": "Employee Advance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Travel Request",
+ "link_count": 0,
+ "link_to": "Travel Request",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Attendance",
+ "link_count": 5,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Attendance Tool",
+ "link_count": 0,
+ "link_to": "Employee Attendance Tool",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Attendance",
+ "link_count": 0,
+ "link_to": "Attendance",
+ "link_type": "DocType",
+ "onboard": 1,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Attendance Request",
+ "link_count": 0,
+ "link_to": "Attendance Request",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Upload Attendance",
+ "link_count": 0,
+ "link_to": "Upload Attendance",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Employee Checkin",
+ "link_count": 0,
+ "link_to": "Employee Checkin",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leaves",
+ "link_count": 10,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Holiday List",
+ "link_count": 0,
+ "link_to": "Holiday List",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Type",
+ "link_count": 0,
+ "link_to": "Leave Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Period",
+ "link_count": 0,
+ "link_to": "Leave Period",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Leave Type",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Policy",
+ "link_count": 0,
+ "link_to": "Leave Policy",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Leave Policy",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Policy Assignment",
+ "link_count": 0,
+ "link_to": "Leave Policy Assignment",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Application",
+ "link_count": 0,
+ "link_to": "Leave Application",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Allocation",
+ "link_count": 0,
+ "link_to": "Leave Allocation",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Encashment",
+ "link_count": 0,
+ "link_to": "Leave Encashment",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Leave Block List",
+ "link_count": 0,
+ "link_to": "Leave Block List",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "Employee",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Compensatory Leave Request",
+ "link_count": 0,
+ "link_to": "Compensatory Leave Request",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Shift Management",
+ "link_count": 3,
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Shift Type",
+ "link_count": 0,
+ "link_to": "Shift Type",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Shift Request",
+ "link_count": 0,
+ "link_to": "Shift Request",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Shift Assignment",
+ "link_count": 0,
+ "link_to": "Shift Assignment",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
}
],
- "modified": "2021-08-31 12:18:59.842919",
+ "modified": "2021-12-05 22:05:13.004462",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 26fb859..7b7629f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -316,3 +316,4 @@
erpnext.patches.v14_0.migrate_crm_settings
erpnext.patches.v13_0.rename_ksa_qr_field
erpnext.patches.v13_0.disable_ksa_print_format_for_others
+erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
new file mode 100644
index 0000000..8b1752b
--- /dev/null
+++ b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
@@ -0,0 +1,27 @@
+import os
+
+import frappe
+from frappe import _
+
+
+def execute():
+ frappe.reload_doc("email", "doctype", "email_template")
+ frappe.reload_doc("hr", "doctype", "hr_settings")
+
+ template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
+ if not template:
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(os.path.join(base_path, "exit_interview/exit_questionnaire_notification_template.html"))
+
+ template = frappe.get_doc({
+ "doctype": "Email Template",
+ "name": _("Exit Questionnaire Notification"),
+ "response": response,
+ "subject": _("Exit Questionnaire Notification"),
+ "owner": frappe.session.user,
+ }).insert(ignore_permissions=True)
+ template = template.name
+
+ hr_settings = frappe.get_doc("HR Settings")
+ hr_settings.exit_questionnaire_notification_template = template
+ hr_settings.save()
diff --git a/erpnext/setup/setup_wizard/operations/defaults_setup.py b/erpnext/setup/setup_wizard/operations/defaults_setup.py
index e4b1fa2..ca1f57e 100644
--- a/erpnext/setup/setup_wizard/operations/defaults_setup.py
+++ b/erpnext/setup/setup_wizard/operations/defaults_setup.py
@@ -68,6 +68,8 @@
hr_settings.send_interview_feedback_reminder = 1
hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder")
+
+ hr_settings.exit_questionnaire_notification_template = _("Exit Questionnaire Notification")
hr_settings.save()
def set_no_copy_fields_in_variant_settings():
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 98f9119..97d850b 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -278,6 +278,11 @@
records += [{'doctype': 'Email Template', 'name': _('Interview Feedback Reminder'), 'response': response,
'subject': _('Interview Feedback Reminder'), 'owner': frappe.session.user}]
+ response = frappe.read_file(os.path.join(base_path, 'exit_interview/exit_questionnaire_notification_template.html'))
+
+ records += [{'doctype': 'Email Template', 'name': _('Exit Questionnaire Notification'), 'response': response,
+ 'subject': _('Exit Questionnaire Notification'), 'owner': frappe.session.user}]
+
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html"))