359 lines
15 KiB
Markdown
359 lines
15 KiB
Markdown
# 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/<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_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
|
|
```
|