chore: move widely used doctypes from HR to Setup module

- Employee: used in Telephony, Stock, Quality, Projects, Manufacturing, Loans, Buying, Assets, Accounts, Dependent Apps: Healthcare

- Holiday List: CRM (Appointment Booking), Projects, Healthcare, Support (SLA), Education (Student's Holiday List)

- Branch: Selling (SMS Center), used widely as an accounting dimension

- Department: Assets, Projects, SMS Center, used widely as an accounting dimension

- Designation: Lead, Healthcare Practitioner, used in Employee master which will now be in core

- Employee Group: Telephony, Communication

- Child Tables for above dts: Employee Group Table (Employee Group), Employee Education (Employee), Employee External Work History (Employee), Employee Internal Work History (Employee), Designation Skill (Designation), Holiday (Holiday List)
This commit is contained in:
Rucha Mahabal
2022-06-10 11:02:00 +05:30
parent b29edb302c
commit f520f042af
70 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1 @@
Location belonging to the organization where Employees can belong.

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Branch', {
refresh: function(frm) {
}
});

View File

@@ -0,0 +1,103 @@
{
"allow_copy": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:branch",
"beta": 0,
"creation": "2013-01-10 16:34:13",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "branch",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Branch",
"length": 0,
"no_copy": 0,
"oldfieldname": "branch",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-code-fork",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-07-25 05:24:26.534086",
"modified_by": "Administrator",
"module": "HR",
"name": "Branch",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class Branch(Document):
pass

View File

@@ -0,0 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
test_records = frappe.get_test_records("Branch")

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
Department where Employee belongs.

View File

@@ -0,0 +1,31 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Department', {
onload: function(frm) {
frm.set_query("parent_department", function(){
return {"filters": [["Department", "is_group", "=", 1]]};
});
frm.set_query("payroll_cost_center", function() {
return {
filters: {
"company": frm.doc.company,
"is_group": 0
}
};
});
},
refresh: function(frm) {
// read-only for root department
if(!frm.doc.parent_department && !frm.is_new()) {
frm.set_read_only();
frm.set_intro(__("This is a root department and cannot be edited."));
}
},
validate: function(frm) {
if(frm.doc.name=="All Departments") {
frappe.throw(__("You cannot edit root node."));
}
}
});

View File

@@ -0,0 +1,228 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-02-05 11:48:26",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"department_name",
"parent_department",
"column_break_3",
"company",
"is_group",
"disabled",
"section_break_4",
"payroll_cost_center",
"column_break_9",
"leave_block_list",
"approvers",
"leave_approvers",
"expense_approvers",
"shift_request_approver",
"lft",
"rgt",
"old_parent"
],
"fields": [
{
"fieldname": "department_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Department",
"oldfieldname": "department_name",
"oldfieldtype": "Data",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "parent_department",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Parent Department",
"options": "Department",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Company",
"options": "Company",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"bold": 1,
"default": "0",
"fieldname": "is_group",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Group",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
},
{
"description": "Days for which Holidays are blocked for this department.",
"fieldname": "leave_block_list",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Leave Block List",
"options": "Leave Block List",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "leave_approvers",
"fieldtype": "Table",
"label": "Leave Approver",
"options": "Department Approver",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "expense_approvers",
"fieldtype": "Table",
"label": "Expense Approver",
"options": "Department Approver",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
"label": "lft",
"print_hide": 1,
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
"label": "rgt",
"print_hide": 1,
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Old Parent",
"print_hide": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "payroll_cost_center",
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"description": "The first Approver in the list will be set as the default Approver.",
"fieldname": "approvers",
"fieldtype": "Section Break",
"label": "Approvers",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "shift_request_approver",
"fieldtype": "Table",
"label": "Shift Request Approver",
"options": "Department Approver",
"show_days": 1,
"show_seconds": 1
}
],
"icon": "fa fa-sitemap",
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2020-06-23 15:42:00.563272",
"modified_by": "Administrator",
"module": "HR",
"name": "Department",
"nsm_parent_field": "parent_department",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 1,
"share": 1,
"write": 1
}
],
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@@ -0,0 +1,94 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.utils.nestedset import NestedSet, get_root_of
from erpnext.utilities.transaction_base import delete_events
class Department(NestedSet):
nsm_parent_field = "parent_department"
def autoname(self):
root = get_root_of("Department")
if root and self.department_name != root:
self.name = get_abbreviated_name(self.department_name, self.company)
else:
self.name = self.department_name
def validate(self):
if not self.parent_department:
root = get_root_of("Department")
if root:
self.parent_department = root
def before_rename(self, old, new, merge=False):
# renaming consistency with abbreviation
if not frappe.get_cached_value("Company", self.company, "abbr") in new:
new = get_abbreviated_name(new, self.company)
return new
def on_update(self):
if not (frappe.local.flags.ignore_update_nsm or frappe.flags.in_setup_wizard):
super(Department, self).on_update()
def on_trash(self):
super(Department, self).on_trash()
delete_events(self.doctype, self.name)
def on_doctype_update():
frappe.db.add_index("Department", ["lft", "rgt"])
def get_abbreviated_name(name, company):
abbr = frappe.get_cached_value("Company", company, "abbr")
new_name = "{0} - {1}".format(name, abbr)
return new_name
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
condition = ""
var_dict = {
"name": get_root_of("Department"),
"parent": parent,
"company": company,
}
if company == parent:
condition = "name=%(name)s"
elif company:
condition = "parent_department=%(parent)s and company=%(company)s"
else:
condition = "parent_department = %(parent)s"
return frappe.db.sql(
"""
select
name as value,
is_group as expandable
from `tab{doctype}`
where
{condition}
order by name""".format(
doctype=doctype, condition=condition
),
var_dict,
as_dict=1,
)
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
args = frappe.form_dict
args = make_tree_args(**args)
if args.parent_department == args.company:
args.parent_department = None
frappe.get_doc(args).insert()

View File

@@ -0,0 +1,28 @@
frappe.treeview_settings["Department"] = {
ignore_fields:["parent_department"],
get_tree_nodes: 'erpnext.hr.doctype.department.department.get_children',
add_tree_node: 'erpnext.hr.doctype.department.department.add_node',
filters: [
{
fieldname: "company",
fieldtype:"Link",
options: "Company",
label: __("Company"),
},
],
breadcrumb: "HR",
root_label: "All Departments",
get_tree_root: true,
menu_items: [
{
label: __("New Department"),
action: function() {
frappe.new_doc("Department", true);
},
condition: 'frappe.boot.user.can_create.indexOf("Department") !== -1'
}
],
onload: function(treeview) {
treeview.make_tree();
}
};

View File

@@ -0,0 +1,31 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
test_ignore = ["Leave Block List"]
class TestDepartment(unittest.TestCase):
def test_remove_department_data(self):
doc = create_department("Test Department")
frappe.delete_doc("Department", doc.name)
def create_department(department_name, parent_department=None):
doc = frappe.get_doc(
{
"doctype": "Department",
"is_group": 0,
"parent_department": parent_department,
"department_name": department_name,
"company": frappe.defaults.get_defaults().company,
}
).insert()
return doc
test_records = frappe.get_test_records("Department")

View File

@@ -0,0 +1,4 @@
[
{"doctype":"Department", "department_name":"_Test Department", "company": "_Test Company", "parent_department": "All Departments"},
{"doctype":"Department", "department_name":"_Test Department 1", "company": "_Test Company", "parent_department": "All Departments"}
]

View File

@@ -0,0 +1 @@
Employee Designation.

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Designation', {
refresh: function(frm) {
}
});

View File

@@ -0,0 +1,198 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:designation_name",
"beta": 0,
"creation": "2013-01-10 16:34:13",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "designation_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Designation",
"length": 0,
"no_copy": 0,
"oldfieldname": "designation_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Text",
"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": "Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "required_skills_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Required Skills",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "skills",
"fieldtype": "Table",
"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": "Skills",
"length": 0,
"no_copy": 0,
"options": "Designation Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"icon": "fa fa-bookmark",
"idx": 1,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-04-16 10:02:23.277734",
"modified_by": "Administrator",
"module": "HR",
"name": "Designation",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"read": 1,
"role": "Sales User"
}
],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class Designation(Document):
pass

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
# test_records = frappe.get_test_records('Designation')
def create_designation(**args):
args = frappe._dict(args)
if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
return frappe.get_doc("Designation", args.designation_name or "_Test designation")
designation = frappe.get_doc(
{
"doctype": "Designation",
"designation_name": args.designation_name or "_Test designation",
"description": args.description or "_Test description",
}
)
designation.save()
return designation

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,74 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-04-16 10:01:05.259881",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "skill",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Skill",
"length": 0,
"no_copy": 0,
"options": "Skill",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-04-16 13:42:10.760449",
"modified_by": "Administrator",
"module": "HR",
"name": "Designation Skill",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class DesignationSkill(Document):
pass

View File

@@ -0,0 +1 @@
Employee master.

View File

