Public release from ruodoo-project: 19.0 - 2026-05-31 21:19:12 UTC
This commit is contained in:
34
mklab_project_task_indicators/README.md
Normal file
34
mklab_project_task_indicators/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Слои показателей для задач проектов
|
||||
name: mklab_project_task_indicators
|
||||
|
||||
|
||||
### 1. Общее описание
|
||||
Модуль расширяет функционал управления задачами в Odoo Project, добавляя возможность работы с гиперграфными связями и показателями для задач.
|
||||
|
||||
### 2. Установка
|
||||
1.Скопируйте папку модуля в директорию addons Odoo
|
||||
|
||||
2.Перезапустите сервер Odoo
|
||||
|
||||
3.Активируйте модуль через интерфейс администрирования
|
||||
|
||||
### 3. Функциональность
|
||||
3.1 Основные возможности:
|
||||
- Интегрировать задачи проекта в систему гиперграфных связей
|
||||
- Отслеживать показатели эффективности задач
|
||||
- Управлять связями между задачами и другими объектами системы
|
||||
- Проводить комплексный анализ задач с учетом их взаимосвязей
|
||||
|
||||
|
||||
### Пример использования
|
||||
|
||||
Переходим в меню Слои показателей
|
||||

|
||||
Создаем вершины
|
||||

|
||||
Создаем связи между сущностями
|
||||

|
||||
Добавляем показатели
|
||||

|
||||
Проверяем связи по задаче
|
||||

