Jump to content

Repo comfac-hrms ExtensionPoints

From Game in the Brain Wiki

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:

  1. 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."
        )
  1. 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.