150 lines
4.7 KiB
Python
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
|