@@ -0,0 +1,159 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("erpnext.hr");
erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form.Controller {
setup() {
this.frm.fields_dict.user_id.get_query = function(doc, cdt, cdn) {
return {
query: "frappe.core.doctype.user.user.user_query",
filters: {ignore_user_type: 1}
}
}
this.frm.fields_dict.reports_to.get_query = function(doc, cdt, cdn) {
return { query: "erpnext.controllers.queries.employee_query"} }
}
refresh() {
erpnext.toggle_naming_series();
}
date_of_birth() {
return cur_frm.call({
method: "get_retirement_date",
args: {
date_of_birth: this.frm.doc.date_of_birth
}
});
}
salutation() {
if (this.frm.doc.salutation) {
this.frm.set_value("gender", {
"Mr": "Male",
"Ms": "Female"
}[this.frm.doc.salutation]);
}
}
};
frappe.ui.form.on('Employee', {
setup: function (frm) {
frm.set_query("leave_policy", function() {
return {
"filters": {
"docstatus": 1
}
};
});
frm.set_query("payroll_cost_center", function() {
return {
filters: {
"company": frm.doc.company,
"is_group": 0
}
};
});
},
onload: function (frm) {
frm.set_query("department", function() {
return {
"filters": {
"company": frm.doc.company,
}
};
});
},
prefered_contact_email: function(frm) {
frm.events.update_contact(frm);
},
personal_email: function(frm) {
frm.events.update_contact(frm);
},
company_email: function(frm) {
frm.events.update_contact(frm);
},
user_id: function(frm) {
frm.events.update_contact(frm);
},
update_contact: function(frm) {
var prefered_email_fieldname = frappe.model.scrub(frm.doc.prefered_contact_email) || 'user_id';
frm.set_value("prefered_email",
frm.fields_dict[prefered_email_fieldname].value);
},
status: function(frm) {
return frm.call({
method: "deactivate_sales_person",
args: {
employee: frm.doc.employee,
status: frm.doc.status
}
});
},
create_user: function(frm) {
if (!frm.doc.prefered_email) {
frappe.throw(__("Please enter Preferred Contact Email"));
}
frappe.call({
method: "erpnext.hr.doctype.employee.employee.create_user",
args: {
employee: frm.doc.name,
email: frm.doc.prefered_email
},
callback: function (r) {
frm.set_value("user_id", r.message);
}
});
}
});
cur_frm.cscript = new erpnext.hr.EmployeeController({
frm: cur_frm
});
frappe.tour['Employee'] = [
{
fieldname: "first_name",
title: "First Name",
description: __("Enter First and Last name of Employee, based on Which Full Name will be updated. IN transactions, it will be Full Name which will be fetched.")
},
{
fieldname: "company",
title: "Company",
description: __("Select a Company this Employee belongs to. Other HR features like Payroll. Expense Claims and Leaves for this Employee will be created for a given company only.")
},
{
fieldname: "date_of_birth",
title: "Date of Birth",
description: __("Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff.")
},
{
fieldname: "date_of_joining",
title: "Date of Joining",
description: __("Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases.")
},
{
fieldname: "holiday_list",
title: "Holiday List",
description: __("Select a default Holiday List for this Employee. The days listed in Holiday List will not be counted in Leave Application.")
},
{
fieldname: "reports_to",
title: "Reports To",
description: __("Here, you can select a senior of this Employee. Based on this, Organization Chart will be populated.")
},
{
fieldname: "leave_approver",
title: "Leave Approver",
description: __("Select Leave Approver for an employee. The user one who will look after his/her Leave application")
},
];

View File

