Fixed merge conflict

This commit is contained in:
Nabin Hait 2017-09-04 10:13:29 +05:30
commit baacf51a00
25 changed files with 352 additions and 79 deletions

View File

@ -9,22 +9,23 @@ Schedule seminars, workshops, conferences etc using Training Event. You can also
### Inviting Employees for Event ### Inviting Employees for Event
You can invite your employees to attend the event. You can do so by selecting the employees to be invited in the employee table. You can invite your employees to attend the event. You can do so by selecting the employees to be invited in the employee table.
By default the status of the employee will be 'Open'. By default the status of the employee will be 'Open'.
The system shall notify the employee with status 'Open' by sending a email to the office email address of the employee as mentioned in the employee master if you have selected 'Send Email' checkbox.
The status is changed to 'Invited' when an invitation email is sent to the employee by the system.
When an Employee confirms his/her presence for the event you can change the status to 'Confirmed'.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event_employee.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_event_employee.png">
When you submit the Training Event, a notifcation will be sent to the employee notifying that the Training has been scheduled. This is sent via Email Alert "Training Scheduled". You can modifiy this Email Alert to customize the message.
### Training Result ### Training Result
After compleation of the training Employee Wise training results can be stored based on the Feedback received from the Trainer. After compleation of the training Employee-wise training results can be stored based on the Feedback received from the Trainer.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_result.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_result.png">
When the Training Result is submitted, all the employees will receive an email notifying them that they must share their feedback via "Training Feedback". This is also managed via an Email Alert, so you can customize this alert too.
### Trainig Feedback ### Training Feedback
Collect feedback regarding the event from your Employees using Training Feedback. Employees can then share their feedback via Training Feedback.
<img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_feedback.png"> <img class="screenshot" alt="Employee" src="/docs/assets/img/human-resources/training_feedback.png">

View File

@ -289,3 +289,15 @@ def create_user(employee, user = None):
}) })
user.insert() user.insert()
return user.name return user.name
def get_employee_emails(employee_list):
'''Returns list of employee emails either based on user_id or company_email'''
employee_emails = []
for employee in employee_list:
if not employee:
continue
user, email = frappe.db.get_value('Employee', employee, ['user_id', 'company_email'])
if user or email:
employee_emails.append(user or email)
return employee_emails

View File

@ -0,0 +1,40 @@
QUnit.module('hr');
QUnit.test("test: Training Event", function (assert) {
// number of asserts
assert.expect(1);
let done = assert.async();
frappe.run_serially([
// insert a new Training Event
() => frappe.set_route("List", "Training Event", "List"),
() => frappe.new_doc("Training Event"),
() => frappe.timeout(1),
() => frappe.click_link('Edit in full page'),
() => cur_frm.set_value("event_name", "Test Event " + frappe.utils.get_random(10)),
() => cur_frm.set_value("start_time", "2017-07-26, 2:00 pm PDT"),
() => cur_frm.set_value("end_time", "2017-07-26, 2:30 pm PDT"),
() => cur_frm.set_value("introduction", "This is a test report"),
() => cur_frm.set_value("location", "Fake office"),
() => frappe.click_button('Add Row'),
() => frappe.db.get_value('Employee', {'employee_name':'Test Employee 1'}, 'name'),
(r) => {
console.log(r);
return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.employee = r.message.name;
},
() => {
return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.attendance = "Optional";
},
() => frappe.click_button('Save'),
() => frappe.timeout(2),
() => frappe.click_button('Submit'),
() => frappe.timeout(2),
() => frappe.click_button('Yes'),
() => frappe.timeout(1),
() => {
assert.equal(cur_frm.doc.docstatus, 1);
},
() => done()
]);
});

View File

@ -18,4 +18,4 @@ frappe.ui.form.on('Training Event', {
}); });
} }
} }
}); });

View File

@ -25,7 +25,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Event Name", "label": "Event Name",
"length": 0, "length": 0,
@ -55,7 +55,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Event Status", "label": "Event Status",
"length": 0, "length": 0,
@ -115,12 +115,12 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Type", "label": "Type",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Seminar\nTheory\nWorkshop\nConference\nExam\nInternet", "options": "Seminar\nTheory\nWorkshop\nConference\nExam\nInternet\nSelf-Study",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -386,7 +386,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Location", "label": "Location",
"length": 0, "length": 0,
@ -581,37 +581,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Will send an email about the event to employees with status 'Open'",
"fieldname": "send_email",
"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": "Send Email",
"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_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
@ -672,6 +641,37 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_emails",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Emails",
"length": 0,
"no_copy": 0,
"options": "Email",
"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
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@ -684,7 +684,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-05-29 06:13:38.411039", "modified": "2017-08-11 03:11:25.768563",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Training Event", "name": "Training Event",

View File

