17. Development Guide
This section is for developers who want to extend, customise, or contribute to the app.
17.0 Repository Structure
navari_cams_biometric/
├── .github/
│ └── workflows/ # CI/CD pipeline (GitHub Actions)
├── navari_cams_biometric/
│ └── cams_biometric/
│ ├── controllers/
│ │ ├── cams_call.py # Core: attendance(), handle_attendance_log(),
│ │ │ # handle_punch_logs(), update_last_sync_time()
│ │ └── test_cams_call.py # Unit + integration tests
│ ├── doctype/
│ │ └── cams_biometric_settings/
│ │ ├── cams_biometric_settings.json # DocType schema
│ │ ├── cams_biometric_settings.py # Server-side logic
│ │ └── cams_biometric_settings.js # Client-side logic
│ └── fixtures/ # Exported DocType fields (e.g. punch_type new field)
├── .cspell.json # Spell checker config for code review
├── .gitignore
├── .pre-commit-config.yaml # Pre-commit hooks (linting, formatting)
├── .releaserc # Semantic release config
├── README.md
├── license.txt # AGPL-3.0
└── pyproject.toml # Python project metadata
17.1 Core Controller: cams_call.py
The heart of the app is navari_cams_biometric/cams_biometric/controllers/cams_call.py.
Key functions:
attendance(): The main entry point. Decorated with @frappe.whitelist(allow_guest=True). Parses the incoming JSON, validates the AuthToken, determines the payload type (RealTime vs PunchLog batch), and dispatches to the appropriate handler.
handle_attendance_log(stgid, log): Handles a single real-time punch log entry. Resolves the employee, creates an Employee Checkin, calls update_last_sync_time().
handle_punch_logs(stgid, logs): Handles a batch of historical punch log entries. Iterates over the list and calls handle_attendance_log() for each.
update_last_sync_time(employee, log_time): Finds the employee's active Shift Assignment and updates its last_sync_datetime field to the punch time.
17.2 Adding Custom Fields
If you need to capture additional data from the CAMS payload (e.g. Temperature, FaceMask, InputType), you can add custom fields to the Employee Checkin doctype:
- Go to Setup > Customize Form > Employee Checkin
- Add the desired fields (e.g.
custom_temperature,custom_face_mask,custom_input_type) - In
cams_call.py, extendhandle_attendance_log()to populate these fields from the payload
Always prefix custom fields with custom_ to follow Frappe conventions and avoid conflicts with future FrappeHR updates.
17.3 Pre-commit Hooks
The project uses pre-commit for code quality:
pip install pre-commit
pre-commit install
Hooks include linting and code formatting. Run manually with:
pre-commit run --all-files
17.4 CI/CD Pipeline
The .github/workflows/ directory contains GitHub Actions workflows that run on push and pull request events. The pipeline:
- Installs MariaDB (pinned version, with a fix committed for the MariaDB client installation)
- Sets up a Frappe bench with ERPNext and HRMS
- Installs
navari_cams_biometric - Runs the test suite
Semantic release is configured via .releaserc and commits using the Conventional Commits format (feat:, fix:, docs:, etc.) automatically trigger version bumps and GitHub release creation.
17.5 Writing Tests
Tests live in test_cams_call.py alongside the controller. Use FrappeTestCase (not Python's unittest.TestCase) to ensure Frappe's database session and setup/teardown lifecycle is respected.
Test philosophy (per maintainer review feedback):
- Avoid excessive mocking: test real database interactions where possible
- Mock only outer dependencies (e.g.
frappe.request,frappe.form_dict) not internal Frappe ORM calls - Use Frappe test helpers like
create_test_employee()to set up fixtures - Verify actual database state after operations (use
frappe.db.exists())
Example test pattern:
from frappe.tests.utils import FrappeTestCase
class TestCamsCall(FrappeTestCase):
def test_handle_attendance_log_creates_checkin(self):
employee = create_test_employee()
log = {
"UserId": employee.attendance_device_id,
"LogTime": "2024-09-17 08:00:00 GMT +0300",
"Type": "CheckIn",
"InputType": "Fingerprint"
}
handle_attendance_log("STG001", log)
self.assertTrue(
frappe.db.exists("Employee Checkin", {"employee": employee.name})
)
17.6 Release History & Changelog
v1.0.0 — December 19, 2025 (Latest)
Features added:
- Punch type field (
MealIn/MealOut): captured separately to avoid corrupting normal attendance records - Logging capability: all key events and errors now produce log entries
- Device ID to Employee mapping hardening: improved resilience when biometric IDs have no matching employee
- Enhanced employee validation and error handling throughout the attendance pipeline
- Improved employee filtering logic in the biometric attendance system
- Fixture export for the new punch type field
- Semantic auto-release pipeline (GitHub Actions + semantic-release)
Bug fixes:
- MariaDB client installation issue in CI/CD fixed
- Pre-commit setup corrected for linters and formatting
- Deprecated
actions/cache@v2updated tov4in GitHub Actions workflow
17.7 Roadmap
The following items are planned or under active development:
In Progress
CAMS Dashboards (Issue #13 / PR #14) A built-in attendance dashboard within FrappeHR showing:
- Real-time check-in/check-out status
- Daily attendance summary by department
- Punch log visualisation by device/location
- Late arrival and early departure metrics
Planned
- Multi-device support: multiple CAMS devices with separate AuthTokens manageable from a single CAMS Biometric Settings page or via a child table
- Webhook retry visibility: surface CAMS Gateway delivery failures in the FrappeHR UI
- Employee self-service: allow employees to view their own biometric check-in history
- More granular punch type mapping: configurable per organisation whether BreakOut/BreakIn count as attendance events