first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
'name': "kpi_project_management",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'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': ['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 KpiProjectManagement(http.Controller):
|
||||
# @http.route('/kpi_project_management/kpi_project_management', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('/kpi_project_management/kpi_project_management/objects', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('kpi_project_management.listing', {
|
||||
# 'root': '/kpi_project_management/kpi_project_management',
|
||||
# 'objects': http.request.env['kpi_project_management.kpi_project_management'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('/kpi_project_management/kpi_project_management/objects/<model("kpi_project_management.kpi_project_management"):obj>', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('kpi_project_management.object', {
|
||||
# 'object': obj
|
||||
# })
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="kpi_project_management.kpi_project_management">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="kpi_project_management.kpi_project_management">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="kpi_project_management.kpi_project_management">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="kpi_project_management.kpi_project_management">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="kpi_project_management.kpi_project_management">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,102 @@
|
||||
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.
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_kpi_project_management_kpi_project_management,kpi_project_management.kpi_project_management,model_kpi_project_management_kpi_project_management,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,32 @@
|
||||
<odoo>
|
||||
<record id="view_task_form2_inherit_kpi" model="ir.ui.view">
|
||||
<field name="name">project.task.form.inherit.kpi</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='description_page']" position="after">
|
||||
<page string="KPI Settings" name="kpi_settings">
|
||||
<group string="KPI Configuration">
|
||||
<group>
|
||||
<field name="type_of_kpi"/>
|
||||
<field name="dateline_of_kpi"/>
|
||||
</group>
|
||||
<group>
|
||||
<!-- Visibility Logic: Show Target if Type is Number or Percentage -->
|
||||
<field name="total_number_kpi" invisible="type_of_kpi == 'other'" required="type_of_kpi in ('number', 'percentage')"/>
|
||||
<!-- Visibility Logic: Show Achieved if Type is Number or Percentage -->
|
||||
<field name="number_of_kpi" invisible="type_of_kpi == 'other'" required="type_of_kpi in ('number', 'percentage')"/>
|
||||
<!-- Readonly Computed Field -->
|
||||
<field name="kpi_achievement_rate" widget="progressbar"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
<!-- Optional: Show KPI Deadline in the tree (list) view for quick overview -->
|
||||
<xpath expr="//field[@name='date_deadline']" position="after">
|
||||
<field name="dateline_of_kpi" optional="hide"/>
|
||||
<field name="kpi_achievement_rate" widget="progressbar" optional="hide"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user