Files

102 lines
4.3 KiB
Python
Raw Permalink Normal View History

2026-07-01 14:41:49 +07:00
from datetime import timedelta
from odoo import models, fields, api
class ProjectTask(models.Model):
_inherit = 'project.task'
# 1. Type of KPI
type_of_kpi = fields.Selection([
('percentage', 'Percentage'),
('number', 'Number'),
('other', 'Other')
], string='KPI Type', default='number')
# 2. Total Number KPI (The Target)
total_number_kpi = fields.Float(string='KPI Target', default=0.0)
# 3. Number of KPI (The Achieved Result)
# This is the main field used for storage
number_of_kpi = fields.Float(string='KPI Achieved', default=0.0)
# 4. Dateline of KPI
dateline_of_kpi = fields.Date(string='KPI Deadline')
# 5. Computed Field: Sum of Sub-tasks KPI
# This shows what the total would be based on children
kpi_achieved_subtask_sum = fields.Float(
string='KPI Sum from Subtasks',
compute='_compute_kpi_achieved_subtask_sum',
store=True
)
# 6. Computed Field: Achievement Rate
kpi_achievement_rate = fields.Float(
string='KPI Achievement (%)',
compute='_compute_kpi_achievement_rate',
store=True
)
@api.depends('child_ids.number_of_kpi')
def _compute_kpi_achieved_subtask_sum(self):
"""Sums the number_of_kpi from all direct sub-tasks"""
for task in self:
task.kpi_achieved_subtask_sum = sum(task.child_ids.mapped('number_of_kpi'))
@api.depends('number_of_kpi', 'total_number_kpi')
def _compute_kpi_achievement_rate(self):
for task in self:
if task.total_number_kpi > 0:
task.kpi_achievement_rate = (task.number_of_kpi / task.total_number_kpi) * 100
else:
task.kpi_achievement_rate = 0.0
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('ks_schedule_mode') == 'auto' and vals.get('ks_constraint_task_type') in ['asap', 'alap']:
# Example: Set start date to today if auto mode
if not vals.get('date_start'):
vals['date_start'] = fields.Datetime.now()
# Example: Auto-calculate deadline based on constraint
if vals.get('ks_constraint_task_type') == 'asap':
vals['date_deadline'] = vals.get('date_start') + timedelta(days=7)
elif vals.get('ks_constraint_task_type') == 'alap':
# ALAP logic here
pass
return super().create(vals_list)
def write(self, vals):
"""Override write to update parent when child KPI changes"""
res = super().write(vals)
# Check if number_of_kpi was updated in this write operation
if 'number_of_kpi' in vals:
for task in self:
if task.parent_id:
# Update the parent's KPI based on all its children
task.parent_id._update_kpi_from_children()
return res
def _update_kpi_from_children(self):
"""Calculates the sum of children KPI and updates the parent's number_of_kpi"""
# Ensure we don't trigger infinite recursion by using a specific context or logic
# Here we directly update the field via sudo to bypass some checks if needed,
# but standard write is safer.
if self.child_ids:
total_achieved = sum(self.child_ids.mapped('number_of_kpi'))
# Only update if different to avoid unnecessary triggers
if self.number_of_kpi != total_achieved:
# We use super().write to avoid triggering this same method again recursively
# However, since we check 'number_of_kpi' in vals, we need to be careful.
# To prevent recursion, we can check if the caller is already updating.
# Simplest way in Odoo for this case:
self.write({'number_of_kpi': total_achieved})
# Note: The write above will trigger this method again for the Grandparent.
# This is desired behavior (cascade update up the tree).
# But we must ensure it doesn't trigger for the children again.
# Since we only check 'if task.parent_id', and the parent doesn't have a parent_id relative to children,
# it won't loop back down.