first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
'name': "inherit_progress_pro",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'license': 'LGPL-3',
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','project'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'views/views.xml',
|
||||
'views/templates.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import controllers
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
# from odoo import http
|
||||
|
||||
|
||||
# class InheritProgressPro(http.Controller):
|
||||
# @http.route('/inherit_progress_pro/inherit_progress_pro', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('/inherit_progress_pro/inherit_progress_pro/objects', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('inherit_progress_pro.listing', {
|
||||
# 'root': '/inherit_progress_pro/inherit_progress_pro',
|
||||
# 'objects': http.request.env['inherit_progress_pro.inherit_progress_pro'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('/inherit_progress_pro/inherit_progress_pro/objects/<model("inherit_progress_pro.inherit_progress_pro"):obj>', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('inherit_progress_pro.object', {
|
||||
# 'object': obj
|
||||
# })
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="inherit_progress_pro.inherit_progress_pro">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="inherit_progress_pro.inherit_progress_pro">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="inherit_progress_pro.inherit_progress_pro">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="inherit_progress_pro.inherit_progress_pro">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="inherit_progress_pro.inherit_progress_pro">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import project_project
|
||||
from . import project_task
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# models/project_project.py
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
progress = fields.Float(
|
||||
string="Project Progress (%)",
|
||||
compute="_compute_project_progress",
|
||||
store=True
|
||||
)
|
||||
|
||||
@api.depends('task_ids.progress', 'task_ids.weight', 'task_ids.parent_id', 'task_ids.task_completed')
|
||||
def _compute_project_progress(self):
|
||||
for project in self:
|
||||
top_level_tasks = project.task_ids.filtered(lambda t: not t.parent_id)
|
||||
|
||||
total_weighted_progress = 0.0
|
||||
total_weight = 0.0
|
||||
|
||||
for task in top_level_tasks:
|
||||
weight = task.weight or 0.0
|
||||
if weight > 0:
|
||||
total_weight += weight
|
||||
total_weighted_progress += weight * (task.progress or 0.0)
|
||||
|
||||
project.progress = (total_weighted_progress / total_weight) if total_weight > 0 else 0.0
|
||||
@@ -0,0 +1,169 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_inherit_progress_pro_inherit_progress_pro,inherit_progress_pro.inherit_progress_pro,model_inherit_progress_pro_inherit_progress_pro,base.group_user,1,1,1,1
|
||||
|
@@ -0,0 +1,24 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<template id="listing">
|
||||
<ul>
|
||||
<li t-foreach="objects" t-as="object">
|
||||
<a t-attf-href="#{ root }/objects/#{ object.id }">
|
||||
<t t-esc="object.display_name"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="object">
|
||||
<h1><t t-esc="object.display_name"/></h1>
|
||||
<dl>
|
||||
<t t-foreach="object._fields" t-as="field">
|
||||
<dt><t t-esc="field"/></dt>
|
||||
<dd><t t-esc="object[field]"/></dd>
|
||||
</t>
|
||||
</dl>
|
||||
</template>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,38 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="inherit_form_task" model="ir.ui.view">
|
||||
<field name="name">បង្កើតកិច្ចការ</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='role_ids']" position="after">
|
||||
<field name="is_manual_weight"/>
|
||||
<field name="progress" widget="progressbar"/>
|
||||
<field name="weight" readonly="is_manual_weight == False"/>
|
||||
<field name="contribution" readonly="1" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="inherit_form_edit_project" model="ir.ui.view">
|
||||
<field name="name">Form Edite</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='tag_ids']" position="before">
|
||||
<field name="progress"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="inherit_kannview_project" model="ir.ui.view">
|
||||
<field name="name">inherit kanban project</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.view_project_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//main[hasclass('o_project_kanban_main')]" position="inside">
|
||||
<span class="ms-1" title="Progress"/>
|
||||
<field name="progress" widget="progressbar" string="Progress"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user