Repo comfac-hrms ExtensionPoints
Repo:comfac-hrms/ExtensionPoints — Extension & Customization
The Safe Edit Zone
Frappe Override Pattern
Frappe HR uses a "don't touch core, override instead" pattern. The safe places to customize are:
| Location | What to Put There |
|---|---|
hrms/overrides/ |
Python classes that extend/override ERPNext behavior |
hrms/public/js/erpnext/ |
Client scripts for ERPNext DocTypes |
hrms/hooks.py |
Hook registrations (add your functions here) |
| Custom Fields (via UI) | Non-core fields added through Frappe desk |
hrms/patches/ |
Migration scripts for data changes |
Files You Should NOT Edit Directly
- ERPNext core files (in separate repo)
- Frappe framework files
- Generated files in
public/dist/ - Third-party libraries in
node_modules/
Hooks & Events
How Hooks Work
The hrms/hooks.py file registers event handlers. When a document event fires, Frappe calls the registered functions.
Key Hook Categories
| Hook Type | Purpose | Example in comfac-hrms |
|---|---|---|
doctype_js |
Attach client scripts to forms | Employee, Timesheet, Company |
override_doctype_class |
Replace Python controller class | EmployeeMaster, EmployeeTimesheet |
doc_events |
Server-side lifecycle hooks | Validate, on_update, on_submit |
scheduler_events |
Background job scheduling | Daily reminders, hourly attendance |
jinja |
Template helper functions | hrms.utils.get_country
|
Document Event Hooks
Current doc_events from hooks.py:
| DocType | Event | Handler Function | What It Does |
|---|---|---|---|
| User | validate | hrms.overrides.employee_master.update_approver_user_roles |
Sync approver roles |
| Company | validate | hrms.overrides.company.validate_default_accounts |
Validate HR accounts |
| Company | on_update | hrms.overrides.company.make_company_fixtures |
Create default data |
| Timesheet | validate | hrms.hr.utils.validate_active_employee |
Check employee status |
| Payment Entry | on_submit | hrms.hr.doctype.expense_claim...update_payment_for_expense_claim |
Update expense claim |
| Journal Entry | on_submit | hrms.payroll.doctype.salary_slip...unlink_ref_doc_from_salary_slip |
Payroll cleanup |
| Employee | validate | hrms.overrides.employee_master.validate_onboarding_process |
Check onboarding |
| Employee | on_update | hrms.overrides.employee_master.update_approver_role |
Update roles |
| Project | validate | hrms.controllers.employee_boarding_controller.update_employee_boarding_status |
Boarding status |
Scheduled Events
| Frequency | Jobs | Purpose |
|---|---|---|
all (every few minutes) |
Interview reminders | Send upcoming interview emails |
hourly |
Daily work summary emails | Trigger group emails |
hourly_long |
Shift attendance processing | Auto check-in/check-out |
daily |
Birthday reminders, work anniversaries, job closing | Daily HR tasks |
daily_long |
Leave expiry, earned leave allocation, gratuity | Heavy daily tasks |
weekly |
Advance reminder notifications | Weekly reminders |
monthly |
Monthly advance reminders | Monthly notifications |
Override Patterns
Pattern 1: Override a DocType Class
Goal: Change how Employee documents are processed
Current implementation (in hrms/overrides/employee_master.py):
from erpnext.setup.doctype.employee.employee import Employee
class EmployeeMaster(Employee):
def autoname(self):
# Custom naming logic
naming_method = frappe.db.get_single_value("HR Settings", "emp_created_by")
# ... implementation
def validate(self):
super().validate() # Call parent
# Add custom validation here
Registration (in hooks.py):
override_doctype_class = {
"Employee": "hrms.overrides.employee_master.EmployeeMaster",
}
Pattern 2: Add a Document Event Handler
Goal: Run custom code when a Leave Application is submitted
Steps:
- Create function in
hrms/hr/utils.py:
def my_custom_leave_handler(doc, method=None):
"""doc is the Leave Application document"""
if doc.status == "Approved":
frappe.sendmail(
recipients=[doc.employee],
subject="Your leave was approved!",
message=f"Your leave from {doc.from_date} to {doc.to_date} was approved."
)
- Register in
hooks.py:
doc_events = {
"Leave Application": {
"on_submit": "hrms.hr.utils.my_custom_leave_handler"
}
}
Pattern 3: Add a Client Script
Goal: Show/hide fields based on user input
Create file hrms/public/js/custom_leave_form.js:
frappe.ui.form.on('Leave Application', {
leave_type: function(frm) {
// Show medical certificate field for sick leave
frm.toggle_display('medical_certificate',
frm.doc.leave_type === 'Sick Leave');
}
});
Register in hooks.py:
doctype_js = {
"Leave Application": "public/js/custom_leave_form.js",
}
Pattern 4: Custom Field vs Forking
| Approach | When to Use | How |
|---|---|---|
| Custom Field (UI) | Simple fields, no code | Frappe Desk → Customize Form → Add Field |
| Custom Field (Code) | Fields that need to be in git | setup.py or fixtures
|
| Edit DocType JSON | Core app changes | Edit */doctype/*/*.json (requires migrate)
|
| Override controller | Change behavior | Python override class |
Patch System
Where Patches Live
hrms/patches/ organized by version:
v14_0/v15_0/v16_0/
How to Write a Migration Patch
Example: Create file hrms/patches/v16_0/add_new_leave_field.py:
import frappe
def execute():
"""Add custom field to Leave Application"""
if not frappe.db.exists("Custom Field", "Leave Application-custom_reason_code"):
frappe.get_doc({
"doctype": "Custom Field",
"dt": "Leave Application",
"fieldname": "custom_reason_code",
"label": "Reason Code",
"fieldtype": "Select",
"options": "Personal\nMedical\nFamily",
"insert_after": "leave_type"
}).insert()
Register in patches.txt:
hrms.patches.v16_0.add_new_leave_field
When You Need a Patch
| Scenario | Solution |
|---|---|
| Adding a new field to existing DocType | Patch + bench migrate
|
| Modifying existing data | Patch with frappe.db.sql() or frappe.get_doc()
|
| Creating new DocTypes | Just add files, bench migrate syncs automatically
|
| Changing field properties | Patch or edit JSON + migrate |
Adding New API Endpoints
Whitelisted Methods
Add to any controller (e.g., hrms/hr/doctype/leave_application/leave_application.py):
import frappe
from frappe import _
class LeaveApplication(Document):
# ... existing code ...
@frappe.whitelist()
def get_leave_balance(self):
"""API endpoint to get current leave balance"""
return frappe.get_all("Leave Ledger Entry",
filters={
"employee": self.employee,
"leave_type": self.leave_type
},
fields=["sum(leaves) as balance"]
)[0].balance or 0
Call from client:
frappe.call({
method: 'hrms.hr.doctype.leave_application.leave_application.LeaveApplication.get_leave_balance',
args: { docname: frm.doc.name },
callback: function(r) {
console.log('Balance:', r.message);
}
});
Regional Overrides
The app supports region-specific logic:
regional_overrides = {
"India": {
"hrms.hr.utils.calculate_annual_eligible_hra_exemption":
"hrms.regional.india.utils.calculate_annual_eligible_hra_exemption",
},
}
To add ComFac-specific overrides, create hrms/regional/philippines/ and register functions.
Related Pages
- Repo_comfac-hrms_Overview — Project overview
- Repo_comfac-hrms_DirectoryMap — Directory structure
- Repo_comfac-hrms_EntryPoints — How app starts, config files
- Repo_comfac-hrms_DataModel — DocTypes and data structures
- Repo_comfac-hrms_CommonEdits — Common edit recipes