@@ -0,0 +1,880 @@
{
"actions": [],
"allow_events_in_timeline": 1,
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2022-02-21 11:54:09.632218",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"basic_information",
"employee",
"naming_series",
"first_name",
"middle_name",
"last_name",
"salutation",
"employee_name",
"employment_type",
"image",
"column_break1",
"company",
"status",
"gender",
"date_of_birth",
"date_of_joining",
"employee_number",
"emergency_contact_details",
"person_to_be_contacted",
"relation",
"column_break_19",
"emergency_phone_number",
"erpnext_user",
"user_id",
"create_user",
"create_user_permission",
"employment_details",
"job_applicant",
"scheduled_confirmation_date",
"final_confirmation_date",
"col_break_22",
"contract_end_date",
"notice_number_of_days",
"date_of_retirement",
"job_profile",
"department",
"designation",
"reports_to",
"column_break_31",
"grade",
"branch",
"approvers_section",
"expense_approver",
"leave_approver",
"column_break_45",
"shift_request_approver",
"attendance_and_leave_details",
"attendance_device_id",
"column_break_44",
"holiday_list",
"default_shift",
"salary_information",
"salary_currency",
"ctc",
"salary_mode",
"payroll_cost_center",
"column_break_52",
"bank_name",
"bank_ac_no",
"health_insurance_section",
"health_insurance_provider",
"health_insurance_no",
"contact_details",
"cell_number",
"prefered_email",
"personal_email",
"unsubscribed",
"permanent_accommodation_type",
"permanent_address",
"column_break4",
"prefered_contact_email",
"company_email",
"current_accommodation_type",
"current_address",
"sb53",
"bio",
"personal_details",
"passport_number",
"date_of_issue",
"valid_upto",
"place_of_issue",
"marital_status",
"blood_group",
"column_break6",
"family_background",
"health_details",
"educational_qualification",
"education",
"previous_work_experience",
"external_work_history",
"history_in_company",
"internal_work_history",
"exit",
"resignation_letter_date",
"relieving_date",
"reason_for_leaving",
"leave_encashed",
"encashment_date",
"exit_interview_details",
"held_on",
"new_workplace",
"feedback",
"lft",
"rgt",
"old_parent"
],
"fields": [
{
"fieldname": "basic_information",
"fieldtype": "Section Break",
"oldfieldtype": "Section Break"
},
{
"fieldname": "employee",
"fieldtype": "Data",
"hidden": 1,
"label": "Employee",
"no_copy": 1,
"print_hide": 1,
"report_hide": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "HR-EMP-",
"set_only_once": 1
},
{
"fieldname": "salutation",
"fieldtype": "Link",
"label": "Salutation",
"oldfieldname": "salutation",
"oldfieldtype": "Select",
"options": "Salutation"
},
{
"fieldname": "first_name",
"fieldtype": "Data",
"label": "First Name",
"reqd": 1
},
{
"allow_in_quick_entry": 1,
"fieldname": "middle_name",
"fieldtype": "Data",
"label": "Middle Name"
},
{
"allow_in_quick_entry": 1,
"fieldname": "last_name",
"fieldtype": "Data",
"label": "Last Name"
},
{
"fieldname": "employee_name",
"fieldtype": "Data",
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Full Name",
"oldfieldname": "employee_name",
"oldfieldtype": "Data",
"read_only": 1
},
{
"fetch_from": "user_id.user_image",
"fetch_if_empty": 1,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1
},
{
"allow_in_quick_entry": 1,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"print_hide": 1,
"remember_last_selected_value": 1,
"reqd": 1
},
{
"default": "Active",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"oldfieldname": "status",
"oldfieldtype": "Select",
"options": "Active\nInactive\nSuspended\nLeft",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "employee_number",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Employee Number",
"oldfieldname": "employee_number",
"oldfieldtype": "Data"
},
{
"fieldname": "employment_type",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Employment Type",
"oldfieldname": "employment_type",
"oldfieldtype": "Link",
"options": "Employment Type"
},
{
"fieldname": "gender",
"fieldtype": "Link",
"label": "Gender",
"oldfieldname": "gender",
"oldfieldtype": "Select",
"options": "Gender",
"reqd": 1
},
{
"fieldname": "date_of_birth",
"fieldtype": "Date",
"label": "Date of Birth",
"oldfieldname": "date_of_birth",
"oldfieldtype": "Date",
"reqd": 1
},
{
"fieldname": "date_of_joining",
"fieldtype": "Date",
"label": "Date of Joining",
"oldfieldname": "date_of_joining",
"oldfieldtype": "Date",
"reqd": 1
},
{
"allow_in_quick_entry": 1,
"fieldname": "emergency_contact_details",
"fieldtype": "Section Break",
"label": "Emergency Contact"
},
{
"bold": 1,
"fieldname": "emergency_phone_number",
"fieldtype": "Data",
"label": "Emergency Phone",
"options": "Phone"
},
{
"bold": 1,
"fieldname": "person_to_be_contacted",
"fieldtype": "Data",
"label": "Emergency Contact Name"
},
{
"fieldname": "relation",
"fieldtype": "Data",
"label": "Relation"
},
{
"collapsible": 1,
"fieldname": "erpnext_user",
"fieldtype": "Section Break",
"label": "ERPNext User"
},
{
"description": "System User (login) ID. If set, it will become default for all HR forms.",
"fieldname": "user_id",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "User ID",
"options": "User"
},
{
"depends_on": "eval:(!doc.user_id)",
"fieldname": "create_user",
"fieldtype": "Button",
"label": "Create User"
},
{
"default": "1",
"depends_on": "user_id",
"description": "This will restrict user access to other employee records",
"fieldname": "create_user_permission",
"fieldtype": "Check",
"label": "Create User Permission"
},
{
"allow_in_quick_entry": 1,
"collapsible": 1,
"fieldname": "employment_details",
"fieldtype": "Section Break",
"label": "Joining Details"
},
{
"fieldname": "job_applicant",
"fieldtype": "Link",
"label": "Job Applicant",
"options": "Job Applicant"
},
{
"fieldname": "scheduled_confirmation_date",
"fieldtype": "Date",
"label": "Offer Date",
"oldfieldname": "scheduled_confirmation_date",
"oldfieldtype": "Date"
},
{
"fieldname": "final_confirmation_date",
"fieldtype": "Date",
"label": "Confirmation Date",
"oldfieldname": "final_confirmation_date",
"oldfieldtype": "Date"
},
{
"fieldname": "col_break_22",
"fieldtype": "Column Break"
},
{
"fieldname": "contract_end_date",
"fieldtype": "Date",
"label": "Contract End Date",
"oldfieldname": "contract_end_date",
"oldfieldtype": "Date"
},
{
"fieldname": "notice_number_of_days",
"fieldtype": "Int",
"label": "Notice (days)",
"oldfieldname": "notice_number_of_days",
"oldfieldtype": "Int"
},
{
"fieldname": "date_of_retirement",
"fieldtype": "Date",
"label": "Date Of Retirement",
"oldfieldname": "date_of_retirement",
"oldfieldtype": "Date"
},
{
"collapsible": 1,
"fieldname": "job_profile",
"fieldtype": "Section Break",
"label": "Department and Grade"
},
{
"fieldname": "department",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Department",
"oldfieldname": "department",
"oldfieldtype": "Link",
"options": "Department"
},
{
"fieldname": "designation",
"fieldtype": "Link",
"in_global_search": 1,
"in_list_view": 1,
"label": "Designation",
"oldfieldname": "designation",
"oldfieldtype": "Link",
"options": "Designation",
"search_index": 1
},
{
"fieldname": "reports_to",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Reports to",
"oldfieldname": "reports_to",
"oldfieldtype": "Link",
"options": "Employee"
},
{
"fieldname": "column_break_31",
"fieldtype": "Column Break"
},
{
"fieldname": "grade",
"fieldtype": "Link",
"label": "Grade",
"options": "Employee Grade"
},
{
"fieldname": "branch",
"fieldtype": "Link",
"label": "Branch",
"oldfieldname": "branch",
"oldfieldtype": "Link",
"options": "Branch"
},
{
"description": "Applicable Holiday List",
"fieldname": "holiday_list",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Holiday List",
"oldfieldname": "holiday_list",
"oldfieldtype": "Link",
"options": "Holiday List"
},
{
"collapsible": 1,
"fieldname": "salary_information",
"fieldtype": "Section Break",
"label": "Salary Details",
"oldfieldtype": "Section Break",
"width": "50%"
},
{
"fieldname": "salary_mode",
"fieldtype": "Select",
"label": "Salary Mode",
"oldfieldname": "salary_mode",
"oldfieldtype": "Select",
"options": "\nBank\nCash\nCheque"
},
{
"depends_on": "eval:doc.salary_mode == 'Bank'",
"fieldname": "bank_name",
"fieldtype": "Data",
"label": "Bank Name",
"oldfieldname": "bank_name",
"oldfieldtype": "Link"
},
{
"depends_on": "eval:doc.salary_mode == 'Bank'",
"fieldname": "bank_ac_no",
"fieldtype": "Data",
"label": "Bank A/C No.",
"oldfieldname": "bank_ac_no",
"oldfieldtype": "Data"
},
{
"collapsible": 1,
"fieldname": "health_insurance_section",
"fieldtype": "Section Break",
"label": "Health Insurance"
},
{
"fieldname": "health_insurance_provider",
"fieldtype": "Link",
"label": "Health Insurance Provider",
"options": "Employee Health Insurance"
},
{
"depends_on": "eval:doc.health_insurance_provider",
"fieldname": "health_insurance_no",
"fieldtype": "Data",
"label": "Health Insurance No"
},
{
"collapsible": 1,
"fieldname": "contact_details",
"fieldtype": "Section Break",
"label": "Contact Details"
},
{
"fieldname": "cell_number",
"fieldtype": "Data",
"label": "Mobile",
"options": "Phone"
},
{
"fieldname": "prefered_contact_email",
"fieldtype": "Select",
"label": "Prefered Contact Email",
"options": "\nCompany Email\nPersonal Email\nUser ID"
},
{
"fieldname": "prefered_email",
"fieldtype": "Data",
"label": "Prefered Email",
"options": "Email",
"read_only": 1
},
{
"description": "Provide Email Address registered in company",
"fieldname": "company_email",
"fieldtype": "Data",
"label": "Company Email",
"oldfieldname": "company_email",
"oldfieldtype": "Data",
"options": "Email"
},
{
"fieldname": "personal_email",
"fieldtype": "Data",
"label": "Personal Email",
"options": "Email"
},
{
"default": "0",
"fieldname": "unsubscribed",
"fieldtype": "Check",
"label": "Unsubscribed"
},
{
"fieldname": "column_break4",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "permanent_accommodation_type",
"fieldtype": "Select",
"label": "Permanent Address Is",
"options": "\nRented\nOwned"
},
{
"fieldname": "permanent_address",
"fieldtype": "Small Text",
"label": "Permanent Address"
},
{
"fieldname": "current_accommodation_type",
"fieldtype": "Select",
"label": "Current Address Is",
"options": "\nRented\nOwned"
},
{
"fieldname": "current_address",
"fieldtype": "Small Text",
"label": "Current Address"
},
{
"collapsible": 1,
"fieldname": "sb53",
"fieldtype": "Section Break",
"label": "Personal Bio"
},
{
"description": "Short biography for website and other publications.",
"fieldname": "bio",
"fieldtype": "Text Editor",
"label": "Bio / Cover Letter"
},
{
"collapsible": 1,
"fieldname": "personal_details",
"fieldtype": "Section Break",
"label": "Personal Details"
},
{
"fieldname": "passport_number",
"fieldtype": "Data",
"label": "Passport Number"
},
{
"fieldname": "date_of_issue",
"fieldtype": "Date",
"label": "Date of Issue"
},
{
"fieldname": "valid_upto",
"fieldtype": "Date",
"label": "Valid Upto"
},
{
"fieldname": "place_of_issue",
"fieldtype": "Data",
"label": "Place of Issue"
},
{
"fieldname": "column_break6",
"fieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "marital_status",
"fieldtype": "Select",
"label": "Marital Status",
"options": "\nSingle\nMarried\nDivorced\nWidowed"
},
{
"fieldname": "blood_group",
"fieldtype": "Select",
"label": "Blood Group",
"options": "\nA+\nA-\nB+\nB-\nAB+\nAB-\nO+\nO-"
},
{
"description": "Here you can maintain family details like name and occupation of parent, spouse and children",
"fieldname": "family_background",
"fieldtype": "Small Text",
"label": "Family Background"
},
{
"description": "Here you can maintain height, weight, allergies, medical concerns etc",
"fieldname": "health_details",
"fieldtype": "Small Text",
"label": "Health Details"
},
{
"collapsible": 1,
"fieldname": "educational_qualification",
"fieldtype": "Section Break",
"label": "Educational Qualification"
},
{
"fieldname": "education",
"fieldtype": "Table",
"label": "Education",
"options": "Employee Education"
},
{
"collapsible": 1,
"fieldname": "previous_work_experience",
"fieldtype": "Section Break",
"label": "Previous Work Experience",
"options": "Simple"
},
{
"fieldname": "external_work_history",
"fieldtype": "Table",
"label": "External Work History",
"options": "Employee External Work History"
},
{
"collapsible": 1,
"fieldname": "history_in_company",
"fieldtype": "Section Break",
"label": "History In Company",
"options": "Simple"
},
{
"fieldname": "internal_work_history",
"fieldtype": "Table",
"label": "Internal Work History",
"options": "Employee Internal Work History"
},
{
"collapsible": 1,
"fieldname": "exit",
"fieldtype": "Section Break",
"label": "Exit",
"oldfieldtype": "Section Break"
},
{
"fieldname": "resignation_letter_date",
"fieldtype": "Date",
"label": "Resignation Letter Date",
"oldfieldname": "resignation_letter_date",
"oldfieldtype": "Date"
},
{
"fieldname": "relieving_date",
"fieldtype": "Date",
"label": "Relieving Date",
"mandatory_depends_on": "eval:doc.status == \"Left\"",
"oldfieldname": "relieving_date",
"oldfieldtype": "Date"
},
{
"fieldname": "reason_for_leaving",
"fieldtype": "Small Text",
"label": "Reason for Leaving",
"oldfieldname": "reason_for_leaving",
"oldfieldtype": "Data"
},
{
"fieldname": "leave_encashed",
"fieldtype": "Select",
"label": "Leave Encashed?",
"oldfieldname": "leave_encashed",
"oldfieldtype": "Select",
"options": "\nYes\nNo"
},
{
"depends_on": "eval:doc.leave_encashed ==\"Yes\"",
"fieldname": "encashment_date",
"fieldtype": "Date",
"label": "Encashment Date",
"oldfieldname": "encashment_date",
"oldfieldtype": "Date"
},
{
"fieldname": "exit_interview_details",
"fieldtype": "Column Break",
"oldfieldname": "col_brk6",
"oldfieldtype": "Column Break",
"width": "50%"
},
{
"fieldname": "held_on",
"fieldtype": "Date",
"label": "Exit Interview Held On",
"oldfieldname": "held_on",
"oldfieldtype": "Date"
},
{
"fieldname": "new_workplace",
"fieldtype": "Data",
"label": "New Workplace",
"oldfieldname": "new_workplace",
"oldfieldtype": "Data"
},
{
"fieldname": "feedback",
"fieldtype": "Small Text",
"label": "Feedback",
"oldfieldname": "feedback",
"oldfieldtype": "Text"
},
{
"fieldname": "lft",
"fieldtype": "Int",
"hidden": 1,
"label": "lft",
"read_only": 1
},
{
"fieldname": "rgt",
"fieldtype": "Int",
"hidden": 1,
"label": "rgt",
"read_only": 1
},
{
"fieldname": "old_parent",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Old Parent"
},
{
"fieldname": "attendance_device_id",
"fieldtype": "Data",
"label": "Attendance Device ID (Biometric/RF tag ID)",
"no_copy": 1,
"unique": 1
},
{
"collapsible": 1,
"fieldname": "attendance_and_leave_details",
"fieldtype": "Section Break",
"label": "Attendance and Leave Details"
},
{
"fieldname": "column_break_44",
"fieldtype": "Column Break"
},
{
"fieldname": "default_shift",
"fieldtype": "Link",
"label": "Default Shift",
"options": "Shift Type"
},
{
"fieldname": "leave_approver",
"fieldtype": "Link",
"label": "Leave Approver",
"options": "User"
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"fetch_from": "department.payroll_cost_center",
"fetch_if_empty": 1,
"fieldname": "payroll_cost_center",
"fieldtype": "Link",
"label": "Payroll Cost Center",
"options": "Cost Center"
},
{
"fieldname": "column_break_52",
"fieldtype": "Column Break"
},
{
"fieldname": "expense_approver",
"fieldtype": "Link",
"label": "Expense Approver",
"options": "User"
},
{
"fieldname": "approvers_section",
"fieldtype": "Section Break",
"label": "Approvers"
},
{
"fieldname": "column_break_45",
"fieldtype": "Column Break"
},
{
"fieldname": "shift_request_approver",
"fieldtype": "Link",
"label": "Shift Request Approver",
"options": "User"
},
{
"fieldname": "salary_currency",
"fieldtype": "Link",
"label": "Salary Currency",
"options": "Currency"
},
{
"fieldname": "ctc",
"fieldtype": "Currency",
"label": "Cost to Company (CTC)",
"options": "salary_currency"
}
],
"icon": "fa fa-user",
"idx": 24,
"image_field": "image",
"links": [],
"modified": "2022-04-22 16:21:55.811983",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee"
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 1,
"share": 1,
"write": 1
}
],
"search_fields": "employee_name",
"show_name_in_global_search": 1,
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "employee_name"
}

