# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) from odoo import models, fields, api, _ from odoo.exceptions import ValidationError class FusionClockBreakRule(models.Model): _name = 'fusion.clock.break.rule' _description = 'Statutory Break Rule' _order = 'sequence, name' name = fields.Char(string='Name', required=True) country_id = fields.Many2one('res.country', string='Country') state_id = fields.Many2one( 'res.country.state', string='Province / State', help="Employees whose company is in this province use this rule.", ) is_default = fields.Boolean( string='Default Rule', help="Used when an employee's company province matches no other rule. " "Only one active rule may be the default.", ) break1_after_hours = fields.Float( string='First Break After (h)', default=5.0, help="Worked hours at or above this trigger the first unpaid break.", ) break1_minutes = fields.Float( string='First Break (min)', default=30.0, help="Length of the first unpaid break. 0 disables it.", ) break2_after_hours = fields.Float( string='Second Break After (h)', default=10.0, help="Worked hours at or above this add the second unpaid break.", ) break2_minutes = fields.Float( string='Second Break (min)', default=30.0, help="Length of the second unpaid break. 0 disables it.", ) sequence = fields.Integer(default=10) active = fields.Boolean(default=True) def break_minutes_for(self, worked_hours): """Total statutory unpaid break (minutes) for the given worked hours. Tiers are inclusive (``>=``): a break applies when worked hours are equal to or greater than the threshold. The second tier adds on top of the first. """ self.ensure_one() worked = worked_hours or 0.0 total = 0.0 if self.break1_minutes and worked >= self.break1_after_hours: total += self.break1_minutes if self.break2_minutes and worked >= self.break2_after_hours: total += self.break2_minutes return total @api.constrains('break1_after_hours', 'break1_minutes', 'break2_after_hours', 'break2_minutes') def _check_tiers(self): for rule in self: if min(rule.break1_after_hours, rule.break1_minutes, rule.break2_after_hours, rule.break2_minutes) < 0: raise ValidationError(_("Break hours and minutes cannot be negative.")) if rule.break2_minutes and rule.break2_after_hours <= rule.break1_after_hours: raise ValidationError(_( "The second break threshold (%(n2)s h) must be greater than " "the first (%(n1)s h).", n2=rule.break2_after_hours, n1=rule.break1_after_hours)) @api.constrains('is_default', 'active') def _check_single_default(self): for rule in self: if rule.is_default and rule.active: dupe = self.search([ ('is_default', '=', True), ('active', '=', True), ('id', '!=', rule.id), ], limit=1) if dupe: raise ValidationError(_( "Only one active break rule can be the default " "(currently: %s).", dupe.name))