@ -3,24 +3,10 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.employee.employee import get_employee_emails
class TrainingEvent(Document): class TrainingEvent(Document):
def on_update(self): def validate(self):
self.invite_employee() self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees]))
def on_update_after_submit(self):
self.invite_employee()
def invite_employee(self):
if self.event_status == "Scheduled" and self.send_email:
subject = _("""You are invited for to attend {0} - {1} scheduled from {2} to {3} at {4}."""\
.format(self.type, self.event_name, self.start_time, self.end_time, self.location))
for emp in self.employees:
if emp.status== "Open":
frappe.sendmail(frappe.db.get_value("Employee", emp.employee, "company_email"), \
subject=subject, content= self.introduction)
emp.status= "Invited"

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"beta": 0, "beta": 0,
@ -11,6 +12,7 @@
"editable_grid": 1, "editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -21,6 +23,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Employee", "label": "Employee",
@ -40,6 +43,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -50,6 +54,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Employee Name", "label": "Employee Name",
@ -69,6 +74,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -79,6 +85,7 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"length": 0, "length": 0,
@ -96,6 +103,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -107,12 +115,44 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Status", "label": "Status",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Open\nInvited\nConfirmed\nAttended\nWithdrawn", "options": "Open\nInvited\nCompleted\nFeedback Submitted",
"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": "attendance",
"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": 0,
"label": "Attendance",
"length": 0,
"no_copy": 0,
"options": "Mandatory\nOptional",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -126,17 +166,17 @@
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-12-14 11:43:40.996578", "modified": "2017-08-11 03:36:22.738253",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Training Event Employee", "name": "Training Event Employee",
@ -146,7 +186,9 @@
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 0,
"track_seen": 0 "track_seen": 0
} }

View File

@ -5,6 +5,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
class TrainingFeedback(Document): class TrainingFeedback(Document):
pass def validate(self):
training_event = frappe.get_doc("Training Event", self.training_event)
if training_event.docstatus != 1:
frappe.throw(_('{0} must be submitted').format(_('Training Event')))
def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event)
for e in training_event.employees:
if e.employee == self.employee:
training_event.status = 'Feedback Submitted'
break
training_event.update_after_submit()

View File

@ -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: Training Result", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Training Result
() => frappe.tests.make('Training Result', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -26,7 +26,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Training Event", "label": "Training Event",
"length": 0, "length": 0,
@ -133,6 +133,37 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_emails",
"fieldtype": "Small Text",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Emails",
"length": 0,
"no_copy": 0,
"options": "Email",
"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
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@ -145,7 +176,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-15 08:16:01.566531", "modified": "2017-08-11 03:53:21.283968",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Training Result", "name": "Training Result",

View File

@ -6,19 +6,27 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.employee.employee import get_employee_emails
class TrainingResult(Document): class TrainingResult(Document):
def validate(self):
training_event = frappe.get_doc("Training Event", self.training_event)
if training_event.docstatus != 1:
frappe.throw(_('{0} must be submitted').format(_('Training Event')))
self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees]))
def on_submit(self): def on_submit(self):
self.send_result() training_event = frappe.get_doc("Training Event", self.training_event)
training_event.status = 'Completed'
def send_result(self): for e in self.employees:
for emp in self.employees: for e1 in training_event.employees:
message = "Thank You for attending {0}.".format(self.training_event) if e1.employee == e.employee:
if emp.grade: e1.status = 'Completed'
message = message + "Your grade: {0}".format(emp.grade) break
frappe.sendmail(frappe.db.get_value("Employee", emp.employee, "company_email"), \
subject=_("{0} Results".format(self.training_event)), \ training_event.save()
content=message)
@frappe.whitelist() @frappe.whitelist()
def get_employees(training_event): def get_employees(training_event):

View File

View File

@ -0,0 +1,6 @@
<p>{{ _("Hello") }},</p>
<p>You attended training {{ frappe.utils.get_link_to_form(
"Training Event", doc.training_event) }}</p>
<p>{{ _("Please share your feedback to the training by clicking on 'Training Feedback' and then 'New'") }}</p>

View File

@ -0,0 +1,24 @@
{
"attach_print": 0,
"creation": "2017-08-11 03:17:11.769210",
"days_in_advance": 0,
"docstatus": 0,
"doctype": "Email Alert",
"document_type": "Training Result",
"enabled": 1,
"event": "Submit",
"idx": 0,
"is_standard": 1,
"message": "<h3>{{_(\"Training Event\")}}</h3>\n<p>{{ message }}</p>\n\n<h4>{{_(\"Details\")}}</h4>\n{{_(\"Event Name\")}}: <a href=\"{{ event_link }}\">{{ name }}</a>\n<br>{{_(\"Event Location\")}}: {{ location }}\n<br>{{_(\"Start Time\")}}: {{ start_time }}\n<br>{{_(\"End Time\")}}: {{ end_time }}\n<br>{{_(\"Attendance\")}}: {{ attendance }}\n",
"modified": "2017-08-11 04:26:58.194793",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Feedback",
"owner": "Administrator",
"recipients": [
{
"email_by_document_field": "employee_emails"
}
],
"subject": "Please Share your Feedback For {{ doc.training_event }}"
}