View File

@@ -0,0 +1,521 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe import _, scrub, throw
from frappe.model.naming import set_name_by_naming_series
from frappe.permissions import (
add_user_permission,
get_doc_permissions,
has_permission,
remove_user_permission,
set_user_permission_if_allowed,
)
from frappe.utils import add_years, cstr, getdate, today, validate_email_address
from frappe.utils.nestedset import NestedSet
from erpnext.utilities.transaction_base import delete_events
class EmployeeUserDisabledError(frappe.ValidationError):
pass
class InactiveEmployeeStatusError(frappe.ValidationError):
pass
class Employee(NestedSet):
nsm_parent_field = "reports_to"
def autoname(self):
naming_method = frappe.db.get_value("HR Settings", None, "emp_created_by")
if not naming_method:
throw(_("Please setup Employee Naming System in Human Resource > HR Settings"))
else:
if naming_method == "Naming Series":
set_name_by_naming_series(self)
elif naming_method == "Employee Number":
self.name = self.employee_number
elif naming_method == "Full Name":
self.set_employee_name()
self.name = self.employee_name
self.employee = self.name
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
self.employee = self.name
self.set_employee_name()
self.validate_date()
self.validate_email()
self.validate_status()
self.validate_reports_to()
self.validate_preferred_email()
if self.job_applicant:
self.validate_onboarding_process()
if self.user_id:
self.validate_user_details()
else:
existing_user_id = frappe.db.get_value("Employee", self.name, "user_id")
if existing_user_id:
remove_user_permission("Employee", self.name, existing_user_id)
def after_rename(self, old, new, merge):
self.db_set("employee", new)
def set_employee_name(self):
self.employee_name = " ".join(
filter(lambda x: x, [self.first_name, self.middle_name, self.last_name])
)
def validate_user_details(self):
if self.user_id:
data = frappe.db.get_value("User", self.user_id, ["enabled", "user_image"], as_dict=1)
if not data:
self.user_id = None
return
if data.get("user_image") and self.image == "":
self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id()
def update_nsm_model(self):
frappe.utils.nestedset.update_nsm(self)
def on_update(self):
self.update_nsm_model()
if self.user_id:
self.update_user()
self.update_user_permissions()
self.reset_employee_emails_cache()
self.update_approver_role()
def update_user_permissions(self):
if not self.create_user_permission:
return
if not has_permission("User Permission", ptype="write", raise_exception=False):
return
employee_user_permission_exists = frappe.db.exists(
"User Permission", {"allow": "Employee", "for_value": self.name, "user": self.user_id}
)
if employee_user_permission_exists:
return
add_user_permission("Employee", self.name, self.user_id)
set_user_permission_if_allowed("Company", self.company, self.user_id)
def update_user(self):
# add employee role if missing
user = frappe.get_doc("User", self.user_id)
user.flags.ignore_permissions = True
if "Employee" not in user.get("roles"):
user.append_roles("Employee")
# copy details like Fullname, DOB and Image to User
if self.employee_name and not (user.first_name and user.last_name):
employee_name = self.employee_name.split(" ")
if len(employee_name) >= 3:
user.last_name = " ".join(employee_name[2:])
user.middle_name = employee_name[1]
elif len(employee_name) == 2:
user.last_name = employee_name[1]
user.first_name = employee_name[0]
if self.date_of_birth:
user.birth_date = self.date_of_birth
if self.gender:
user.gender = self.gender
if self.image:
if not user.user_image:
user.user_image = self.image
try:
frappe.get_doc(
{
"doctype": "File",
"file_url": self.image,
"attached_to_doctype": "User",
"attached_to_name": self.user_id,
}
).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
# already exists
pass
user.save()
def update_approver_role(self):
if self.leave_approver:
user = frappe.get_doc("User", self.leave_approver)
user.flags.ignore_permissions = True
user.add_roles("Leave Approver")
if self.expense_approver:
user = frappe.get_doc("User", self.expense_approver)
user.flags.ignore_permissions = True
user.add_roles("Expense Approver")
def validate_date(self):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
if (
self.date_of_birth
and self.date_of_joining
and getdate(self.date_of_birth) >= getdate(self.date_of_joining)
):
throw(_("Date of Joining must be greater than Date of Birth"))
elif (
self.date_of_retirement
and self.date_of_joining
and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
):
throw(_("Date Of Retirement must be greater than Date of Joining"))
elif (
self.relieving_date
and self.date_of_joining
and (getdate(self.relieving_date) < getdate(self.date_of_joining))
):
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
elif (
self.contract_end_date
and self.date_of_joining
and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
):
throw(_("Contract End Date must be greater than Date of Joining"))
def validate_email(self):
if self.company_email:
validate_email_address(self.company_email, True)
if self.personal_email:
validate_email_address(self.personal_email, True)
def set_preferred_email(self):
preferred_email_field = frappe.scrub(self.prefered_contact_email)
if preferred_email_field:
preferred_email = self.get(preferred_email_field)
self.prefered_email = preferred_email
def validate_status(self):
if self.status == "Left":
reports_to = frappe.db.get_all(
"Employee",
filters={"reports_to": self.name, "status": "Active"},
fields=["name", "employee_name"],
)
if reports_to:
link_to_employees = [
frappe.utils.get_link_to_form("Employee", employee.name, label=employee.employee_name)
for employee in reports_to
]
message = _("The following employees are currently still reporting to {0}:").format(
frappe.bold(self.employee_name)
)
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
message += "</li></ul><br>"
message += _("Please make sure the employees above report to another Active employee.")
throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
if not self.relieving_date:
throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled):
if not self.status == "Active":
return
if enabled is None:
frappe.throw(_("User {0} does not exist").format(self.user_id))
if enabled == 0:
frappe.throw(_("User {0} is disabled").format(self.user_id), EmployeeUserDisabledError)
def validate_duplicate_user_id(self):
employee = frappe.db.sql_list(
"""select name from `tabEmployee` where
user_id=%s and status='Active' and name!=%s""",
(self.user_id, self.name),
)
if employee:
throw(
_("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]),
frappe.DuplicateEntryError,
)
def validate_reports_to(self):
if self.reports_to == self.name:
throw(_("Employee cannot report to himself."))
def on_trash(self):
self.update_nsm_model()
delete_events(self.doctype, self.name)
if frappe.db.exists("Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}):
emp_transfer = frappe.get_doc(
"Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}
)
emp_transfer.db_set("new_employee_id", "")
def validate_preferred_email(self):
if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
def validate_onboarding_process(self):
employee_onboarding = frappe.get_all(
"Employee Onboarding",
filters={
"job_applicant": self.job_applicant,
"docstatus": 1,
"boarding_status": ("!=", "Completed"),
},
)
if employee_onboarding:
doc = frappe.get_doc("Employee Onboarding", employee_onboarding[0].name)
doc.validate_employee_creation()
doc.db_set("employee", self.name)
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
cell_number = cstr(self.get("cell_number"))
prev_number = cstr(prev_doc.get("cell_number"))
if cell_number != prev_number or self.get("user_id") != prev_doc.get("user_id"):
frappe.cache().hdel("employees_with_number", cell_number)
frappe.cache().hdel("employees_with_number", prev_number)
def get_timeline_data(doctype, name):
"""Return timeline for attendance"""
return dict(
frappe.db.sql(
"""select unix_timestamp(attendance_date), count(*)
from `tabAttendance` where employee=%s
and attendance_date > date_sub(curdate(), interval 1 year)
and status in ('Present', 'Half Day')
group by attendance_date""",
name,
)
)
@frappe.whitelist()
def get_retirement_date(date_of_birth=None):
ret = {}
if date_of_birth:
try:
retirement_age = int(frappe.db.get_single_value("HR Settings", "retirement_age") or 60)
dt = add_years(getdate(date_of_birth), retirement_age)
ret = {"date_of_retirement": dt.strftime("%Y-%m-%d")}
except ValueError:
# invalid date
ret = {}
return ret
def validate_employee_role(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
if not frappe.db.get_value("Employee", {"user_id": doc.name}):
frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role"))
doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0])
def update_user_permissions(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
if not has_permission("User Permission", ptype="write", raise_exception=False):
return
employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions()
def get_employee_email(employee_doc):
return (
employee_doc.get("user_id")
or employee_doc.get("personal_email")
or employee_doc.get("company_email")
)
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
else:
holiday_list = ""
company = frappe.db.get_value("Global Defaults", None, "default_company")
if not holiday_list:
holiday_list = frappe.get_cached_value("Company", company, "default_holiday_list")
if not holiday_list and raise_exception:
frappe.throw(
_("Please set a default Holiday List for Employee {0} or Company {1}").format(employee, company)
)
return holiday_list
def is_holiday(
employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False
):
"""
Returns True if given Employee has an holiday on the given date
:param employee: Employee `name`
:param date: Date to check. Will check for today if None
:param raise_exception: Raise an exception if no holiday list found, default is True
:param only_non_weekly: Check only non-weekly holidays, default is False
"""
holiday_list = get_holiday_list_for_employee(employee, raise_exception)
if not date:
date = today()
if not holiday_list:
return False
filters = {"parent": holiday_list, "holiday_date": date}
if only_non_weekly:
filters["weekly_off"] = False
holidays = frappe.get_all("Holiday", fields=["description"], filters=filters, pluck="description")
if with_description:
return len(holidays) > 0, holidays
return len(holidays) > 0
@frappe.whitelist()
def deactivate_sales_person(status=None, employee=None):
if status == "Left":
sales_person = frappe.db.get_value("Sales Person", {"Employee": employee})
if sales_person:
frappe.db.set_value("Sales Person", sales_person, "enabled", 0)
@frappe.whitelist()
def create_user(employee, user=None, email=None):
emp = frappe.get_doc("Employee", employee)
employee_name = emp.employee_name.split(" ")
middle_name = last_name = ""
if len(employee_name) >= 3:
last_name = " ".join(employee_name[2:])
middle_name = employee_name[1]
elif len(employee_name) == 2:
last_name = employee_name[1]
first_name = employee_name[0]
if email:
emp.prefered_email = email
user = frappe.new_doc("User")
user.update(
{
"name": emp.employee_name,
"email": emp.prefered_email,
"enabled": 1,
"first_name": first_name,
"middle_name": middle_name,
"last_name": last_name,
"gender": emp.gender,
"birth_date": emp.date_of_birth,
"phone": emp.cell_number,
"bio": emp.bio,
}
)
user.insert()
return user.name
def get_all_employee_emails(company):
"""Returns list of employee emails either based on user_id or company_email"""
employee_list = frappe.get_all(
"Employee", fields=["name", "employee_name"], filters={"status": "Active", "company": company}
)
employee_emails = []
for employee in employee_list:
if not employee:
continue
user, company_email, personal_email = frappe.db.get_value(
"Employee", employee, ["user_id", "company_email", "personal_email"]
)
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
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, company_email, personal_email = frappe.db.get_value(
"Employee", employee, ["user_id", "company_email", "personal_email"]
)
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
filters = [["status", "=", "Active"]]
if company and company != "All Companies":
filters.append(["company", "=", company])
fields = ["name as value", "employee_name as title"]
if is_root:
parent = ""
if parent and company and parent != company:
filters.append(["reports_to", "=", parent])
else:
filters.append(["reports_to", "=", ""])
employees = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
for employee in employees:
is_expandable = frappe.get_all(doctype, filters=[["reports_to", "=", employee.get("value")]])
employee.expandable = 1 if is_expandable else 0
return employees
def on_doctype_update():
frappe.db.add_index("Employee", ["lft", "rgt"])
def has_user_permission_for_employee(user_name, employee_name):
return frappe.db.exists(
{
"doctype": "User Permission",
"user": user_name,
"allow": "Employee",
"for_value": employee_name,
}
)
def has_upload_permission(doc, ptype="read", user=None):
if not user:
user = frappe.session.user
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
return True
return doc.user_id == user

View File

@@ -0,0 +1,50 @@
from frappe import _
def get_data():
return {
"heatmap": True,
"heatmap_message": _("This is based on the attendance of this Employee"),
"fieldname": "employee",
"non_standard_fieldnames": {"Bank Account": "party", "Employee Grievance": "raised_by"},
"transactions": [
{"label": _("Attendance"), "items": ["Attendance", "Attendance Request", "Employee Checkin"]},
{
"label": _("Leave"),
"items": ["Leave Application", "Leave Allocation", "Leave Policy Assignment"],
},
{
"label": _("Lifecycle"),
"items": [
"Employee Onboarding",
"Employee Transfer",
"Employee Promotion",
"Employee Grievance",
],
},
{
"label": _("Exit"),
"items": ["Employee Separation", "Exit Interview", "Full and Final Statement"],
},
{"label": _("Shift"), "items": ["Shift Request", "Shift Assignment"]},
{"label": _("Expense"), "items": ["Expense Claim", "Travel Request", "Employee Advance"]},
{"label": _("Benefit"), "items": ["Employee Benefit Application", "Employee Benefit Claim"]},
{
"label": _("Payroll"),
"items": [
"Salary Structure Assignment",
"Salary Slip",
"Additional Salary",
"Timesheet",
"Employee Incentive",
"Retention Bonus",
"Bank Account",
],
},
{
"label": _("Training"),
"items": ["Training Event", "Training Result", "Training Feedback", "Employee Skill Map"],
},
{"label": _("Evaluation"), "items": ["Appraisal"]},
],
}

View File

@@ -0,0 +1,9 @@
frappe.listview_settings['Employee'] = {
add_fields: ["status", "branch", "department", "designation","image"],
filters: [["status","=", "Active"]],
get_indicator: function(doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
indicator[1] = {"Active": "green", "Inactive": "red", "Left": "gray", "Suspended": "orange"}[doc.status];
return indicator;
}
};

View File

@@ -0,0 +1,274 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe import _
from frappe.utils import add_days, add_months, comma_sep, getdate, today
from erpnext.hr.doctype.employee.employee import get_all_employee_emails, get_employee_email
from erpnext.hr.utils import get_holidays_for_employee
# -----------------
# HOLIDAY REMINDERS
# -----------------
def send_reminders_in_advance_weekly():
to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
frequency = frappe.db.get_single_value("HR Settings", "frequency")
if not (to_send_in_advance and frequency == "Weekly"):
return
send_advance_holiday_reminders("Weekly")
def send_reminders_in_advance_monthly():
to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
frequency = frappe.db.get_single_value("HR Settings", "frequency")
if not (to_send_in_advance and frequency == "Monthly"):
return
send_advance_holiday_reminders("Monthly")
def send_advance_holiday_reminders(frequency):
"""Send Holiday Reminders in Advance to Employees
`frequency` (str): 'Weekly' or 'Monthly'
"""
if frequency == "Weekly":
start_date = getdate()
end_date = add_days(getdate(), 7)
elif frequency == "Monthly":
# Sent on 1st of every month
start_date = getdate()
end_date = add_months(getdate(), 1)
else:
return
employees = frappe.db.get_all("Employee", filters={"status": "Active"}, pluck="name")
for employee in employees:
holidays = get_holidays_for_employee(
employee, start_date, end_date, only_non_weekly=True, raise_exception=False
)
send_holidays_reminder_in_advance(employee, holidays)
def send_holidays_reminder_in_advance(employee, holidays):
if not holidays:
return
employee_doc = frappe.get_doc("Employee", employee)
employee_email = get_employee_email(employee_doc)
frequency = frappe.db.get_single_value("HR Settings", "frequency")
email_header = _("Holidays this Month.") if frequency == "Monthly" else _("Holidays this Week.")
frappe.sendmail(
recipients=[employee_email],
subject=_("Upcoming Holidays Reminder"),
template="holiday_reminder",
args=dict(
reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(
employee_doc.get("first_name")
),
message=_("Below is the list of upcoming holidays for you:"),
advance_holiday_reminder=True,
holidays=holidays,
frequency=frequency[:-2],
),
header=email_header,
)
# ------------------
# BIRTHDAY REMINDERS
# ------------------
def send_birthday_reminders():
"""Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set."""
to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders"))
if not to_send:
return
employees_born_today = get_employees_who_are_born_today()
for company, birthday_persons in employees_born_today.items():
employee_emails = get_all_employee_emails(company)
birthday_person_emails = [get_employee_email(doc) for doc in birthday_persons]
recipients = list(set(employee_emails) - set(birthday_person_emails))
reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons)
send_birthday_reminder(recipients, reminder_text, birthday_persons, message)
if len(birthday_persons) > 1:
# special email for people sharing birthdays
for person in birthday_persons:
person_email = person["user_id"] or person["personal_email"] or person["company_email"]
others = [d for d in birthday_persons if d != person]
reminder_text, message = get_birthday_reminder_text_and_message(others)
send_birthday_reminder(person_email, reminder_text, others, message)
def get_birthday_reminder_text_and_message(birthday_persons):
if len(birthday_persons) == 1:
birthday_person_text = birthday_persons[0]["name"]
else:
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
person_names = [d["name"] for d in birthday_persons]
birthday_person_text = comma_sep(person_names, frappe._("{0} & {1}"), False)
reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text)
message = _("A friendly reminder of an important date for our team.")
message += "<br>"
message += _("Everyone, lets congratulate {0} on their birthday.").format(birthday_person_text)
return reminder_text, message
def send_birthday_reminder(recipients, reminder_text, birthday_persons, message):
frappe.sendmail(
recipients=recipients,
subject=_("Birthday Reminder"),
template="birthday_reminder",
args=dict(
reminder_text=reminder_text,
birthday_persons=birthday_persons,
message=message,
),
header=_("Birthday Reminder 🎂"),
)
def get_employees_who_are_born_today():
"""Get all employee born today & group them based on their company"""
return get_employees_having_an_event_today("birthday")
def get_employees_having_an_event_today(event_type):
"""Get all employee who have `event_type` today
& group them based on their company. `event_type`
can be `birthday` or `work_anniversary`"""
from collections import defaultdict
# Set column based on event type
if event_type == "birthday":
condition_column = "date_of_birth"
elif event_type == "work_anniversary":
condition_column = "date_of_joining"
else:
return
employees_born_today = frappe.db.multisql(
{
"mariadb": f"""
SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`, `date_of_joining`
FROM `tabEmployee`
WHERE
DAY({condition_column}) = DAY(%(today)s)
AND
MONTH({condition_column}) = MONTH(%(today)s)
AND
YEAR({condition_column}) < YEAR(%(today)s)
AND
`status` = 'Active'
""",
"postgres": f"""
SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image"
FROM "tabEmployee"
WHERE
DATE_PART('day', {condition_column}) = date_part('day', %(today)s)
AND
DATE_PART('month', {condition_column}) = date_part('month', %(today)s)
AND
DATE_PART('year', {condition_column}) < date_part('year', %(today)s)
AND
"status" = 'Active'
""",
},
dict(today=today(), condition_column=condition_column),
as_dict=1,
)
grouped_employees = defaultdict(lambda: [])
for employee_doc in employees_born_today:
grouped_employees[employee_doc.get("company")].append(employee_doc)
return grouped_employees
# --------------------------
# WORK ANNIVERSARY REMINDERS
# --------------------------
def send_work_anniversary_reminders():
"""Send Employee Work Anniversary Reminders if 'Send Work Anniversary Reminders' is checked"""
to_send = int(frappe.db.get_single_value("HR Settings", "send_work_anniversary_reminders"))
if not to_send:
return
employees_joined_today = get_employees_having_an_event_today("work_anniversary")
for company, anniversary_persons in employees_joined_today.items():
employee_emails = get_all_employee_emails(company)
anniversary_person_emails = [get_employee_email(doc) for doc in anniversary_persons]
recipients = list(set(employee_emails) - set(anniversary_person_emails))
reminder_text, message = get_work_anniversary_reminder_text_and_message(anniversary_persons)
send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message)
if len(anniversary_persons) > 1:
# email for people sharing work anniversaries
for person in anniversary_persons:
person_email = person["user_id"] or person["personal_email"] or person["company_email"]
others = [d for d in anniversary_persons if d != person]
reminder_text, message = get_work_anniversary_reminder_text_and_message(others)
send_work_anniversary_reminder(person_email, reminder_text, others, message)
def get_work_anniversary_reminder_text_and_message(anniversary_persons):
if len(anniversary_persons) == 1:
anniversary_person = anniversary_persons[0]["name"]
persons_name = anniversary_person
# Number of years completed at the company
completed_years = getdate().year - anniversary_persons[0]["date_of_joining"].year
anniversary_person += f" completed {get_pluralized_years(completed_years)}"
else:
person_names_with_years = []
names = []
for person in anniversary_persons:
person_text = person["name"]
names.append(person_text)
# Number of years completed at the company
completed_years = getdate().year - person["date_of_joining"].year
person_text += f" completed {get_pluralized_years(completed_years)}"
person_names_with_years.append(person_text)
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
anniversary_person = comma_sep(person_names_with_years, frappe._("{0} & {1}"), False)
persons_name = comma_sep(names, frappe._("{0} & {1}"), False)
reminder_text = _("Today {0} at our Company! 🎉").format(anniversary_person)
message = _("A friendly reminder of an important date for our team.")
message += "<br>"
message += _("Everyone, lets congratulate {0} on their work anniversary!").format(persons_name)
return reminder_text, message
def get_pluralized_years(years):
if years == 1:
return "1 year"
return f"{years} years"
def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
frappe.sendmail(
recipients=recipients,
subject=_("Work Anniversary Reminder"),
template="anniversary_reminder",
args=dict(
reminder_text=reminder_text,
anniversary_persons=anniversary_persons,
message=message,
),
header=_("Work Anniversary Reminder"),
)