|
||||
3
mklab_project_task_indicators/__init__.py
Normal file
3
mklab_project_task_indicators/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
36
mklab_project_task_indicators/__manifest__.py
Normal file
36
mklab_project_task_indicators/__manifest__.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Слои показателей для задач проектов",
|
||||
|
||||
'summary': """
|
||||
Слои показателей для задач модуля project""",
|
||||
|
||||
'description': """
|
||||
Слои показателей для задач модуля project
|
||||
""",
|
||||
|
||||
'author': "MK.Lab",
|
||||
'website': "https://www.inf-centre.ru",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '19.0.2025.11.14',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base', 'mklab_base_indicators', 'project'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'views/project_task_views.xml',
|
||||
'reports/action.xml',
|
||||
'reports/template.xml'
|
||||
],
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'tests/test_project_task.py',
|
||||
],
|
||||
}
|
||||
46
mklab_project_task_indicators/demo/demo.xml
Normal file
46
mklab_project_task_indicators/demo/demo.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Демо-проекты -->
|
||||
<record id="demo_project_construction" model="project.project">
|
||||
<field name="name">Строительство производственного склада (демо)</field>
|
||||
</record>
|
||||
<record id="demo_project_it" model="project.project">
|
||||
<field name="name">Разработка корпоративной CRM-системы (демо)</field>
|
||||
</record>
|
||||
|
||||
<!-- Задачи проекта (project.task наследует hg.hg_mixin, узел создаётся автоматически) -->
|
||||
<record id="demo_task_design_docs" model="project.task">
|
||||
<field name="name">Разработка проектной документации (демо)</field>
|
||||
<field name="project_id" ref="demo_project_construction"/>
|
||||
<field name="description">Подготовка полного комплекта проектной документации для строительства склада.</field>
|
||||
</record>
|
||||
<record id="demo_task_foundation" model="project.task">
|
||||
<field name="name">Устройство монолитного фундамента (демо)</field>
|
||||
<field name="project_id" ref="demo_project_construction"/>
|
||||
<field name="description">Земляные работы, армирование и заливка монолитного фундамента.</field>
|
||||
</record>
|
||||
<record id="demo_task_roofing" model="project.task">
|
||||
<field name="name">Монтаж кровельного покрытия (демо)</field>
|
||||
<field name="project_id" ref="demo_project_construction"/>
|
||||
<field name="description">Монтаж металлочерепицы и водосточной системы.</field>
|
||||
</record>
|
||||
<record id="demo_task_backend" model="project.task">
|
||||
<field name="name">Разработка серверной части CRM (демо)</field>
|
||||
<field name="project_id" ref="demo_project_it"/>
|
||||
<field name="description">Проектирование и реализация REST API, интеграция с базой данных.</field>
|
||||
</record>
|
||||
<record id="demo_task_frontend" model="project.task">
|
||||
<field name="name">Разработка пользовательского интерфейса CRM (демо)</field>
|
||||
<field name="project_id" ref="demo_project_it"/>
|
||||
<field name="description">Вёрстка и реализация клиентской части на основе утверждённых макетов.</field>
|
||||
</record>
|
||||
<record id="demo_task_testing" model="project.task">
|
||||
<field name="name">Тестирование и приёмка системы (демо)</field>
|
||||
<field name="project_id" ref="demo_project_it"/>
|
||||
<field name="description">Функциональное и нагрузочное тестирование, устранение замечаний.</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
BIN
mklab_project_task_indicators/img.png
Normal file
BIN
mklab_project_task_indicators/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
BIN
mklab_project_task_indicators/img_1.png
Normal file
BIN
mklab_project_task_indicators/img_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
mklab_project_task_indicators/img_2.png
Normal file
BIN
mklab_project_task_indicators/img_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
mklab_project_task_indicators/img_3.png
Normal file
BIN
mklab_project_task_indicators/img_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
mklab_project_task_indicators/img_4.png
Normal file
BIN
mklab_project_task_indicators/img_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
3
mklab_project_task_indicators/models/__init__.py
Normal file
3
mklab_project_task_indicators/models/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import project_task
|
||||
9
mklab_project_task_indicators/models/project_task.py
Normal file
9
mklab_project_task_indicators/models/project_task.py
Normal file
@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = ['project.task', 'hg.hg_mixin']
|
||||
_name = "project.task"
|
||||
|
||||
|
||||
15
mklab_project_task_indicators/reports/action.xml
Normal file
15
mklab_project_task_indicators/reports/action.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="action_report_project" model="ir.actions.report">
|
||||
<field name="name">Сводная ведомость по проекту</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="report_type">qweb-html</field>
|
||||
<field name="report_name">mklab_project_task_indicators.general_report</field>
|
||||
<field name="report_file">mklab_project_task_indicators.general_report</field>
|
||||
<field name="binding_model_id" ref="project.model_project_project"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="print_report_name">'Ведомость для проекта '+str(object.name)</field>
|
||||
<field name="attachment">'Ведомость для проекта '+str(object.name)</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
101
mklab_project_task_indicators/reports/template.xml
Normal file
101
mklab_project_task_indicators/reports/template.xml
Normal file
@ -0,0 +1,101 @@
|
||||
<odoo>
|
||||
<template id="mklab_project_task_indicators.general_report">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<t t-call="web.basic_layout">
|
||||
<div class="header">
|
||||
<style>
|
||||
.separator {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.gen-style {
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="row gen-style">
|
||||
<div class="col-12">
|
||||
<h2>Сводная ведомость по проекту:</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row separator gen-style">
|
||||
<div class="col-12">
|
||||
<h2 t-field="doc.sudo().name"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page">
|
||||
<style>
|
||||
.separator {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.gen-style {
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
}
|
||||
.font-arial {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
.task-name{
|
||||
background-color:lightgray;
|
||||
}
|
||||
</style>
|
||||
<div class="oe_structure"/>
|
||||
<div class="container">
|
||||
<div class="row gen-style">
|
||||
<div class="col-4 font-arial">
|
||||
Наименование показателя
|
||||
</div>
|
||||
<div class="col-4 font-arial">
|
||||
На дату
|
||||
</div>
|
||||
<div class="col-4 font-arial">
|
||||
Плановый показатель
|
||||
</div>
|
||||
</div>
|
||||
<div t-foreach="doc.sudo().task_ids" t-as="task">
|
||||
<t t-if="len(task.index_ids) > 1">
|
||||
<div class="row gen-style">
|
||||
<div class="col-12 font-arial task-name">
|
||||
<span t-field="task.sudo().name"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="task.sudo().index_ids" t-as="value">
|
||||
<div class="col-4 font-arial">
|
||||
<span t-field="value.sudo().name"/>
|
||||
</div>
|
||||
<t t-foreach="value.sudo().value_ids" t-as="h_values">
|
||||
<div class="row separator">
|
||||
<div class="col-4 font-arial"/>
|
||||
<div class="col-4 font-arial">
|
||||
<span t-field="h_values.sudo().date_due" t-options="{"widget": "date","format": "dd.MM.yyyy"}"/>
|
||||
</div>
|
||||
<div class="col-4 font-arial">
|
||||
<span t-field="h_values.sudo().value_float_plan"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-4"><span t-esc="context_timestamp(datetime.datetime.now()).strftime('%d.%m.%Y')"/></div>
|
||||
<div class="col-4">Согласовано</div>
|
||||
<div class="col-4"><span t-field="doc.sudo().user_id"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
BIN
mklab_project_task_indicators/static/description/icon.png
Normal file
BIN
mklab_project_task_indicators/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
4
mklab_project_task_indicators/tests/__init__.py
Normal file
4
mklab_project_task_indicators/tests/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_project_task
|
||||
from . import test_hg_mixin
|
||||
67
mklab_project_task_indicators/tests/test_hg_mixin.py
Normal file
67
mklab_project_task_indicators/tests/test_hg_mixin.py
Normal file
@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestHgMixin(TransactionCase):
|
||||
"""
|
||||
Тестируем mixin через project.task (наследует hg.hg_mixin).
|
||||
Требует установленного модуля mklab_project_task_indicators.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.project = cls.env['project.project'].create({'name': 'Тестовый проект'})
|
||||
|
||||
def test_node_created_on_task_create(self):
|
||||
task = self.env['project.task'].create({
|
||||
'name': 'Задача с узлом',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
self.assertTrue(task.node_id, 'При создании задачи должна создаваться вершина графа')
|
||||
self.assertEqual(task.node_id.res_model, 'project.task')
|
||||
self.assertEqual(task.node_id.res_id, task.id)
|
||||
|
||||
def test_node_name_matches_task(self):
|
||||
task = self.env['project.task'].create({
|
||||
'name': 'Задача для проверки имени',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
self.assertEqual(task.node_id.name, 'Задача для проверки имени')
|
||||
|
||||
def test_index_ids_computed(self):
|
||||
task = self.env['project.task'].create({
|
||||
'name': 'Задача с показателями',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
index = self.env['hg.index'].create({
|
||||
'name': 'Показатель задачи',
|
||||
'node_id': task.node_id.id,
|
||||
})
|
||||
task._compute_indexes()
|
||||
self.assertIn(index, task.index_ids)
|
||||
|
||||
def test_related_ids_computed(self):
|
||||
task_a = self.env['project.task'].create({
|
||||
'name': 'Задача А',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
task_b = self.env['project.task'].create({
|
||||
'name': 'Задача Б',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
self.env['hg.link'].create({
|
||||
'name': 'Связь А → Б',
|
||||
'source_id': task_a.node_id.id,
|
||||
'target_ids': [(4, task_b.node_id.id)],
|
||||
})
|
||||
task_a._compute_related()
|
||||
self.assertIn(task_b.node_id, task_a.related_ids)
|
||||
|
||||
def test_related_ids_empty_without_links(self):
|
||||
task = self.env['project.task'].create({
|
||||
'name': 'Изолированная задача',
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
task._compute_related()
|
||||
self.assertFalse(task.related_ids)
|
||||
68
mklab_project_task_indicators/tests/test_project_task.py
Normal file
68
mklab_project_task_indicators/tests/test_project_task.py
Normal file
@ -0,0 +1,68 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestProjectTaskIndicators(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.project = cls.env['project.project'].create({'name': 'Тестовый проект'})
|
||||
|
||||
def _create_task(self, name='Тестовая задача'):
|
||||
return self.env['project.task'].create({
|
||||
'name': name,
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
|
||||
def test_task_inherits_mixin(self):
|
||||
task = self._create_task()
|
||||
self.assertTrue(hasattr(task, 'node_id'))
|
||||
self.assertTrue(hasattr(task, 'index_ids'))
|
||||
self.assertTrue(hasattr(task, 'related_ids'))
|
||||
|
||||
def test_node_auto_created(self):
|
||||
task = self._create_task('Задача с автоузлом')
|
||||
self.assertTrue(task.node_id)
|
||||
self.assertEqual(task.node_id.res_model, 'project.task')
|
||||
self.assertEqual(task.node_id.res_id, task.id)
|
||||
|
||||
def test_multiple_tasks_have_separate_nodes(self):
|
||||
task_a = self._create_task('Задача 1')
|
||||
task_b = self._create_task('Задача 2')
|
||||
self.assertNotEqual(task_a.node_id, task_b.node_id)
|
||||
|
||||
def test_index_assigned_via_node(self):
|
||||
task = self._create_task('Задача с показателем')
|
||||
index = self.env['hg.index'].create({
|
||||
'name': 'Показатель задачи',
|
||||
'node_id': task.node_id.id,
|
||||
})
|
||||
task._compute_indexes()
|
||||
self.assertIn(index, task.index_ids)
|
||||
|
||||
def test_no_indexes_without_node_assignment(self):
|
||||
task = self._create_task('Задача без показателей')
|
||||
task._compute_indexes()
|
||||
other_node = self.env['hg.node'].create({
|
||||
'name': 'Чужой узел',
|
||||
'res_model': 'hg.node',
|
||||
'res_id': 0,
|
||||
})
|
||||
index = self.env['hg.index'].create({
|
||||
'name': 'Чужой показатель',
|
||||
'node_id': other_node.id,
|
||||
})
|
||||
task._compute_indexes()
|
||||
self.assertNotIn(index, task.index_ids)
|
||||
|
||||
def test_related_tasks_via_link(self):
|
||||
task_a = self._create_task('Задача-источник')
|
||||
task_b = self._create_task('Задача-приёмник')
|
||||
self.env['hg.link'].create({
|
||||
'name': 'Связь задач',
|
||||
'source_id': task_a.node_id.id,
|
||||
'target_ids': [(4, task_b.node_id.id)],
|
||||
})
|
||||
task_a._compute_related()
|
||||
self.assertIn(task_b.node_id, task_a.related_ids)
|
||||
33
mklab_project_task_indicators/views/project_task_views.xml
Normal file
33
mklab_project_task_indicators/views/project_task_views.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<odoo>
|
||||
<record id="mklab_view_task_form2_inherit" model="ir.ui.view">
|
||||
<field name="name">Кастомизация формы задачи Project</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 name = "hypergraph_info" string="Связи и показатели">
|
||||
<h3>Показатели</h3>
|
||||
<field name="index_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="internal_code_id"/>
|
||||
<field name="external_code"/>
|
||||
<field name="current_value"/>
|
||||
<button name="calc" type="object" string="Вычислить"/>
|
||||
</list>
|
||||
</field>
|
||||
<h3>Связи</h3>
|
||||
<field name="related_ids">
|
||||
<list>
|
||||
<field name="name" string="Название"/>
|
||||
<field name="res_model" string="Модель"/>
|
||||
<button name="goto_related" type="object" string="Открыть"/>
|
||||
</list>
|
||||
</field>
|
||||
<h4>Вершина графа</h4>
|
||||
<field name="node_id" readonly="1"/>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user