View File

@ -0,0 +1,9 @@
<h3>{{_("Training Event")}}</h3>
<p>{{ message }}</p>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a>
<br>{{_("Event Location")}}: {{ location }}
<br>{{_("Start Time")}}: {{ start_time }}
<br>{{_("End Time")}}: {{ end_time }}
<br>{{_("Attendance")}}: {{ attendance }}

View File

@ -0,0 +1,7 @@
from __future__ import unicode_literals
import frappe
def get_context(context):
# do your magic here
pass

View File

@ -0,0 +1,9 @@
<h3>{{_("Training Event")}}</h3>
<p>{{ doc.introduction }}</p>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
<br>{{_("Event Location")}}: {{ doc.location }}
<br>{{_("Start Time")}}: {{ doc.start_time }}
<br>{{_("End Time")}}: {{ doc.end_time }}

View File

@ -0,0 +1,24 @@
{
"attach_print": 0,
"creation": "2017-08-11 03:13:40.519614",
"days_in_advance": 0,
"docstatus": 0,
"doctype": "Email Alert",
"document_type": "Training Event",
"enabled": 1,
"event": "Submit",
"idx": 0,
"is_standard": 1,
"message": "<h3>{{_(\"Training Event\")}}</h3>\n\n<p>{{ doc.introduction }}</p>\n\n<h4>{{_(\"Details\")}}</h4>\n{{_(\"Event Name\")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n<br>{{_(\"Event Location\")}}: {{ doc.location }}\n<br>{{_(\"Start Time\")}}: {{ doc.start_time }}\n<br>{{_(\"End Time\")}}: {{ doc.end_time }}\n",
"modified": "2017-08-13 22:49:42.338881",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",
"owner": "Administrator",
"recipients": [
{
"email_by_document_field": "employee_emails"
}
],
"subject": "Training Scheduled: {{ doc.name }}"
}

View File

@ -0,0 +1,9 @@
<h3>{{_("Training Event")}}</h3>
<p>{{ message }}</p>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a>
<br>{{_("Event Location")}}: {{ location }}
<br>{{_("Start Time")}}: {{ start_time }}
<br>{{_("End Time")}}: {{ end_time }}
<br>{{_("Attendance")}}: {{ attendance }}

View File

@ -0,0 +1,7 @@
from __future__ import unicode_literals
import frappe
def get_context(context):
# do your magic here
pass

View File

@ -0,0 +1,21 @@
<h3>{{_("Training Event")}}</h3>
<p>{{ message }}</p>
<h4>{{_("Details")}}</h4>
{{_("Event Name")}}: <a href="{{ event_link }}">{{ name }}</a>
<br>{{_("Event Location")}}: {{ location }}
<br>{{_("Start Time")}}: {{ start_time }}
<br>{{_("End Time")}}: {{ end_time }}
<br>{{_("Attendance")}}: {{ attendance }}
<h4>{{_("Update Response")}}</h4>
{% if not self_study %}
<p>{{_("Please update your status for this training event")}}:</p>
<form action="{{ confirm_link }}"><input style="display:inline-block" type="submit" value="Confirm Attendance" /></form>
<form action="{{ reject_link }}"><input style="display:inline-block" type="submit" value="Reject Invitation" /></form>
{% else %}
<p>{{_("Please confirm once you have completed your training")}}:</p>
<form action="{{ complete_link }}"><input style="display:inline-block" type="submit" value="Completed Training" /></form>
{% endif %}
<p>{{_("Thank you")}},<br>
{{ user_fullname }}</p>

View File

@ -70,7 +70,8 @@ erpnext/hr/doctype/appraisal_template/test_appraisal_template.js
erpnext/hr/doctype/appraisal/test_appraisal.js erpnext/hr/doctype/appraisal/test_appraisal.js
erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
erpnext/hr/doctype/expense_claim/test_expense_claim.js erpnext/hr/doctype/expense_claim/test_expense_claim.js
erpnext/hr/doctype/training_event/test_training_event.js erpnext/hr/doctype/training_event/tests/test_training_event.js
erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
erpnext/hr/doctype/training_result_employee/test_training_result.js erpnext/hr/doctype/training_result_employee/test_training_result.js
erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/hr/doctype/training_feedback/test_training_feedback.js
erpnext/hr/doctype/loan_type/test_loan_type.js erpnext/hr/doctype/loan_type/test_loan_type.js