View File

@@ -0,0 +1,36 @@
frappe.treeview_settings['Employee'] = {
get_tree_nodes: "erpnext.hr.doctype.employee.employee.get_children",
filters: [
{
fieldname: "company",
fieldtype:"Select",
options: ['All Companies'].concat(erpnext.utils.get_tree_options("company")),
label: __("Company"),
default: erpnext.utils.get_tree_default("company")
}
],
breadcrumb: "Hr",
disable_add_node: true,
get_tree_root: false,
toolbar: [
{ toggle_btn: true },
{
label:__("Edit"),
condition: function(node) {
return !node.is_root;
},
click: function(node) {
frappe.set_route("Form", "Employee", node.data.value);
}
}
],
menu_items: [
{
label: __("New Employee"),
action: function() {
frappe.new_doc("Employee", true);
},
condition: 'frappe.boot.user.can_create.indexOf("Employee") !== -1'
}
],
};

View File

@@ -0,0 +1,99 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
import frappe.utils
import erpnext
from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
test_records = frappe.get_test_records("Employee")
class TestEmployee(unittest.TestCase):
def test_employee_status_left(self):
employee1 = make_employee("test_employee_1@company.com")
employee2 = make_employee("test_employee_2@company.com")
employee1_doc = frappe.get_doc("Employee", employee1)
employee2_doc = frappe.get_doc("Employee", employee2)
employee2_doc.reload()
employee2_doc.reports_to = employee1_doc.name
employee2_doc.save()
employee1_doc.reload()
employee1_doc.status = "Left"
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
def test_employee_status_inactive(self):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
employee = make_employee("test_employee_status@company.com")
employee_doc = frappe.get_doc("Employee", employee)
employee_doc.status = "Inactive"
employee_doc.save()
employee_doc.reload()
make_holiday_list()
frappe.db.set_value(
"Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List"
)
frappe.db.sql(
"""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'"""
)
salary_structure = make_salary_structure(
"Test Inactive Employee Salary Slip",
"Monthly",
employee=employee_doc.name,
company=employee_doc.company,
)
salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
def tearDown(self):
frappe.db.rollback()
def make_employee(user, company=None, **kwargs):
if not frappe.db.get_value("User", user):
frappe.get_doc(
{
"doctype": "User",
"email": user,
"first_name": user,
"new_password": "password",
"send_welcome_email": 0,
"roles": [{"doctype": "Has Role", "role": "Employee"}],
}
).insert()
if not frappe.db.get_value("Employee", {"user_id": user}):
employee = frappe.get_doc(
{
"doctype": "Employee",
"naming_series": "EMP-",
"first_name": user,
"company": company or erpnext.get_default_company(),
"user_id": user,
"date_of_birth": "1990-05-08",
"date_of_joining": "2013-01-01",
"department": frappe.get_all("Department", fields="name")[0].name,
"gender": "Female",
"company_email": user,
"prefered_contact_email": "Company Email",
"prefered_email": user,
"status": "Active",
"employment_type": "Intern",
}
)
if kwargs:
employee.update(kwargs)
employee.insert()
return employee.name
else:
frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active")
return frappe.get_value("Employee", {"employee_name": user}, "name")

