Jump to content

Repo comfac-hrms ExtensionPoints: Difference between revisions

From Game in the Brain Wiki
"Repo analysis: comfac-hrms and comfac-webshop (14 pages)"
 
"Fix markdown code blocks → wikitext syntaxhighlight in Repo analysis pages"
 
Line 104: Line 104:
'''Current implementation''' (in <code>hrms/overrides/employee_master.py</code>):
'''Current implementation''' (in <code>hrms/overrides/employee_master.py</code>):


```python
<syntaxhighlight lang="python">
from erpnext.setup.doctype.employee.employee import Employee
from erpnext.setup.doctype.employee.employee import Employee


Line 116: Line 116:
         super().validate()  # Call parent
         super().validate()  # Call parent
         # Add custom validation here
         # Add custom validation here
```
</syntaxhighlight>


'''Registration''' (in hooks.py):
'''Registration''' (in hooks.py):


```python
<syntaxhighlight lang="python">
override_doctype_class = {
override_doctype_class = {
     "Employee": "hrms.overrides.employee_master.EmployeeMaster",
     "Employee": "hrms.overrides.employee_master.EmployeeMaster",
}
}
```
</syntaxhighlight>


=== Pattern 2: Add a Document Event Handler ===
=== Pattern 2: Add a Document Event Handler ===
Line 134: Line 134:
# Create function in <code>hrms/hr/utils.py</code>:
# Create function in <code>hrms/hr/utils.py</code>:


```python
<syntaxhighlight lang="python">
def my_custom_leave_handler(doc, method=None):
def my_custom_leave_handler(doc, method=None):
     """doc is the Leave Application document"""
     """doc is the Leave Application document"""
Line 143: Line 143:
             message=f"Your leave from {doc.from_date} to {doc.to_date} was approved."
             message=f"Your leave from {doc.from_date} to {doc.to_date} was approved."
         )
         )
```
</syntaxhighlight>


# Register in <code>hooks.py</code>:
# Register in <code>hooks.py</code>:


```python
<syntaxhighlight lang="python">
doc_events = {
doc_events = {
     "Leave Application": {
     "Leave Application": {
Line 153: Line 153:
     }
     }
}
}
```
</syntaxhighlight>


=== Pattern 3: Add a Client Script ===
=== Pattern 3: Add a Client Script ===
Line 161: Line 161:
'''Create file''' <code>hrms/public/js/custom_leave_form.js</code>:
'''Create file''' <code>hrms/public/js/custom_leave_form.js</code>:


```javascript
<syntaxhighlight lang="javascript">
frappe.ui.form.on('Leave Application', {
frappe.ui.form.on('Leave Application', {
     leave_type: function(frm) {
     leave_type: function(frm) {
Line 169: Line 169:
     }
     }
});
});
```
</syntaxhighlight>


'''Register in hooks.py:'''
'''Register in hooks.py:'''


```python
<syntaxhighlight lang="python">
doctype_js = {
doctype_js = {
     "Leave Application": "public/js/custom_leave_form.js",
     "Leave Application": "public/js/custom_leave_form.js",
}
}
```
</syntaxhighlight>


=== Pattern 4: Custom Field vs Forking ===
=== Pattern 4: Custom Field vs Forking ===
Line 206: Line 206:
'''Example:''' Create file <code>hrms/patches/v16_0/add_new_leave_field.py</code>:
'''Example:''' Create file <code>hrms/patches/v16_0/add_new_leave_field.py</code>:


```python
<syntaxhighlight lang="python">
import frappe
import frappe


Line 221: Line 221:
             "insert_after": "leave_type"
             "insert_after": "leave_type"
         }).insert()
         }).insert()
```
</syntaxhighlight>


'''Register in patches.txt:'''
'''Register in patches.txt:'''


```
<syntaxhighlight lang="text">
hrms.patches.v16_0.add_new_leave_field
hrms.patches.v16_0.add_new_leave_field
```
</syntaxhighlight>


=== When You Need a Patch ===
=== When You Need a Patch ===
Line 249: Line 249:
Add to any controller (e.g., <code>hrms/hr/doctype/leave_application/leave_application.py</code>):
Add to any controller (e.g., <code>hrms/hr/doctype/leave_application/leave_application.py</code>):


```python
<syntaxhighlight lang="python">
import frappe
import frappe
from frappe import _
from frappe import _
Line 266: Line 266:
             fields=["sum(leaves) as balance"]
             fields=["sum(leaves) as balance"]
         )[0].balance or 0
         )[0].balance or 0
```
</syntaxhighlight>


'''Call from client:'''
'''Call from client:'''


```javascript
<syntaxhighlight lang="javascript">
frappe.call({
frappe.call({
     method: 'hrms.hr.doctype.leave_application.leave_application.LeaveApplication.get_leave_balance',
     method: 'hrms.hr.doctype.leave_application.leave_application.LeaveApplication.get_leave_balance',
Line 278: Line 278:
     }
     }
});
});
```
</syntaxhighlight>


== Regional Overrides ==
== Regional Overrides ==
Line 284: Line 284:
The app supports region-specific logic:
The app supports region-specific logic:


```python
<syntaxhighlight lang="python">
regional_overrides = {
regional_overrides = {
     "India": {
     "India": {
Line 291: Line 291:
     },
     },
}
}
```
</syntaxhighlight>


To add ComFac-specific overrides, create <code>hrms/regional/philippines/</code> and register functions.
To add ComFac-specific overrides, create <code>hrms/regional/philippines/</code> and register functions.

Latest revision as of 16:40, 9 March 2026

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.