Files
2026-04-20 20:31:02 -04:00

150 lines
4.7 KiB
Python

# -*- 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