View File

@@ -0,0 +1,268 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
from datetime import timedelta
import frappe
from frappe.utils import add_months, getdate
from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change
from erpnext.hr.utils import get_holidays_for_employee
class TestEmployeeReminders(unittest.TestCase):
@classmethod
def setUpClass(cls):
from erpnext.hr.doctype.holiday_list.test_holiday_list import make_holiday_list
# Create a test holiday list
test_holiday_dates = cls.get_test_holiday_dates()
test_holiday_list = make_holiday_list(
"TestHolidayRemindersList",
holiday_dates=[
{"holiday_date": test_holiday_dates[0], "description": "test holiday1"},
{"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
{"holiday_date": test_holiday_dates[2], "description": "test holiday3", "weekly_off": 1},
{"holiday_date": test_holiday_dates[3], "description": "test holiday4"},
{"holiday_date": test_holiday_dates[4], "description": "test holiday5"},
{"holiday_date": test_holiday_dates[5], "description": "test holiday6"},
],
from_date=getdate() - timedelta(days=10),
to_date=getdate() + timedelta(weeks=5),
)
# Create a test employee
test_employee = frappe.get_doc(
"Employee", make_employee("test@gopher.io", company="_Test Company")
)
# Attach the holiday list to employee
test_employee.holiday_list = test_holiday_list.name
test_employee.save()
# Attach to class
cls.test_employee = test_employee
cls.test_holiday_dates = test_holiday_dates
# Employee without holidays in this month/week
test_employee_2 = make_employee("test@empwithoutholiday.io", company="_Test Company")
test_employee_2 = frappe.get_doc("Employee", test_employee_2)
test_holiday_list = make_holiday_list(
"TestHolidayRemindersList2",
holiday_dates=[
{"holiday_date": add_months(getdate(), 1), "description": "test holiday1"},
],
from_date=add_months(getdate(), -2),
to_date=add_months(getdate(), 2),
)
test_employee_2.holiday_list = test_holiday_list.name
test_employee_2.save()
cls.test_employee_2 = test_employee_2
cls.holiday_list_2 = test_holiday_list
@classmethod
def get_test_holiday_dates(cls):
today_date = getdate()
return [
today_date,
today_date - timedelta(days=4),
today_date - timedelta(days=3),
today_date + timedelta(days=1),
today_date + timedelta(days=3),
today_date + timedelta(weeks=3),
]
def setUp(self):
# Clear Email Queue
frappe.db.sql("delete from `tabEmail Queue`")
frappe.db.sql("delete from `tabEmail Queue Recipient`")
def test_is_holiday(self):
from erpnext.hr.doctype.employee.employee import is_holiday
self.assertTrue(is_holiday(self.test_employee.name))
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1]))
self.assertFalse(is_holiday(self.test_employee.name, date=getdate() - timedelta(days=1)))
# Test weekly_off holidays
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2]))
self.assertFalse(
is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True)
)
# Test with descriptions
has_holiday, descriptions = is_holiday(self.test_employee.name, with_description=True)
self.assertTrue(has_holiday)
self.assertTrue("test holiday1" in descriptions)
def test_birthday_reminders(self):
employee = frappe.get_doc(
"Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]
)
employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
employee.company_email = "test@example.com"
employee.company = "_Test Company"
employee.save()
from erpnext.hr.doctype.employee.employee_reminders import (
get_employees_who_are_born_today,
send_birthday_reminders,
)
employees_born_today = get_employees_who_are_born_today()
self.assertTrue(employees_born_today.get("_Test Company"))
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.send_birthday_reminders = 1
hr_settings.save()
send_birthday_reminders()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
def test_work_anniversary_reminders(self):
make_employee(
"test_work_anniversary@gmail.com",
date_of_joining="1998" + frappe.utils.nowdate()[4:],
company="_Test Company",
)
from erpnext.hr.doctype.employee.employee_reminders import (
get_employees_having_an_event_today,
send_work_anniversary_reminders,
)
employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
user_ids.append(entry.user_id)
self.assertTrue("test_work_anniversary@gmail.com" in user_ids)
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.send_work_anniversary_reminders = 1
hr_settings.save()
send_work_anniversary_reminders()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
def test_work_anniversary_reminder_not_sent_for_0_years(self):
make_employee(
"test_work_anniversary_2@gmail.com",
date_of_joining=getdate(),
company="_Test Company",
)
from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today
employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
user_ids.append(entry.user_id)
self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids)
def test_send_holidays_reminder_in_advance(self):
setup_hr_settings("Weekly")
holidays = get_holidays_for_employee(
self.test_employee.get("name"),
getdate(),
getdate() + timedelta(days=3),
only_non_weekly=True,
raise_exception=False,
)
send_holidays_reminder_in_advance(self.test_employee.get("name"), holidays)
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 1)
self.assertTrue("Holidays this Week." in email_queue[0].message)
def test_advance_holiday_reminders_monthly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
setup_hr_settings("Monthly")
# disable emp 2, set same holiday list
frappe.db.set_value(
"Employee",
self.test_employee_2.name,
{"status": "Left", "holiday_list": self.test_employee.holiday_list},
)
send_reminders_in_advance_monthly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
frappe.db.set_value(
"Employee",
self.test_employee_2.name,
{"status": "Active", "holiday_list": self.holiday_list_2.name},
)
def test_advance_holiday_reminders_weekly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
setup_hr_settings("Weekly")
# disable emp 2, set same holiday list
frappe.db.set_value(
"Employee",
self.test_employee_2.name,
{"status": "Left", "holiday_list": self.test_employee.holiday_list},
)
send_reminders_in_advance_weekly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
frappe.db.set_value(
"Employee",
self.test_employee_2.name,
{"status": "Active", "holiday_list": self.holiday_list_2.name},
)
def test_reminder_not_sent_if_no_holdays(self):
setup_hr_settings("Monthly")
# reminder not sent if there are no holidays
holidays = get_holidays_for_employee(
self.test_employee_2.get("name"),
getdate(),
getdate() + timedelta(days=3),
only_non_weekly=True,
raise_exception=False,
)
send_holidays_reminder_in_advance(self.test_employee_2.get("name"), holidays)
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 0)
def setup_hr_settings(frequency=None):
# Get HR settings and enable advance holiday reminders
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.send_holiday_reminders = 1
set_proceed_with_frequency_change()
hr_settings.frequency = frequency or "Weekly"
hr_settings.save()

