Merge branch 'rmehta-bcornwellmott-confirm_training' into develop

This commit is contained in:
Nabin Hait 2017-09-04 10:13:47 +05:30
commit 2b05e61d58
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
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'.
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">
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
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">
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">

View File

@ -289,3 +289,15 @@ def create_user(employee, user = None):
})
user.insert()
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,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Event Name",
"length": 0,
@ -55,7 +55,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Event Status",
"length": 0,
@ -115,12 +115,12 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "Seminar\nTheory\nWorkshop\nConference\nExam\nInternet",
"options": "Seminar\nTheory\nWorkshop\nConference\nExam\nInternet\nSelf-Study",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@ -386,7 +386,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Location",
"length": 0,
@ -581,37 +581,6 @@
"set_only_once": 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_on_submit": 1,
@ -672,6 +641,37 @@
"search_index": 0,
"set_only_once": 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,
@ -684,7 +684,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-05-29 06:13:38.411039",
"modified": "2017-08-11 03:11:25.768563",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Event",

View File

@ -3,24 +3,10 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.hr.doctype.employee.employee import get_employee_emails
class TrainingEvent(Document):
def on_update(self):
self.invite_employee()
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"
def validate(self):
self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees]))

View File

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

View File

@ -5,6 +5,19 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
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,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Training Event",
"length": 0,
@ -133,6 +133,37 @@
"search_index": 0,
"set_only_once": 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,
@ -145,7 +176,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-15 08:16:01.566531",
"modified": "2017-08-11 03:53:21.283968",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Result",

View File

@ -6,19 +6,27 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.hr.doctype.employee.employee import get_employee_emails
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):
self.send_result()
def send_result(self):
for emp in self.employees:
message = "Thank You for attending {0}.".format(self.training_event)
if emp.grade:
message = message + "Your grade: {0}".format(emp.grade)
frappe.sendmail(frappe.db.get_value("Employee", emp.employee, "company_email"), \
subject=_("{0} Results".format(self.training_event)), \
content=message)
training_event = frappe.get_doc("Training Event", self.training_event)
training_event.status = 'Completed'
for e in self.employees:
for e1 in training_event.employees:
if e1.employee == e.employee:
e1.status = 'Completed'
break
training_event.save()
@frappe.whitelist()
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/expense_claim_type/test_expense_claim_type.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_feedback/test_training_feedback.js
erpnext/hr/doctype/loan_type/test_loan_type.js