to select ↑↓ to navigate
CAMS Biometric Integration

CAMS Biometric Integration

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:

  1. Go to Setup > Customize Form > Employee Checkin
  2. Add the desired fields (e.g. custom_temperature, custom_face_mask, custom_input_type)
  3. In cams_call.py, extend handle_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@v2 updated to v4 in 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
Last updated 4 days ago
Was this helpful?
Thanks!