View File

@@ -0,0 +1,38 @@
[
{
"company": "_Test Company",
"date_of_birth": "1980-01-01",
"date_of_joining": "2010-01-01",
"department": "_Test Department - _TC",
"doctype": "Employee",
"first_name": "_Test Employee",
"gender": "Female",
"naming_series": "_T-Employee-",
"status": "Active",
"user_id": "test@example.com"
},
{
"company": "_Test Company",
"date_of_birth": "1980-01-01",
"date_of_joining": "2010-01-01",
"department": "_Test Department 1 - _TC",
"doctype": "Employee",
"first_name": "_Test Employee 1",
"gender": "Male",
"naming_series": "_T-Employee-",
"status": "Active",
"user_id": "test1@example.com"
},
{
"company": "_Test Company",
"date_of_birth": "1980-01-01",
"date_of_joining": "2010-01-01",
"department": "_Test Department 1 - _TC",
"doctype": "Employee",
"first_name": "_Test Employee 2",
"gender": "Male",
"naming_series": "_T-Employee-",
"status": "Active",
"user_id": "test2@example.com"
}
]

View File

@@ -0,0 +1 @@
Education detail for parent Employee.

View File

@@ -0,0 +1,192 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:45",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "school_univ",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "School/University",
"length": 0,
"no_copy": 0,
"oldfieldname": "school_univ",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "qualification",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Qualification",
"length": 0,
"no_copy": 0,
"oldfieldname": "qualification",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "100px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "level",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Level",
"length": 0,
"no_copy": 0,
"oldfieldname": "level",
"oldfieldtype": "Select",
"options": "Graduate\nPost Graduate\nUnder Graduate",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "year_of_passing",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Year of Passing",
"length": 0,
"no_copy": 0,
"oldfieldname": "year_of_passing",
"oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "class_per",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Class / Percentage",
"length": 0,
"no_copy": 0,
"oldfieldname": "class_per",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "maj_opt_subj",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Major/Optional Subjects",
"length": 0,
"no_copy": 0,
"oldfieldname": "maj_opt_subj",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:59.995464",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Education",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class EmployeeEducation(Document):
pass

View File

@@ -0,0 +1 @@
External work history details of parent Employee.

View File

@@ -0,0 +1,190 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:45",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "company_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"oldfieldname": "company_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "designation",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Designation",
"length": 0,
"no_copy": 0,
"oldfieldname": "designation",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "salary",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Salary",
"length": 0,
"no_copy": 0,
"oldfieldname": "salary",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "address",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Address",
"length": 0,
"no_copy": 0,
"oldfieldname": "address",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "contact",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Contact",
"length": 0,
"no_copy": 0,
"oldfieldname": "contact",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "total_experience",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Total Experience",
"length": 0,
"no_copy": 0,
"oldfieldname": "total_experience",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:00.088980",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee External Work History",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class EmployeeExternalWorkHistory(Document):
pass

View File

@@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Employee Group', {
});

View File

@@ -0,0 +1,162 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:employee_group_name",
"beta": 0,
"creation": "2018-11-19 12:33:31.351364",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_group_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_00",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_list",
"fieldtype": "Table",
"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": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee Group Table",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-12-27 19:52:34.791538",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Group",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class EmployeeGroup(Document):
pass

View File

@@ -0,0 +1,34 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeGroup(unittest.TestCase):
pass
def make_employee_group():
employee = make_employee("testemployee@example.com")
employee_group = frappe.get_doc(
{
"doctype": "Employee Group",
"employee_group_name": "_Test Employee Group",
"employee_list": [{"employee": employee}],
}
)
employee_group_exist = frappe.db.exists("Employee Group", "_Test Employee Group")
if not employee_group_exist:
employee_group.insert()
return employee_group.employee_group_name
else:
return employee_group_exist
def get_employee_group():
employee_group = frappe.db.exists("Employee Group", "_Test Employee Group")
return employee_group

View File

@@ -0,0 +1,46 @@
{
"creation": "2018-11-19 12:39:46.153061",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"user_id"
],
"fields": [
{
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee"
},
{
"fetch_from": "employee.first_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Employee Name"
},
{
"fetch_from": "employee.user_id",
"fieldname": "user_id",
"fieldtype": "Data",
"in_list_view": 1,
"label": "ERPNext User ID",
"read_only": 1
}
],
"istable": 1,
"modified": "2022-02-13 19:44:21.302938",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Group Table",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class EmployeeGroupTable(Document):
pass

