fix(fusion_clock): settings audit — remove 2 dead knobs, make IP-fallback + all Boolean toggles work

Audit of all 41 settings found 3 that were shown but read nowhere, and 17 Boolean
toggles that couldn't be turned OFF.

- Remove grace_period_minutes (orphaned by the schedule-driven cron rewrite) and
  weekly_overtime_threshold (never implemented): field + view + seed.
- enable_ip_fallback now actually gates _verify_location's IP-whitelist check
  (default ON to preserve current behaviour).
- All 17 fusion_clock Boolean settings now persist explicitly as 'True'/'False'
  via a _FCLK_BOOL_PARAMS loop in get_values/set_values (config_parameter Booleans
  can't store False, so OFF never stuck). Add round-trip tests. Bump 3.15.2 -> 3.16.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-31 11:51:40 -04:00
parent 31098c4d14
commit d6d6bbe161
8 changed files with 88 additions and 58 deletions

View File

@@ -23,7 +23,6 @@ class ResConfigSettings(models.TransientModel):
)
fclk_auto_deduct_break = fields.Boolean(
string='Auto-Deduct Break',
config_parameter='fusion_clock.auto_deduct_break',
default=True,
help="Automatically deduct break from worked hours on clock-out.",
)
@@ -43,15 +42,10 @@ class ResConfigSettings(models.TransientModel):
# ── Attendance Rules ───────────────────────────────────────────────
fclk_enable_auto_clockout = fields.Boolean(
string='Enable Auto Clock-Out',
config_parameter='fusion_clock.enable_auto_clockout',
default=True,
help="Automatically clock out employees who forget. Triggers after shift end time plus grace period, or after max shift hours.",
)
fclk_grace_period_minutes = fields.Float(
string='Grace Period (min)',
config_parameter='fusion_clock.grace_period_minutes',
default=15.0,
help="Minutes allowed after scheduled end before auto clock-out.",
help="Automatically clock out employees who forget — closes an attendance "
"left open past the Max Shift Length safety cap (overtime up to the cap "
"is never cut off).",
)
fclk_max_shift_hours = fields.Float(
string='Max Shift Length (hours)',
@@ -64,7 +58,6 @@ class ResConfigSettings(models.TransientModel):
)
fclk_enable_penalties = fields.Boolean(
string='Enable Penalty Tracking',
config_parameter='fusion_clock.enable_penalties',
default=True,
help="Deduct minutes from worked hours when employees clock in late or clock out early.",
)
@@ -82,9 +75,8 @@ class ResConfigSettings(models.TransientModel):
)
fclk_enable_overtime = fields.Boolean(
string='Enable Overtime Tracking',
config_parameter='fusion_clock.enable_overtime',
default=True,
help="Calculate and track overtime when net hours exceed the daily or weekly threshold.",
help="Calculate and track overtime when net hours exceed the daily threshold.",
)
fclk_daily_overtime_threshold = fields.Float(
string='Daily OT Threshold (hours)',
@@ -92,12 +84,6 @@ class ResConfigSettings(models.TransientModel):
default=8.0,
help="Net hours beyond this threshold count as daily overtime.",
)
fclk_weekly_overtime_threshold = fields.Float(
string='Weekly OT Threshold (hours)',
config_parameter='fusion_clock.weekly_overtime_threshold',
default=40.0,
help="Net hours beyond this threshold count as weekly overtime.",
)
# ── Notifications ──────────────────────────────────────────────────
fclk_office_user_id = fields.Many2one(
@@ -119,7 +105,6 @@ class ResConfigSettings(models.TransientModel):
)
fclk_enable_employee_notifications = fields.Boolean(
string='Enable Employee Notifications',
config_parameter='fusion_clock.enable_employee_notifications',
default=True,
help="Send clock-in/out reminders to employees.",
)
@@ -137,7 +122,6 @@ class ResConfigSettings(models.TransientModel):
)
fclk_send_weekly_summary = fields.Boolean(
string='Send Weekly Summary',
config_parameter='fusion_clock.send_weekly_summary',
default=True,
help="Send weekly attendance summary to each employee on Monday.",
)
@@ -145,13 +129,12 @@ class ResConfigSettings(models.TransientModel):
# ── Location & Verification ────────────────────────────────────────
fclk_enable_ip_fallback = fields.Boolean(
string='Enable IP Fallback',
config_parameter='fusion_clock.enable_ip_fallback',
default=False,
help="Allow IP-based location verification when GPS is unavailable.",
default=True,
help="Allow IP-whitelist location verification when GPS is unavailable "
"or outside all geofences.",
)
fclk_enable_photo_verification = fields.Boolean(
string='Enable Photo Verification',
config_parameter='fusion_clock.enable_photo_verification',
default=False,
help="Global toggle for selfie verification on clock-in (per-location control).",
)
@@ -163,25 +146,21 @@ class ResConfigSettings(models.TransientModel):
# ── Kiosk & Portal ─────────────────────────────────────────────────
fclk_enable_kiosk = fields.Boolean(
string='Enable Kiosk Mode',
config_parameter='fusion_clock.enable_kiosk',
default=False,
help="Allow employees to clock in/out from a shared device using their PIN code.",
)
fclk_kiosk_pin_required = fields.Boolean(
string='Require PIN for Kiosk',
config_parameter='fusion_clock.kiosk_pin_required',
default=True,
help="Require employees to enter a PIN when using kiosk mode.",
)
fclk_enable_correction_requests = fields.Boolean(
string='Enable Correction Requests',
config_parameter='fusion_clock.enable_correction_requests',
default=True,
help="Allow employees to request timesheet corrections from the portal.",
)
fclk_enable_sounds = fields.Boolean(
string='Enable Clock Sounds',
config_parameter='fusion_clock.enable_sounds',
default=True,
help="Play audio confirmation sounds when employees clock in or out.",
)
@@ -211,13 +190,11 @@ class ResConfigSettings(models.TransientModel):
)
fclk_auto_generate_reports = fields.Boolean(
string='Auto-Generate Reports',
config_parameter='fusion_clock.auto_generate_reports',
default=True,
help="Automatically create attendance reports at the end of each pay period.",
)
fclk_send_employee_reports = fields.Boolean(
string='Send Employee Copies',
config_parameter='fusion_clock.send_employee_reports',
default=True,
help="Send each employee a copy of their individual attendance report.",
)
@@ -243,13 +220,11 @@ class ResConfigSettings(models.TransientModel):
# ── NFC Clock Kiosk ────────────────────────────────────────────────
fclk_enable_nfc_kiosk = fields.Boolean(
string='Enable NFC Clock Kiosk',
config_parameter='fusion_clock.enable_nfc_kiosk',
default=False,
help="Enable the tap-to-clock NFC kiosk page at /fusion_clock/kiosk/nfc.",
)
fclk_nfc_photo_required = fields.Boolean(
string='Require Photo on Tap',
config_parameter='fusion_clock.nfc_photo_required',
default=True,
help="If enabled, the kiosk rejects taps when the front camera is unavailable. "
"Recommended for buddy-punch deterrence.",
@@ -262,7 +237,6 @@ class ResConfigSettings(models.TransientModel):
)
fclk_nfc_kiosk_debug = fields.Boolean(
string='Debug Mode (overlay + mock-tap)',
config_parameter='fusion_clock.nfc_kiosk_debug',
default=False,
help="Enables two dev/troubleshooting features on the NFC kiosk page: "
"(1) a green-text debug overlay at the top of the screen logging every NFC and tap event in real time, "
@@ -286,9 +260,35 @@ class ResConfigSettings(models.TransientModel):
"Set to 0 to disable the auto-wipe.",
)
# Boolean settings persisted explicitly (NOT via config_parameter): Odoo
# deletes a config param when you write a falsy value, so a config_parameter
# Boolean can never be turned OFF (the row vanishes and get_param returns the
# default). Storing 'True'/'False' strings ourselves makes the toggles work.
_FCLK_BOOL_PARAMS = [
('fclk_auto_deduct_break', 'fusion_clock.auto_deduct_break', True),
('fclk_enable_auto_clockout', 'fusion_clock.enable_auto_clockout', True),
('fclk_enable_penalties', 'fusion_clock.enable_penalties', True),
('fclk_enable_overtime', 'fusion_clock.enable_overtime', True),
('fclk_enable_employee_notifications', 'fusion_clock.enable_employee_notifications', True),
('fclk_send_weekly_summary', 'fusion_clock.send_weekly_summary', True),
('fclk_enable_ip_fallback', 'fusion_clock.enable_ip_fallback', True),
('fclk_enable_photo_verification', 'fusion_clock.enable_photo_verification', False),
('fclk_enable_kiosk', 'fusion_clock.enable_kiosk', False),
('fclk_kiosk_pin_required', 'fusion_clock.kiosk_pin_required', True),
('fclk_enable_correction_requests', 'fusion_clock.enable_correction_requests', True),
('fclk_enable_sounds', 'fusion_clock.enable_sounds', True),
('fclk_auto_generate_reports', 'fusion_clock.auto_generate_reports', True),
('fclk_send_employee_reports', 'fusion_clock.send_employee_reports', True),
('fclk_enable_nfc_kiosk', 'fusion_clock.enable_nfc_kiosk', False),
('fclk_nfc_photo_required', 'fusion_clock.nfc_photo_required', True),
('fclk_nfc_kiosk_debug', 'fusion_clock.nfc_kiosk_debug', False),
]
def set_values(self):
super().set_values()
ICP = self.env['ir.config_parameter'].sudo()
for fname, key, _default in self._FCLK_BOOL_PARAMS:
ICP.set_param(key, 'True' if self[fname] else 'False')
if self.fclk_office_user_id:
ICP.set_param('fusion_clock.office_user_id', str(self.fclk_office_user_id.id))
else:
@@ -308,6 +308,8 @@ class ResConfigSettings(models.TransientModel):
def get_values(self):
res = super().get_values()
ICP = self.env['ir.config_parameter'].sudo()
for fname, key, default in self._FCLK_BOOL_PARAMS:
res[fname] = ICP.get_param(key, 'True' if default else 'False') == 'True'
office_user_id = int(ICP.get_param('fusion_clock.office_user_id', '0'))
if office_user_id:
res['fclk_office_user_id'] = office_user_id