From cd0a25ca174d88dac8cc8a9ae637189ceff8ed66 Mon Sep 17 00:00:00 2001 From: Krishna Shirsath Date: Tue, 17 Feb 2026 13:14:31 +0530 Subject: [PATCH] feat(employee): Create User button and form. (cherry picked from commit 3b521b74ea41622ee20ea0e9725fa92dd66205c2) --- erpnext/setup/doctype/employee/employee.js | 66 ++++++--- erpnext/setup/doctype/employee/employee.json | 9 +- erpnext/setup/doctype/employee/employee.py | 140 ++++++++++++++++++- 3 files changed, 182 insertions(+), 33 deletions(-) diff --git a/erpnext/setup/doctype/employee/employee.js b/erpnext/setup/doctype/employee/employee.js index 21d67d70e78..2a525f18f7f 100755 --- a/erpnext/setup/doctype/employee/employee.js +++ b/erpnext/setup/doctype/employee/employee.js @@ -45,6 +45,54 @@ 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.company_email || frm.doc.personal_email || frm.doc.user_id, + }, + { + fieldtype: "Check", + fieldname: "create_user_permission", + label: __("Create User Permission"), + default: 0, + }, + ], + 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(); + }); + } }, prefered_contact_email: function (frm) { @@ -77,24 +125,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({ diff --git a/erpnext/setup/doctype/employee/employee.json b/erpnext/setup/doctype/employee/employee.json index e663913e8d7..4069813e318 100644 --- a/erpnext/setup/doctype/employee/employee.json +++ b/erpnext/setup/doctype/employee/employee.json @@ -28,7 +28,6 @@ "status", "erpnext_user", "user_id", - "create_user", "create_user_permission", "company_details_section", "company", @@ -285,12 +284,6 @@ "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", @@ -823,7 +816,7 @@ "image_field": "image", "is_tree": 1, "links": [], - "modified": "2025-08-29 11:52:12.819878", + "modified": "2026-02-16 13:06:01.752904", "modified_by": "Administrator", "module": "Setup", "name": "Employee", diff --git a/erpnext/setup/doctype/employee/employee.py b/erpnext/setup/doctype/employee/employee.py index 1ecbb4b9ac7..48819a70ef4 100755 --- a/erpnext/setup/doctype/employee/employee.py +++ b/erpnext/setup/doctype/employee/employee.py @@ -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,93 @@ 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_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): @@ -310,9 +397,28 @@ def deactivate_sales_person(status=None, employee=None): @frappe.whitelist() -def create_user(employee, user=None, email=None): +def create_user(employee, user=None, email=None, create_user_permission=0): + if not employee: + frappe.throw(_("Employee is required")) + emp = frappe.get_doc("Employee", employee) + if email: + email = cstr(email).strip().lower() + else: + email = emp.prefered_email + + if not email: + frappe.throw(_("Email is required to create a user")) + + validate_email_address(email, True) + + if emp.user_id: + frappe.throw(_("Employee {0} already has a linked user").format(emp.name)) + + if frappe.db.exists("User", email): + frappe.throw(_("User {0} already exists").format(email)) + employee_name = emp.employee_name.split(" ") middle_name = last_name = "" @@ -324,14 +430,14 @@ def create_user(employee, user=None, email=None): first_name = employee_name[0] - if email: - emp.prefered_email = email + frappe.db.set_value("Employee", emp.name, "user_id", email, update_modified=False) + frappe.db.commit() 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, @@ -340,11 +446,31 @@ def create_user(employee, user=None, email=None): "birth_date": emp.date_of_birth, "phone": emp.cell_number, "bio": emp.bio, + "send_welcome_email": 1, } ) - user.insert() - emp.user_id = user.name + user.append_roles("Employee") + user.insert(ignore_permissions=True) + + emp.reload() + emp.company_email = email + if not emp.prefered_contact_email: + emp.prefered_contact_email = "Company Email" emp.save() + + if cint(create_user_permission): + if not frappe.db.exists( + "User Permission", + {"allow": "Employee", "for_value": emp.name, "user": user.name}, + ): + add_user_permission("Employee", emp.name, user.name) + + if not frappe.db.exists( + "User Permission", + {"allow": "Company", "for_value": emp.company, "user": user.name}, + ): + add_user_permission("Company", emp.company, user.name) + return user.name