View File

@@ -0,0 +1 @@
Work history details of parent Employee within the organization.

View File

@@ -0,0 +1,168 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:45",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "branch",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Branch",
"length": 0,
"no_copy": 0,
"oldfieldname": "branch",
"oldfieldtype": "Select",
"options": "Branch",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Department",
"length": 0,
"no_copy": 0,
"oldfieldname": "department",
"oldfieldtype": "Select",
"options": "Department",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "designation",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Designation",
"length": 0,
"no_copy": 0,
"oldfieldname": "designation",
"oldfieldtype": "Select",
"options": "Designation",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "From Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "from_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "To Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "to_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:00.195130",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Internal Work History",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class EmployeeInternalWorkHistory(Document):
pass

View File

@@ -0,0 +1 @@
Holiday date in Holiday List.

View File

@@ -0,0 +1,60 @@
{
"actions": [],
"creation": "2013-02-22 01:27:46",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"holiday_date",
"column_break_2",
"weekly_off",
"section_break_4",
"description"
],
"fields": [
{
"fieldname": "holiday_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date",
"oldfieldname": "holiday_date",
"oldfieldtype": "Date",
"reqd": 1
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Description",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"default": "0",
"fieldname": "weekly_off",
"fieldtype": "Check",
"label": "Weekly Off"
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-18 19:03:23.507845",
"modified_by": "Administrator",
"module": "HR",
"name": "Holiday",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class Holiday(Document):
pass

View File

@@ -0,0 +1 @@
List of Holidays.

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Holiday List", {
refresh: function(frm) {
if (frm.doc.holidays) {
frm.set_value("total_holidays", frm.doc.holidays.length);
}
},
from_date: function(frm) {
if (frm.doc.from_date && !frm.doc.to_date) {
var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1));
}
}
});
frappe.tour["Holiday List"] = [
{
fieldname: "holiday_list_name",
title: "Holiday List Name",
description: __("Enter a name for this Holiday List."),
},
{
fieldname: "from_date",
title: "From Date",
description: __("Based on your HR Policy, select your leave allocation period's start date"),
},
{
fieldname: "to_date",
title: "To Date",
description: __("Based on your HR Policy, select your leave allocation period's end date"),
},
{
fieldname: "weekly_off",
title: "Weekly Off",
description: __("Select your weekly off day"),
},
{
fieldname: "get_weekly_off_dates",
title: "Add Holidays",
description: __("Click on Add to Holidays. This will populate the holidays table with all the dates that fall on the selected weekly off. Repeat the process for populating the dates for all your weekly holidays"),
},
{
fieldname: "holidays",
title: "Holidays",
description: __("Here, your weekly offs are pre-populated based on the previous selections. You can add more rows to also add public and national holidays individually.")
},
];

View File

@@ -0,0 +1,480 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:holiday_list_name",
"beta": 0,
"creation": "2013-01-10 16:34:14",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holiday_list_name",
"fieldtype": "Data",
"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": "Holiday List Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "holiday_list_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "From Date",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "To Date",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_holidays",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Holidays",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "add_weekly_holidays",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Add Weekly Holidays",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "weekly_off",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Weekly Off",
"length": 0,
"no_copy": 1,
"options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_weekly_off_dates",
"fieldtype": "Button",
"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": "Add to Holidays",
"length": 0,
"no_copy": 0,
"options": "get_weekly_off_dates",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holidays_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Holidays",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "holidays",
"fieldtype": "Table",
"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": "Holidays",
"length": 0,
"no_copy": 0,
"oldfieldname": "holiday_list_details",
"oldfieldtype": "Table",
"options": "Holiday",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "clear_table",
"fieldtype": "Button",
"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": "Clear Table",
"length": 0,
"no_copy": 0,
"options": "clear_table",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "color",
"fieldtype": "Color",
"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": "Color",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-calendar",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-03 07:22:46.474096",
"modified_by": "Administrator",
"module": "HR",
"name": "Holiday List",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"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,
"track_views": 0
}

View File

@@ -0,0 +1,120 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe import _, throw
from frappe.model.document import Document
from frappe.utils import cint, formatdate, getdate, today
class OverlapError(frappe.ValidationError):
pass
class HolidayList(Document):
def validate(self):
self.validate_days()
self.total_holidays = len(self.holidays)
@frappe.whitelist()
def get_weekly_off_dates(self):
self.validate_values()
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
last_idx = max(
[cint(d.idx) for d in self.get("holidays")]
or [
0,
]
)
for i, d in enumerate(date_list):
ch = self.append("holidays", {})
ch.description = _(self.weekly_off)
ch.holiday_date = d
ch.weekly_off = 1
ch.idx = last_idx + i + 1
def validate_values(self):
if not self.weekly_off:
throw(_("Please select weekly off day"))
def validate_days(self):
if getdate(self.from_date) > getdate(self.to_date):
throw(_("To Date cannot be before From Date"))
for day in self.get("holidays"):
if not (getdate(self.from_date) <= getdate(day.holiday_date) <= getdate(self.to_date)):
frappe.throw(
_("The holiday on {0} is not between From Date and To Date").format(
formatdate(day.holiday_date)
)
)
def get_weekly_off_date_list(self, start_date, end_date):
start_date, end_date = getdate(start_date), getdate(end_date)
import calendar
from datetime import timedelta
from dateutil import relativedelta
date_list = []
existing_date_list = []
weekday = getattr(calendar, (self.weekly_off).upper())
reference_date = start_date + relativedelta.relativedelta(weekday=weekday)
existing_date_list = [getdate(holiday.holiday_date) for holiday in self.get("holidays")]
while reference_date <= end_date:
if reference_date not in existing_date_list:
date_list.append(reference_date)
reference_date += timedelta(days=7)
return date_list
@frappe.whitelist()
def clear_table(self):
self.set("holidays", [])
@frappe.whitelist()
def get_events(start, end, filters=None):
"""Returns events for Gantt / Calendar view rendering.
:param start: Start date-time.
:param end: End date-time.
:param filters: Filters (JSON).
"""
if filters:
filters = json.loads(filters)
else:
filters = []
if start:
filters.append(["Holiday", "holiday_date", ">", getdate(start)])
if end:
filters.append(["Holiday", "holiday_date", "<", getdate(end)])
return frappe.get_list(
"Holiday List",
fields=[
"name",
"`tabHoliday`.holiday_date",
"`tabHoliday`.description",
"`tabHoliday List`.color",
],
filters=filters,
update={"allDay": 1},
)
def is_holiday(holiday_list, date=None):
"""Returns true if the given date is a holiday in the given holiday list"""
if date is None:
date = today()
if holiday_list:
return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
else:
return False

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.views.calendar["Holiday List"] = {
field_map: {
"start": "holiday_date",
"end": "holiday_date",
"id": "name",
"title": "description",
"allDay": "allDay"
},
order_by: `from_date`,
get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events",
filters: [
{
'fieldtype': 'Link',
'fieldname': 'holiday_list',
'options': 'Holiday List',
'label': __('Holiday List')
}
]
}

View File

@@ -0,0 +1,15 @@
def get_data():
return {
"fieldname": "holiday_list",
"non_standard_fieldnames": {
"Company": "default_holiday_list",
"Leave Period": "optional_holiday_list",
},
"transactions": [
{
"items": ["Company", "Employee", "Workstation"],
},
{"items": ["Leave Period", "Shift Type"]},
{"items": ["Service Level", "Service Level Agreement"]},
],
}

View File

@@ -0,0 +1,61 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
from contextlib import contextmanager
from datetime import timedelta
import frappe
from frappe.utils import getdate
class TestHolidayList(unittest.TestCase):
def test_holiday_list(self):
today_date = getdate()
test_holiday_dates = [today_date - timedelta(days=5), today_date - timedelta(days=4)]
holiday_list = make_holiday_list(
"test_holiday_list",
holiday_dates=[
{"holiday_date": test_holiday_dates[0], "description": "test holiday"},
{"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
],
)
fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
self.assertEqual(holiday_list.name, fetched_holiday_list)
def make_holiday_list(
name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
):
frappe.delete_doc_if_exists("Holiday List", name, force=1)
doc = frappe.get_doc(
{
"doctype": "Holiday List",
"holiday_list_name": name,
"from_date": from_date,
"to_date": to_date,
"holidays": holiday_dates,
}
).insert()
return doc
@contextmanager
def set_holiday_list(holiday_list, company_name):
"""
Context manager for setting holiday list in tests
"""
try:
company = frappe.get_doc("Company", company_name)
previous_holiday_list = company.default_holiday_list
company.default_holiday_list = holiday_list
company.save()
yield
finally:
# restore holiday list setup
company = frappe.get_doc("Company", company_name)
company.default_holiday_list = previous_holiday_list
company.save()

View File

@@ -0,0 +1,22 @@
[
{
"doctype": "Holiday List",
"from_date": "2013-01-01",
"to_date":"2013-12-31",
"holidays": [
{
"description": "New Year",
"holiday_date": "2013-01-01"
},
{
"description": "Republic Day",
"holiday_date": "2013-01-26"
},
{
"description": "Test Holiday",
"holiday_date": "2013-02-01"
}
],
"holiday_list_name": "_Test Holiday List"
}
]