Public release from ruodoo-project: 19.0 - 2026-05-31 21:19:12 UTC

This commit is contained in:
CI Publish Bot
2026-05-31 21:19:21 +00:00
commit aa4214c195
1213 changed files with 183945 additions and 0 deletions

View File

@ -0,0 +1,34 @@
# Слои показателей для задач проектов
name: mklab_project_task_indicators
### 1. Общее описание
Модуль расширяет функционал управления задачами в Odoo Project, добавляя возможность работы с гиперграфными связями и показателями для задач.
### 2. Установка
1.Скопируйте папку модуля в директорию addons Odoo
2.Перезапустите сервер Odoo
3.Активируйте модуль через интерфейс администрирования
### 3. Функциональность
3.1 Основные возможности:
- Интегрировать задачи проекта в систему гиперграфных связей
- Отслеживать показатели эффективности задач
- Управлять связями между задачами и другими объектами системы
- Проводить комплексный анализ задач с учетом их взаимосвязей
### Пример использования
Переходим в меню Слои показателей
![img.png](img.png)
Создаем вершины
![img_1.png](img_1.png)
Создаем связи между сущностями
![img_2.png](img_2.png)
Добавляем показатели
![img_3.png](img_3.png)
Проверяем связи по задаче
![img_4.png](img_4.png)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View 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',
],
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import project_task

View 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"

View 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>

View 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="{&quot;widget&quot;: &quot;date&quot;,&quot;format&quot;: &quot;dd.MM.yyyy&quot;}"/>
</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_project_task
from . import test_hg_mixin

View 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)

View 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)

View 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>