mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-11 10:55:09 +00:00
Merge pull request #53711 from frappe/mergify/bp/version-16-hotfix/pr-52726
feat(employee): Create User button and form. (backport #52726)
This commit is contained in:
@@ -45,6 +45,64 @@ frappe.ui.form.on("Employee", {
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.fields_dict.date_of_birth.datepicker.update({ maxDate: new Date() });
|
||||
|
||||
if (!frm.is_new() && !frm.doc.user_id) {
|
||||
frm.add_custom_button(__("Create User"), () => {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Create User"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "email",
|
||||
label: __("Email"),
|
||||
reqd: 1,
|
||||
default:
|
||||
frm.doc.prefered_email || frm.doc.company_email || frm.doc.personal_email,
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
fieldname: "create_user_permission",
|
||||
label: __("Create User Permission"),
|
||||
default: 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Create"),
|
||||
primary_action: (values) => {
|
||||
if (!values.email) {
|
||||
frappe.msgprint(__("Email is required to create a user."));
|
||||
return;
|
||||
}
|
||||
|
||||
frappe
|
||||
.call({
|
||||
method: "erpnext.setup.doctype.employee.employee.create_user",
|
||||
args: {
|
||||
employee: frm.doc.name,
|
||||
email: values.email,
|
||||
create_user_permission: values.create_user_permission ? 1 : 0,
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Creating User..."),
|
||||
})
|
||||
.then(() => {
|
||||
dialog.hide();
|
||||
frm.reload_doc();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
create_user_automatically: function (frm) {
|
||||
if (frm.doc.create_user_automatically) {
|
||||
frm.set_value("user_id", "");
|
||||
frm.set_df_property("user_id", "read_only", 1);
|
||||
} else {
|
||||
frm.set_df_property("user_id", "read_only", 0);
|
||||
}
|
||||
},
|
||||
|
||||
prefered_contact_email: function (frm) {
|
||||
@@ -77,24 +135,6 @@ frappe.ui.form.on("Employee", {
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
create_user: function (frm) {
|
||||
if (!frm.doc.prefered_email) {
|
||||
frappe.throw(__("Please enter Preferred Contact Email"));
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.setup.doctype.employee.employee.create_user",
|
||||
args: {
|
||||
employee: frm.doc.name,
|
||||
email: frm.doc.prefered_email,
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __("Creating User..."),
|
||||
callback: function (r) {
|
||||
frm.reload_doc();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
cur_frm.cscript = new erpnext.setup.EmployeeController({
|
||||
|
||||
@@ -28,8 +28,9 @@
|
||||
"status",
|
||||
"erpnext_user",
|
||||
"user_id",
|
||||
"create_user",
|
||||
"create_user_permission",
|
||||
"column_break_xwnm",
|
||||
"create_user_automatically",
|
||||
"company_details_section",
|
||||
"company",
|
||||
"department",
|
||||
@@ -39,19 +40,11 @@
|
||||
"reports_to",
|
||||
"column_break_18",
|
||||
"branch",
|
||||
"employment_details",
|
||||
"scheduled_confirmation_date",
|
||||
"column_break_32",
|
||||
"final_confirmation_date",
|
||||
"contract_end_date",
|
||||
"col_break_22",
|
||||
"notice_number_of_days",
|
||||
"date_of_retirement",
|
||||
"contact_details",
|
||||
"cell_number",
|
||||
"column_break_40",
|
||||
"personal_email",
|
||||
"company_email",
|
||||
"personal_email",
|
||||
"column_break4",
|
||||
"prefered_contact_email",
|
||||
"prefered_email",
|
||||
@@ -101,6 +94,14 @@
|
||||
"external_work_history",
|
||||
"history_in_company",
|
||||
"internal_work_history",
|
||||
"employment_details",
|
||||
"scheduled_confirmation_date",
|
||||
"column_break_32",
|
||||
"final_confirmation_date",
|
||||
"contract_end_date",
|
||||
"col_break_22",
|
||||
"notice_number_of_days",
|
||||
"date_of_retirement",
|
||||
"exit",
|
||||
"resignation_letter_date",
|
||||
"relieving_date",
|
||||
@@ -273,6 +274,7 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.__islocal",
|
||||
"fieldname": "erpnext_user",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "User Details"
|
||||
@@ -285,20 +287,23 @@
|
||||
"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",
|
||||
"depends_on": "eval:doc.user_id || doc.create_user_automatically",
|
||||
"description": "This will restrict user access to other employee records",
|
||||
"fieldname": "create_user_permission",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create User Permission"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.__islocal && !doc.user_id",
|
||||
"description": "Creates a User account for this employee using the Preferred, Company, or Personal email.",
|
||||
"fieldname": "create_user_automatically",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create User Automatically",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"collapsible": 1,
|
||||
@@ -348,6 +353,7 @@
|
||||
{
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Department",
|
||||
"oldfieldname": "department",
|
||||
@@ -377,6 +383,7 @@
|
||||
{
|
||||
"fieldname": "branch",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Branch",
|
||||
"oldfieldname": "branch",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -600,7 +607,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "exit",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Employee Exit",
|
||||
"label": "Exit",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
@@ -816,6 +823,10 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "IBAN",
|
||||
"options": "IBAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xwnm",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
@@ -823,7 +834,7 @@
|
||||
"image_field": "image",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-29 11:52:12.819878",
|
||||
"modified": "2026-03-23 15:26:05.149280",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Employee",
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe.permissions import (
|
||||
get_doc_permissions,
|
||||
remove_user_permission,
|
||||
)
|
||||
from frappe.utils import cstr, getdate, today, validate_email_address
|
||||
from frappe.utils import cint, cstr, getdate, today, validate_email_address
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
from erpnext.utilities.transaction_base import delete_events
|
||||
@@ -23,6 +23,94 @@ class InactiveEmployeeStatusError(frappe.ValidationError):
|
||||
|
||||
|
||||
class Employee(NestedSet):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.setup.doctype.employee_education.employee_education import EmployeeEducation
|
||||
from erpnext.setup.doctype.employee_external_work_history.employee_external_work_history import (
|
||||
EmployeeExternalWorkHistory,
|
||||
)
|
||||
from erpnext.setup.doctype.employee_internal_work_history.employee_internal_work_history import (
|
||||
EmployeeInternalWorkHistory,
|
||||
)
|
||||
|
||||
attendance_device_id: DF.Data | None
|
||||
bank_ac_no: DF.Data | None
|
||||
bank_name: DF.Data | None
|
||||
bio: DF.TextEditor | None
|
||||
blood_group: DF.Literal["", "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]
|
||||
branch: DF.Link | None
|
||||
cell_number: DF.Data | None
|
||||
company: DF.Link
|
||||
company_email: DF.Data | None
|
||||
contract_end_date: DF.Date | None
|
||||
create_user_automatically: DF.Check
|
||||
create_user_permission: DF.Check
|
||||
ctc: DF.Currency
|
||||
current_accommodation_type: DF.Literal["", "Rented", "Owned"]
|
||||
current_address: DF.SmallText | None
|
||||
date_of_birth: DF.Date
|
||||
date_of_issue: DF.Date | None
|
||||
date_of_joining: DF.Date
|
||||
date_of_retirement: DF.Date | None
|
||||
department: DF.Link | None
|
||||
designation: DF.Link | None
|
||||
education: DF.Table[EmployeeEducation]
|
||||
emergency_phone_number: DF.Data | None
|
||||
employee: DF.Data | None
|
||||
employee_name: DF.Data | None
|
||||
employee_number: DF.Data | None
|
||||
encashment_date: DF.Date | None
|
||||
external_work_history: DF.Table[EmployeeExternalWorkHistory]
|
||||
family_background: DF.SmallText | None
|
||||
feedback: DF.SmallText | None
|
||||
final_confirmation_date: DF.Date | None
|
||||
first_name: DF.Data
|
||||
gender: DF.Link
|
||||
health_details: DF.SmallText | None
|
||||
held_on: DF.Date | None
|
||||
holiday_list: DF.Link | None
|
||||
iban: DF.Data | None
|
||||
image: DF.AttachImage | None
|
||||
internal_work_history: DF.Table[EmployeeInternalWorkHistory]
|
||||
last_name: DF.Data | None
|
||||
leave_encashed: DF.Literal["", "Yes", "No"]
|
||||
lft: DF.Int
|
||||
marital_status: DF.Literal["", "Single", "Married", "Divorced", "Widowed"]
|
||||
middle_name: DF.Data | None
|
||||
naming_series: DF.Literal["HR-EMP-"]
|
||||
new_workplace: DF.Data | None
|
||||
notice_number_of_days: DF.Int
|
||||
old_parent: DF.Data | None
|
||||
passport_number: DF.Data | None
|
||||
permanent_accommodation_type: DF.Literal["", "Rented", "Owned"]
|
||||
permanent_address: DF.SmallText | None
|
||||
person_to_be_contacted: DF.Data | None
|
||||
personal_email: DF.Data | None
|
||||
place_of_issue: DF.Data | None
|
||||
prefered_contact_email: DF.Literal["", "Company Email", "Personal Email", "User ID"]
|
||||
prefered_email: DF.Data | None
|
||||
reason_for_leaving: DF.SmallText | None
|
||||
relation: DF.Data | None
|
||||
relieving_date: DF.Date | None
|
||||
reports_to: DF.Link | None
|
||||
resignation_letter_date: DF.Date | None
|
||||
rgt: DF.Int
|
||||
salary_currency: DF.Link | None
|
||||
salary_mode: DF.Literal["", "Bank", "Cash", "Cheque"]
|
||||
salutation: DF.Link | None
|
||||
scheduled_confirmation_date: DF.Date | None
|
||||
status: DF.Literal["Active", "Inactive", "Suspended", "Left"]
|
||||
unsubscribed: DF.Check
|
||||
user_id: DF.Link | None
|
||||
valid_upto: DF.Date | None
|
||||
# end: auto-generated types
|
||||
|
||||
nsm_parent_field = "reports_to"
|
||||
|
||||
def autoname(self):
|
||||
@@ -72,6 +160,16 @@ class Employee(NestedSet):
|
||||
self.validate_for_enabled_user_id(data.get("enabled", 0))
|
||||
self.validate_duplicate_user_id()
|
||||
|
||||
def validate_auto_user_creation(self):
|
||||
if self.create_user_automatically and not (
|
||||
self.prefered_email or self.company_email or self.personal_email
|
||||
):
|
||||
frappe.throw(
|
||||
_("Company or Personal Email is mandatory when 'Create User Automatically' is enabled"),
|
||||
frappe.MandatoryError,
|
||||
title=_("Auto User Creation Error"),
|
||||
)
|
||||
|
||||
def update_nsm_model(self):
|
||||
frappe.utils.nestedset.update_nsm(self)
|
||||
|
||||
@@ -83,6 +181,22 @@ class Employee(NestedSet):
|
||||
self.update_user_permissions()
|
||||
self.reset_employee_emails_cache()
|
||||
|
||||
def before_insert(self):
|
||||
self.validate_auto_user_creation()
|
||||
|
||||
def after_insert(self):
|
||||
if not self.create_user_automatically:
|
||||
return
|
||||
|
||||
if self.user_id:
|
||||
return
|
||||
|
||||
create_user(
|
||||
employee=self.name,
|
||||
email=self.prefered_email or self.company_email or self.personal_email,
|
||||
create_user_permission=self.create_user_permission,
|
||||
)
|
||||
|
||||
def update_user_permissions(self):
|
||||
if not self.has_value_changed("user_id") and not self.has_value_changed("create_user_permission"):
|
||||
return
|
||||
@@ -310,10 +424,17 @@ def deactivate_sales_person(status=None, employee=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_user(employee, user=None, email=None):
|
||||
def create_user(employee: str, email: str | None = None, create_user_permission: int = 0) -> str:
|
||||
emp = frappe.get_doc("Employee", employee)
|
||||
if emp.user_id:
|
||||
frappe.throw(_("Employee {0} already has a linked user").format(emp.name))
|
||||
|
||||
if not email:
|
||||
frappe.throw(_("Email is required to create a user"))
|
||||
|
||||
email = validate_email_address(email, True)
|
||||
employee_name = emp.employee_name.split(" ")
|
||||
first_name = employee_name[0]
|
||||
middle_name = last_name = ""
|
||||
|
||||
if len(employee_name) >= 3:
|
||||
@@ -322,16 +443,10 @@ def create_user(employee, user=None, email=None):
|
||||
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,
|
||||
"email": email,
|
||||
"enabled": 1,
|
||||
"first_name": first_name,
|
||||
"middle_name": middle_name,
|
||||
@@ -342,9 +457,18 @@ def create_user(employee, user=None, email=None):
|
||||
"bio": emp.bio,
|
||||
}
|
||||
)
|
||||
emp.db_set("user_id", email)
|
||||
user.append_roles("Employee")
|
||||
user.insert()
|
||||
|
||||
emp.user_id = user.name
|
||||
emp.create_user_permission = cint(create_user_permission)
|
||||
emp.save()
|
||||
|
||||
if cint(create_user_permission):
|
||||
add_user_permission("Employee", emp.name, user.name)
|
||||
add_user_permission("Company", emp.company, user.name)
|
||||
|
||||
return user.name
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
frappe.listview_settings["Employee"] = {
|
||||
add_fields: ["status", "branch", "department", "designation", "image"],
|
||||
filters: [["status", "=", "Active"]],
|
||||
get_indicator: function (doc) {
|
||||
get_indicator(doc) {
|
||||
return [
|
||||
__(doc.status, null, "Employee"),
|
||||
{ Active: "green", Inactive: "red", Left: "gray", Suspended: "orange" }[doc.status],
|
||||
"status,=," + doc.status,
|
||||
];
|
||||
},
|
||||
|
||||
onload(listview) {
|
||||
if (frappe.perm.has_perm("Employee", 0, "create")) {
|
||||
frappe.db.count("Employee").then((count) => {
|
||||
if (count === 0) {
|
||||
listview.page.add_inner_button(__("Import Employees"), () => {
|
||||
frappe.new_doc("Data Import", {
|
||||
reference_doctype: "Employee",
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,6 +64,58 @@ class TestEmployee(ERPNextTestSuite):
|
||||
self.assertEqual(qb_employee_list, employee_list)
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_create_user_automatically(self):
|
||||
def get_new_employee(email: str, create_user_permission: int):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Employee",
|
||||
"first_name": "Test Auto User 1",
|
||||
"company": "_Test Company",
|
||||
"date_of_birth": "2000-05-08",
|
||||
"date_of_joining": "2013-01-01",
|
||||
"gender": "Female",
|
||||
"personal_email": email,
|
||||
"status": "Active",
|
||||
"create_user_automatically": 1,
|
||||
"create_user_permission": create_user_permission,
|
||||
}
|
||||
).insert()
|
||||
|
||||
employee1 = get_new_employee("test_auto_user1@example.com", True)
|
||||
user = frappe.db.get_value("User", "test_auto_user1@example.com")
|
||||
self.assertTrue(user)
|
||||
self.assertEqual(employee1.user_id, user)
|
||||
|
||||
# Verify user permissions are created
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"User Permission", {"allow": "Employee", "for_value": employee1.name, "user": user}
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"User Permission", {"allow": "Company", "for_value": employee1.company, "user": user}
|
||||
)
|
||||
)
|
||||
|
||||
# Test disabled create_user_permission
|
||||
employee2 = get_new_employee("test_auto_user2@example.com", False)
|
||||
user2 = frappe.db.get_value("User", "test_auto_user2@example.com")
|
||||
self.assertTrue(user2)
|
||||
self.assertEqual(employee2.user_id, user2)
|
||||
|
||||
# Verify user permissions are not created
|
||||
self.assertFalse(
|
||||
frappe.db.exists(
|
||||
"User Permission", {"allow": "Employee", "for_value": employee2.name, "user": user2}
|
||||
)
|
||||
)
|
||||
self.assertFalse(
|
||||
frappe.db.exists(
|
||||
"User Permission", {"allow": "Company", "for_value": employee2.company, "user": user2}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def make_employee(user, company=None, **kwargs):
|
||||
if not frappe.db.get_value("User", user):
|
||||
|
||||
Reference in New Issue
Block a user