# Fusion Clock - Claude Code Instructions > Read together with the repo-root `../CLAUDE.md` for global Odoo 19 rules, asset-cache handling, Supabase KB notes, and shared Fusion conventions. This file is only for the `fusion_clock` module. ## 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. 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: - `pytz` for timezone-safe local day boundaries. - `requests` for Google Geocoding, OpenStreetMap/Nominatim fallback, and IP metadata. - `dateutil.relativedelta` inside pay-period calculations. External browser APIs: - Browser geolocation. - `ipapi.co` fallback geolocation in frontend/backend clock widgets. - Google Maps/Places when `fusion_clock.google_maps_api_key` is 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_clock` - `hr.employee.x_fclk_nfc_card_uid` - `hr.attendance.x_fclk_clock_source` - `res.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`: all `fusion_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: 1. Resolve current user to `hr.employee`. 2. Block if `x_fclk_enable_clock` is false. 3. If `x_fclk_pending_reason` is true, return `requires_reason`. 4. Verify location against allowed active `fusion.clock.location` records. 5. Call Odoo's `_attendance_action_change()`. 6. Write location, distance, source, and optional photo to `hr.attendance`. 7. Log `clock_in`. 8. Create `late_in` penalty when outside grace. 9. Increment/reset on-time streak; log milestone at 5, 10, 20, 50, 100. 10. Notify office user for very-late clock-ins. Clock-out flow: 1. Verify location again. 2. Call `_attendance_action_change()`. 3. Write out-distance. 4. Apply break deduction when configured. 5. Create `early_out` penalty when outside grace. 6. Log `clock_out`. 7. 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_kiosk` and `fusion_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_kiosk` - `fusion_clock.nfc_photo_required` - `fusion_clock.nfc_enroll_password` - `fusion_clock.nfc_kiosk_debug` - `res.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 returns `debounce`. - Photo data URLs are stripped before writing binary fields. - NFC clock-ins write `x_fclk_check_in_photo`; NFC clock-outs write `x_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_id` is set. - Batch reports when `employee_id` is empty. - PDF generation through QWeb reports: - `fusion_clock.action_report_clock_employee` - `fusion_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_user` - `group_fusion_clock_team_lead` - `group_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`, and `fusion.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.css` - `static/src/scss/nfc_kiosk.scss` - `static/src/js/fusion_clock_portal.js` - `static/src/js/fusion_clock_kiosk.js` - `static/src/js/fusion_clock_nfc_kiosk.js` Backend bundle: - `static/src/scss/fusion_clock.scss` - `static/src/js/fusion_clock_systray.js` - `static/src/xml/systray_clock.xml` - `static/src/js/fusion_clock_dashboard.js` - `static/src/xml/fusion_clock_dashboard.xml` - `static/src/js/fusion_clock_location_map.js` - `static/src/js/fusion_clock_location_places.js` - `static/src/xml/fusion_clock_location.xml` Patterns: - Public portal/kiosk JS should use `Interaction` from `@web/public/interaction` and register in `registry.category("public.interactions")`. - Backend OWL client actions and field widgets use standalone `rpc()` from `@web/core/network/rpc`. - `fusion_clock_systray.js` is a systray OWL component registered as `fusion_clock.ClockSystray`. - `fusion_clock_dashboard.js` is a client action registered as `fusion_clock.Dashboard`. - Location widgets are registered field widgets: `fclk_location_map` and `fclk_places_autocomplete`. Known technical debt: - `static/src/js/fusion_clock_nfc_kiosk.js` is currently an isolated IIFE. If touching it, prefer migrating to an Odoo 19 `Interaction` instead of expanding the IIFE pattern. - `static/src/css/portal_clock.css` and `static/src/scss/fusion_clock.scss` contain runtime dark-mode selectors/media rules. For backend SCSS changes, follow the repo-root Odoo 19 compile-time dark bundle guidance. - `fusion_clock.scss` uses 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//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_hours` is computed from Odoo `worked_hours` minus 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_fallback` exists 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 return `no_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__.py` version 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: ```bash docker exec odoo-dev-app odoo -d fusion-dev -u fusion_clock --test-tags fusion_clock --stop-after-init ``` For a normal module upgrade: ```bash docker exec odoo-dev-app odoo -d fusion-dev -u fusion_clock --stop-after-init ```