15 KiB
Fusion Clock - Claude Code Instructions
Read together with the repo-root
../CLAUDE.mdfor global Odoo 19 rules, asset-cache handling, Supabase KB notes, and shared Fusion conventions. This file is only for thefusion_clockmodule.
1. What This Module Is
- Name: Fusion Clock.
- Version:
19.0.3.3.0. - Category: Human Resources/Attendances.
- License: OPL-1, Nexa Systems Inc.
- Purpose: complete time and attendance app built on Odoo
hr.attendance. - Top-level menu:
Fusion Clock. - Main surfaces:
- Portal clock page at
/my/clock. - Portal timesheets at
/my/clock/timesheets. - Portal reports at
/my/clock/reports. - Shared PIN kiosk at
/fusion_clock/kiosk. - NFC tap kiosk at
/fusion_clock/kiosk/nfc. - Backend systray clock widget.
- Backend manager/team-lead dashboard client action.
- Portal clock page at
Core behaviours: geofenced clock-in/out, IP whitelist fallback, shift scheduling, break deduction, penalties, overtime, auto clock-out, absence detection, leave requests, correction workflow, payroll CSV export, PDF reports, weekly summaries, shared kiosk, NFC kiosk with photo capture, and activity audit logs.
2. Dependencies
Declared in __manifest__.py:
hr_attendance, hr, portal, mail, resource
External Python used directly:
pytzfor timezone-safe local day boundaries.requestsfor Google Geocoding, OpenStreetMap/Nominatim fallback, and IP metadata.dateutil.relativedeltainside pay-period calculations.
External browser APIs:
- Browser geolocation.
ipapi.cofallback geolocation in frontend/backend clock widgets.- Google Maps/Places when
fusion_clock.google_maps_api_keyis configured. - Web NFC and camera APIs for the NFC kiosk.
3. Naming And Field Prefixes
This module uses the module-specific prefix x_fclk_* on inherited Odoo models, not x_fc_*.
Examples:
hr.employee.x_fclk_enable_clockhr.employee.x_fclk_nfc_card_uidhr.attendance.x_fclk_clock_sourceres.company.x_fclk_nfc_kiosk_location_id
New inherited fields in this module should keep the x_fclk_* prefix unless there is a strong migration reason not to.
4. Model Map
Custom models:
| Model | File | Purpose |
|---|---|---|
fusion.clock.location |
models/clock_location.py |
Geofenced/IP-whitelisted clock locations. |
fusion.clock.shift |
models/clock_shift.py |
Shift start/end/break schedule assigned to employees. |
fusion.clock.penalty |
models/clock_penalty.py |
Late clock-in / early clock-out penalty records. |
fusion.clock.activity.log |
models/clock_activity_log.py |
Append-style audit log for clock activity, geofence misses, absences, NFC enrolment, corrections. |
fusion.clock.leave.request |
models/clock_leave_request.py |
Portal leave requests, auto-approved but office-notified. |
fusion.clock.correction |
models/clock_correction.py |
Timesheet correction requests with approve/reject workflow. |
fusion.clock.report |
models/clock_report.py |
Employee or batch pay-period report with PDF/CSV export and email send. |
fusion.clock.nfc.enrollment.wizard |
wizard/clock_nfc_enrollment_wizard.py |
Backend NFC card enrolment/reassignment wizard. |
Inherited models:
hr.employee: enable clock, default location, shift, kiosk PIN, NFC UID, pending reason flag, streaks, absence/overtime counters, and One2many links.hr.attendance: clock source, location, distances, photos, break minutes, net hours, penalties, auto clock-out flag, overtime fields.res.config.settings: allfusion_clock.*settings.res.company: NFC kiosk location binding.
Timezone helpers live in models/tz_utils.py. Use get_local_today() and get_local_day_boundaries() for attendance domains instead of comparing UTC dates directly.
5. Clocking Flow
Primary API endpoint: /fusion_clock/clock_action in controllers/clock_api.py.
Clock-in flow:
- Resolve current user to
hr.employee. - Block if
x_fclk_enable_clockis false. - If
x_fclk_pending_reasonis true, returnrequires_reason. - Verify location against allowed active
fusion.clock.locationrecords. - Call Odoo's
_attendance_action_change(). - Write location, distance, source, and optional photo to
hr.attendance. - Log
clock_in. - Create
late_inpenalty when outside grace. - Increment/reset on-time streak; log milestone at 5, 10, 20, 50, 100.
- Notify office user for very-late clock-ins.
Clock-out flow:
- Verify location again.
- Call
_attendance_action_change(). - Write out-distance.
- Apply break deduction when configured.
- Create
early_outpenalty when outside grace. - Log
clock_out. - Log overtime if computed overtime is positive.
Location verification uses GPS when coordinates are available and geocoded locations exist. IP whitelist matching is attempted when a client IP is available. Error types include no_locations, gps_unavailable, no_geocoded, and outside.
6. Kiosk And NFC
Classic kiosk:
- Page:
/fusion_clock/kiosk - JSON routes:
/fusion_clock/kiosk/search/fusion_clock/kiosk/verify_pin/fusion_clock/kiosk/clock
- Requires
fusion_clock.group_fusion_clock_manager. - Controlled by
fusion_clock.enable_kioskandfusion_clock.kiosk_pin_required. - Uses
hr.employee.x_fclk_kiosk_pin.
NFC kiosk:
- Page:
/fusion_clock/kiosk/nfc - JSON routes:
/fusion_clock/kiosk/nfc/enroll/fusion_clock/kiosk/nfc/tap/fusion_clock/kiosk/nfc/employee_search
- Requires
fusion_clock.group_fusion_clock_manager. - Controlled by:
fusion_clock.enable_nfc_kioskfusion_clock.nfc_photo_requiredfusion_clock.nfc_enroll_passwordfusion_clock.nfc_kiosk_debugres.company.x_fclk_nfc_kiosk_location_id
- Card UID canonical format is uppercase colon-separated hex, e.g.
04:A2:B5:62:C1:80. - Normalization lives in
FusionClockNfcKiosk._normalize_uid()and is reused by the backend wizard. - Tap debounce is module-level memory in
controllers/clock_nfc_kiosk.py: same UID within 5 seconds returnsdebounce. - Photo data URLs are stripped before writing binary fields.
- NFC clock-ins write
x_fclk_check_in_photo; NFC clock-outs writex_fclk_check_out_photo.
Important: unknown-card taps currently return card_unknown; the unknown_card_tap log type exists but is not written by the endpoint.
7. Reports And Payroll Export
fusion.clock.report supports:
- Employee reports when
employee_idis set. - Batch reports when
employee_idis empty. - PDF generation through QWeb reports:
fusion_clock.action_report_clock_employeefusion_clock.action_report_clock_batch
- CSV export via
action_export_csv(). - Custom CSV headings via JSON in
fusion_clock.csv_column_mapping. - Email send with generated PDF attached.
Pay period types:
weekly, biweekly, semi_monthly, monthly
The anchor date setting is fusion_clock.pay_period_start as a string in YYYY-MM-DD format.
Historical report generation is exposed through the Generate Historical Reports menu action and creates draft reports for completed attendance periods. The scheduled report cron only generates when yesterday is the period end.
8. Scheduled Automation
Configured in data/ir_cron_data.xml:
| Cron | Model method | Frequency |
|---|---|---|
| Fusion Clock: Auto Clock-Out | hr.attendance._cron_fusion_auto_clock_out() |
Every 15 minutes |
| Fusion Clock: Generate Period Reports | fusion.clock.report._cron_generate_period_reports() |
Daily |
| Fusion Clock: Daily Absence Check | hr.attendance._cron_fusion_check_absences() |
Daily |
| Fusion Clock: Employee Reminders | hr.attendance._cron_fusion_employee_reminders() |
Every 15 minutes |
| Fusion Clock: Weekly Summary | hr.attendance._cron_fusion_weekly_summary() |
Daily, internally sends Mondays |
Auto clock-out closes open attendances after scheduled end plus grace, capped by max shift hours. It sets x_fclk_pending_reason so the employee must explain before clocking in again.
Absence detection checks enabled employees, skips weekends and global resource calendar leaves, and logs absent when no attendance or leave request exists.
9. Security
Groups:
group_fusion_clock_usergroup_fusion_clock_team_leadgroup_fusion_clock_manager
Admin is auto-assigned to manager in security/security.xml.
Access pattern:
- Users and portal users can read their own clock data.
- Team leads can read direct reports for penalties, activity logs, corrections, and dashboard data.
- Managers have full model access and all configuration/kiosk/report menus.
- Portal rules are defined for
hr.attendance,fusion.clock.location,fusion.clock.report,fusion.clock.penalty,fusion.clock.activity.log,fusion.clock.leave.request,fusion.clock.correction, andfusion.clock.shift.
Backend dashboard access is checked in /fusion_clock/dashboard_data: manager sees all enabled employees; team lead sees employees where parent_id is the current user's employee.
10. Frontend Assets
Frontend bundle:
static/src/css/portal_clock.cssstatic/src/scss/nfc_kiosk.scssstatic/src/js/fusion_clock_portal.jsstatic/src/js/fusion_clock_kiosk.jsstatic/src/js/fusion_clock_nfc_kiosk.js
Backend bundle:
static/src/scss/fusion_clock.scssstatic/src/js/fusion_clock_systray.jsstatic/src/xml/systray_clock.xmlstatic/src/js/fusion_clock_dashboard.jsstatic/src/xml/fusion_clock_dashboard.xmlstatic/src/js/fusion_clock_location_map.jsstatic/src/js/fusion_clock_location_places.jsstatic/src/xml/fusion_clock_location.xml
Patterns:
- Public portal/kiosk JS should use
Interactionfrom@web/public/interactionand register inregistry.category("public.interactions"). - Backend OWL client actions and field widgets use standalone
rpc()from@web/core/network/rpc. fusion_clock_systray.jsis a systray OWL component registered asfusion_clock.ClockSystray.fusion_clock_dashboard.jsis a client action registered asfusion_clock.Dashboard.- Location widgets are registered field widgets:
fclk_location_mapandfclk_places_autocomplete.
Known technical debt:
static/src/js/fusion_clock_nfc_kiosk.jsis currently an isolated IIFE. If touching it, prefer migrating to an Odoo 19Interactioninstead of expanding the IIFE pattern.static/src/css/portal_clock.cssandstatic/src/scss/fusion_clock.scsscontain runtime dark-mode selectors/media rules. For backend SCSS changes, follow the repo-root Odoo 19 compile-time dark bundle guidance.fusion_clock.scssuses some Bootstrap CSS vars for status accents. Avoid relying on Bootstrap vars for card/background/border surfaces in new dashboard work.
11. Settings Keys
Important ir.config_parameter keys:
fusion_clock.default_clock_in_time
fusion_clock.default_clock_out_time
fusion_clock.default_break_minutes
fusion_clock.auto_deduct_break
fusion_clock.break_threshold_hours
fusion_clock.enable_auto_clockout
fusion_clock.grace_period_minutes
fusion_clock.max_shift_hours
fusion_clock.enable_penalties
fusion_clock.penalty_grace_minutes
fusion_clock.penalty_deduction_minutes
fusion_clock.enable_overtime
fusion_clock.daily_overtime_threshold
fusion_clock.weekly_overtime_threshold
fusion_clock.office_user_id
fusion_clock.very_late_threshold_minutes
fusion_clock.max_monthly_absences
fusion_clock.enable_employee_notifications
fusion_clock.reminder_before_shift_minutes
fusion_clock.reminder_before_end_minutes
fusion_clock.send_weekly_summary
fusion_clock.enable_ip_fallback
fusion_clock.enable_photo_verification
fusion_clock.google_maps_api_key
fusion_clock.enable_kiosk
fusion_clock.kiosk_pin_required
fusion_clock.enable_correction_requests
fusion_clock.enable_sounds
fusion_clock.pay_period_type
fusion_clock.pay_period_start
fusion_clock.auto_generate_reports
fusion_clock.send_employee_reports
fusion_clock.report_recipient_user_ids
fusion_clock.report_recipient_emails
fusion_clock.csv_column_mapping
fusion_clock.enable_nfc_kiosk
fusion_clock.nfc_photo_required
fusion_clock.nfc_enroll_password
fusion_clock.nfc_kiosk_debug
fclk_report_recipient_user_ids is a Many2many on settings but is persisted manually as comma-separated user IDs in fusion_clock.report_recipient_user_ids.
12. Routes
HTTP pages:
/my/clock
/my/clock/timesheets
/my/clock/reports
/my/clock/reports/<report_id>/download
/fusion_clock/kiosk
/fusion_clock/kiosk/nfc
JSON-RPC endpoints:
/fusion_clock/verify_location
/fusion_clock/clock_action
/fusion_clock/submit_reason
/fusion_clock/request_leave
/fusion_clock/request_correction
/fusion_clock/get_status
/fusion_clock/get_locations
/fusion_clock/get_settings
/fusion_clock/dashboard_data
/fusion_clock/kiosk/search
/fusion_clock/kiosk/verify_pin
/fusion_clock/kiosk/clock
/fusion_clock/kiosk/nfc/enroll
/fusion_clock/kiosk/nfc/tap
/fusion_clock/kiosk/nfc/employee_search
All new JSON endpoints must use type="jsonrpc", not deprecated type="json".
13. Gotchas
- Always use local-day helpers for date domains. UTC midnight boundaries will break attendance totals around timezone offsets.
hr.employee._get_fclk_scheduled_times(date)returns naive UTC datetimes suitable for Odoo comparisons.- Break deduction is stored as minutes in
hr.attendance.x_fclk_break_minutes; penalties add to that same field. x_fclk_net_hoursis computed from Odooworked_hoursminus break minutes.- Daily overtime currently compares net hours to employee scheduled hours or daily threshold; weekly threshold is configured but not used in
hr.attendance._compute_overtime_hours(). fusion_clock.enable_ip_fallbackexists in settings, but server-side_verify_location()attempts IP whitelist matching whenever a client IP is present.- NFC kiosk needs a company-level
x_fclk_nfc_kiosk_location_id; without it taps returnno_location_configured. - Kiosk routes are authenticated (
auth='user') and manager-gated; wall tablets need a manager-authorised kiosk user. - Portal report download manually streams the PDF binary rather than using
fusion_pdf_preview. - If CSS/assets change, bump
__manifest__.pyversion so Odoo rebuilds bundles.
14. Tests
Tests are post-install tagged:
@tagged('-at_install', 'post_install', 'fusion_clock')
Coverage currently focuses on NFC:
tests/test_nfc_models.py: employee UID uniqueness, attendance NFC source/photo fields, company kiosk location field.tests/test_clock_nfc_kiosk.py: kiosk page gating, UID normalization, enroll endpoint, tap happy path, tap errors, photo-required handling, employee search.
Run locally:
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_clock --test-tags fusion_clock --stop-after-init
For a normal module upgrade:
docker exec odoo-dev-app odoo -d fusion-dev -u fusion_clock --stop-after-init