# -*- coding: utf-8 -*- from odoo import models, fields, api import logging _logger = logging.getLogger(__name__) class Task(models.Model): _inherit = 'project.task' # --- Fields --- weight = fields.Float(string="Weight (%)", default=0.0, tracking=True) is_manual_weight = fields.Boolean(string="Manual Weight?", default=False) progress = fields.Float(string="Progress (%)", default=0.0, tracking=True) contribution = fields.Float(string="Contribution", compute="_compute_contribution") # Task Completed checkbox (ADJUST FIELD NAME IF DIFFERENT) task_completed = fields.Boolean(string="Task Completed", default=False, tracking=True) # --- Compute Methods --- @api.depends('progress', 'weight') def _compute_contribution(self): for task in self: task.contribution = (task.progress or 0.0) * (task.weight or 0.0) / 100.0 # --- Core Logic --- def _rebalance_weights(self, changed_task): """Rebalance weights among siblings""" if changed_task.parent_id: siblings = changed_task.parent_id.child_ids else: siblings = changed_task.project_id.task_ids.filtered(lambda t: not t.parent_id) if len(siblings) == 1: siblings.with_context(bypass_weight_balance=True).write({ 'weight': 100.0, 'is_manual_weight': False }) return manual_tasks = siblings.filtered(lambda t: t.is_manual_weight and t != changed_task) auto_tasks = siblings.filtered(lambda t: not t.is_manual_weight) total_manual_weight = sum(manual_tasks.mapped('weight')) if changed_task.is_manual_weight: total_manual_weight += changed_task.weight remaining_for_auto = 100.0 - total_manual_weight new_auto_weight = max(0.0, remaining_for_auto / len(auto_tasks)) if auto_tasks else 0.0 if auto_tasks: auto_tasks.with_context(bypass_weight_balance=True).write({ 'weight': new_auto_weight, 'is_manual_weight': False }) def _rollup_progress(self): """ Roll up progress from subtasks to parent tasks. Call this on subtasks to update their parents. """ for task in self: if task.parent_id: parent = task.parent_id siblings = parent.child_ids if siblings: # Calculate average progress of all subtasks new_progress = sum(s.progress for s in siblings) / len(siblings) # Update parent progress (with bypass to prevent recursion) if abs(parent.progress - new_progress) > 0.01: parent.with_context(bypass_weight_balance=True).write({ 'progress': new_progress }) # Continue rolling up to grandparent parent._rollup_progress() # --- Overrides --- @api.model_create_multi def create(self, vals_list): _logger.info(f"CREATE called with {len(vals_list)} tasks") tasks = super(Task, self).create(vals_list) for task, vals in zip(tasks, vals_list): # Initialize weight if not set if 'weight' not in vals: task._rebalance_weights(task) # If this is a subtask, roll up progress to parent if task.parent_id: task._rollup_progress() _logger.info(f"CREATE completed for tasks: {[t.name for t in tasks]}") return tasks def write(self, vals): _logger.info(f"WRITE called on {self.mapped('name')} with {vals}") _logger.info(f"Bypass context: {self.env.context.get('bypass_weight_balance')}") # CRITICAL: Check bypass flag FIRST to prevent recursion if self.env.context.get('bypass_weight_balance'): _logger.info("WRITE bypassed due to context flag") return super(Task, self).write(vals) is_weight_change = 'weight' in vals is_progress_change = 'progress' in vals is_completion_change = 'task_completed' in vals is_stage_change = 'stage_id' in vals res = super(Task, self).write(vals) for task in self: _logger.info(f"Processing task: {task.name}, completion: {task.task_completed}") # 1. HANDLE TASK COMPLETION → Set Progress to 100% if is_completion_change: if task.task_completed: _logger.info(f"Task {task.name} marked complete, setting progress to 100%") task.with_context(bypass_weight_balance=True).write({ 'progress': 100.0 }) else: # If unchecked and no subtasks, reset progress to 0 if not task.child_ids: task.with_context(bypass_weight_balance=True).write({ 'progress': 0.0 }) # 2. HANDLE STAGE CHANGE → If stage is "Done", set progress to 100% if is_stage_change: if task.stage_id and task.stage_id.name.lower() in ['done', 'completed', 'closed']: task.with_context(bypass_weight_balance=True).write({ 'progress': 100.0 }) elif not task.child_ids: task.with_context(bypass_weight_balance=True).write({ 'progress': 0.0 }) # 3. Handle weight changes if is_weight_change: task.is_manual_weight = True task._rebalance_weights(task) # 4. Handle progress rollup (if task has children) # This ensures parent task progress updates when subtasks change if task.child_ids and (is_progress_change or is_weight_change or is_completion_change or is_stage_change): children = task.child_ids if children: calc_progress = sum(c.progress for c in children) / len(children) _logger.info(f"Task {task.name} has {len(children)} children, calculated progress: {calc_progress}") if abs(task.progress - calc_progress) > 0.01: task.with_context(bypass_weight_balance=True).write({ 'progress': calc_progress }) # 5. Roll up progress to parent (CRITICAL FOR YOUR ISSUE) if (is_progress_change or is_completion_change or is_stage_change) and task.parent_id: _logger.info(f"Rolling up progress from {task.name} to parent {task.parent_id.name}") task._rollup_progress() _logger.info("WRITE completed") return res