# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. from datetime import timedelta from odoo import _, api, fields, models class FpValueRotation(models.Model): """A schedule that rotates a value to the front of the shop's attention. Used to drive "Fundamental of the Day/Week/Month" programs. The admin creates a rotation, picks a set and a period, and enables it. This module does NOT ship an ir.cron — the shop admin wires a cron to call `_cron_advance_rotation` if they want automatic advancement. Many shops prefer to advance the rotation manually at a weekly stand-up. """ _name = 'fusion.plating.value.rotation' _description = 'Fusion Plating — Value Rotation Schedule' _inherit = ['mail.thread'] _order = 'name' name = fields.Char( string='Name', required=True, tracking=True, help='Display name of the rotation, e.g. "Weekly Fundamental".', ) set_id = fields.Many2one( 'fusion.plating.value.set', string='Value Set', required=True, ondelete='cascade', tracking=True, ) rotation_period = fields.Selection( [ ('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ], string='Rotation Period', required=True, default='weekly', tracking=True, ) current_value_id = fields.Many2one( 'fusion.plating.value', string='Current Value', readonly=True, tracking=True, help='The value currently surfaced by this rotation. Updated by ' '_cron_advance_rotation.', domain="[('set_id', '=', set_id)]", ) last_rotation_date = fields.Date( string='Last Rotated On', readonly=True, ) next_rotation_date = fields.Date( string='Next Rotation', compute='_compute_next_rotation_date', ) company_id = fields.Many2one( 'res.company', string='Company', related='set_id.company_id', store=True, readonly=True, ) active = fields.Boolean( string='Active', default=True, ) @api.depends('last_rotation_date', 'rotation_period') def _compute_next_rotation_date(self): for rec in self: if not rec.last_rotation_date: rec.next_rotation_date = fields.Date.context_today(rec) continue delta_days = { 'daily': 1, 'weekly': 7, 'monthly': 30, }.get(rec.rotation_period, 7) rec.next_rotation_date = rec.last_rotation_date + timedelta( days=delta_days ) def action_advance(self): """Manually advance the rotation to the next value. Wired to a form button so a shop that doesn't want cron-driven rotation can move the fundamental forward at a stand-up meeting. """ for rec in self: rec._advance_one() return True def _advance_one(self): """Move this rotation to the next value in the set, wrapping.""" self.ensure_one() values = self.set_id.value_ids.filtered('active').sorted( key=lambda v: (v.sequence, v.number, v.id) ) if not values: return False if not self.current_value_id: next_value = values[0] else: try: idx = list(values).index(self.current_value_id) next_idx = (idx + 1) % len(values) next_value = values[next_idx] except ValueError: next_value = values[0] self.write({ 'current_value_id': next_value.id, 'last_rotation_date': fields.Date.context_today(self), }) self.message_post( body=_('Rotation advanced to %(value)s.') % { 'value': next_value.display_name, } ) return True @api.model def _cron_advance_rotation(self): """Cron entry point. A shop admin can wire an ir.cron to this method. The module intentionally does not ship one — different shops want different cadences. Only advances rotations whose next_rotation_date is today or earlier. """ today = fields.Date.context_today(self) rotations = self.search([('active', '=', True)]) for rotation in rotations: if ( not rotation.last_rotation_date or rotation.next_rotation_date and rotation.next_rotation_date <= today ): rotation._advance_one() return True