This commit is contained in:
gsinghpal
2026-02-23 00:32:20 -05:00
parent d6bac8e623
commit e8e554de95
549 changed files with 1330 additions and 124935 deletions

View File

@@ -15,20 +15,13 @@ class HrAttendance(models.Model):
x_fclk_location_id = fields.Many2one(
'fusion.clock.location',
string='Clock-In Location',
string='Clock Location',
help="The geofenced location where employee clocked in.",
)
x_fclk_out_location_id = fields.Many2one(
'fusion.clock.location',
string='Clock-Out Location',
help="The geofenced location where employee clocked out.",
)
x_fclk_clock_source = fields.Selection(
[
('portal', 'Portal'),
('portal_fab', 'Portal FAB'),
('systray', 'Systray'),
('backend_fab', 'Backend FAB'),
('kiosk', 'Kiosk'),
('manual', 'Manual'),
('auto', 'Auto Clock-Out'),
@@ -73,90 +66,6 @@ class HrAttendance(models.Model):
help="Whether the grace period was consumed before auto clock-out.",
)
# -- Pay period grouping fields --
x_fclk_pay_period = fields.Char(
string='Pay Period',
compute='_compute_pay_period',
store=True,
help="Human-readable pay period label for grouping.",
)
x_fclk_pay_period_start = fields.Date(
string='Period Start',
compute='_compute_pay_period',
store=True,
help="Pay period start date, used for chronological ordering.",
)
# ------------------------------------------------------------------
# CRUD overrides: auto-apply break on any attendance with check_out
# ------------------------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records._auto_apply_break()
return records
def write(self, vals):
res = super().write(vals)
if 'check_out' in vals or 'worked_hours' in vals:
self._auto_apply_break()
return res
def _auto_apply_break(self):
"""Apply break deduction to completed attendances that don't have it.
Only applies when:
- auto_deduct_break setting is enabled
- check_out is set (completed shift)
- worked_hours >= break threshold
- break_minutes is still 0 (not manually set or already applied)
"""
ICP = self.env['ir.config_parameter'].sudo()
if ICP.get_param('fusion_clock.auto_deduct_break', 'True') != 'True':
return
threshold = float(ICP.get_param('fusion_clock.break_threshold_hours', '5.0'))
for att in self:
if not att.check_out:
continue
if (att.x_fclk_break_minutes or 0) > 0:
continue
if (att.worked_hours or 0) < threshold:
continue
emp = att.employee_id
if emp:
break_min = emp._get_fclk_break_minutes()
att.sudo().write({'x_fclk_break_minutes': break_min})
@api.model
def action_backfill_breaks(self):
"""Apply break deduction to all historical records that are missing it."""
ICP = self.env['ir.config_parameter'].sudo()
threshold = float(ICP.get_param('fusion_clock.break_threshold_hours', '5.0'))
records = self.sudo().search([
('check_out', '!=', False),
('x_fclk_break_minutes', '=', 0),
])
count = 0
for att in records:
if (att.worked_hours or 0) < threshold:
continue
emp = att.employee_id
if emp:
break_min = emp._get_fclk_break_minutes()
att.write({'x_fclk_break_minutes': break_min})
count += 1
_logger.info("Fusion Clock: Backfilled break on %d attendance records.", count)
return count
# ------------------------------------------------------------------
@api.depends('worked_hours', 'x_fclk_break_minutes')
def _compute_net_hours(self):
for att in self:
@@ -164,93 +73,6 @@ class HrAttendance(models.Model):
raw = att.worked_hours or 0.0
att.x_fclk_net_hours = max(raw - break_hours, 0.0)
@api.depends('check_in')
def _compute_pay_period(self):
ICP = self.env['ir.config_parameter'].sudo()
schedule_type = ICP.get_param('fusion_clock.pay_period_type', 'biweekly')
anchor_str = ICP.get_param('fusion_clock.pay_period_start', '')
if anchor_str:
try:
anchor = fields.Date.from_string(anchor_str)
except Exception:
anchor = None
else:
anchor = None
for att in self:
if not att.check_in:
att.x_fclk_pay_period = False
att.x_fclk_pay_period_start = False
continue
ref_date = att.check_in.date()
period_start, period_end = self._calc_period(
schedule_type, anchor, ref_date,
)
att.x_fclk_pay_period_start = period_start
att.x_fclk_pay_period = (
f"{period_start.strftime('%b %d')} - "
f"{period_end.strftime('%b %d, %Y')}"
)
@staticmethod
def _calc_period(schedule_type, anchor, ref_date):
"""Calculate pay period start/end for a given date."""
if not anchor:
anchor = ref_date.replace(day=1)
if schedule_type == 'weekly':
days_diff = (ref_date - anchor).days
period_num = days_diff // 7
period_start = anchor + timedelta(days=period_num * 7)
period_end = period_start + timedelta(days=6)
elif schedule_type == 'biweekly':
days_diff = (ref_date - anchor).days
period_num = days_diff // 14
period_start = anchor + timedelta(days=period_num * 14)
period_end = period_start + timedelta(days=13)
elif schedule_type == 'semi_monthly':
if ref_date.day <= 15:
period_start = ref_date.replace(day=1)
period_end = ref_date.replace(day=15)
else:
period_start = ref_date.replace(day=16)
next_month = ref_date.replace(day=28) + timedelta(days=4)
period_end = next_month - timedelta(days=next_month.day)
elif schedule_type == 'monthly':
period_start = ref_date.replace(day=1)
next_month = ref_date.replace(day=28) + timedelta(days=4)
period_end = next_month - timedelta(days=next_month.day)
else:
days_diff = (ref_date - anchor).days
period_num = days_diff // 14
period_start = anchor + timedelta(days=period_num * 14)
period_end = period_start + timedelta(days=13)
return period_start, period_end
@api.model
def _read_group(self, domain, groupby=(), aggregates=(), having=(),
offset=0, limit=None, order=None):
"""Sort pay period groups chronologically (newest first) using the
stored Date field instead of alphabetical Char order."""
if 'x_fclk_pay_period' in groupby:
order = 'x_fclk_pay_period_start:max desc'
return super()._read_group(
domain, groupby, aggregates, having, offset, limit, order,
)
def action_recompute_pay_periods(self):
"""Recompute pay period for all attendance records. Called from settings."""
all_atts = self.sudo().search([])
all_atts._compute_pay_period()
return True
@api.model
def _cron_fusion_auto_clock_out(self):
"""Cron job: auto clock-out employees after shift + grace period.