Public release from ruodoo-project: 19.0 - 2026-07-26 21:17:35 UTC
This commit is contained in:
62
mklab_project_task_indicators/README.md
Normal file
62
mklab_project_task_indicators/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Слои показателей для задач проектов
|
||||
name: mklab_project_task_indicators
|
||||
version: 19.0.2025.11.17
|
||||
author: MK.Lab
|
||||
|
||||
### 1. Общее описание
|
||||
Модуль добавляет в задачи Odoo Project систему **слоёв показателей** — механизм для отслеживания числовых метрик (KPI) прямо внутри задачи. Каждой задаче можно назначить набор показателей с плановыми значениями и датами, а затем следить за их фактическим выполнением.
|
||||
|
||||
### 2. Установка
|
||||
1. Скопируйте папку модуля mklab_project_task_indicators в директорию addons Odoo
|
||||
|
||||
2. Перезапустите сервер Odoo
|
||||
|
||||
3. Обновите список приложений: Настройки — Приложения — Обновить список
|
||||
|
||||
4. Найдите модуль через интерфейс администрирования и установите
|
||||
|
||||
5. Настройте права доступа для пользователей
|
||||
|
||||
> Внимание! Сначала должен быть установлен модуль `mklab_base_indicators`.
|
||||
|
||||
### 3. Функциональность
|
||||
3.1 Основные возможности:
|
||||
- Интегрировать задачи проекта в систему гиперграфных связей
|
||||
- Отслеживать показатели эффективности задач
|
||||
- Управлять связями между задачами и другими объектами системы
|
||||
- Проводить комплексный анализ задач с учетом их взаимосвязей
|
||||
|
||||
|
||||
### Пример использования
|
||||
|
||||
### 1. Работа с показателями в задаче
|
||||
|
||||
Откройте любую задачу в модуле **Проект**. В форме задачи появятся:
|
||||
|
||||
- Кнопка **«Показатели»** (stat button) — открывает список всех показателей задачи
|
||||
- Блок **«Вершины»** — показывает связи задачи с другими объектами в системе показателей
|
||||
|
||||

|
||||
|
||||
### 2. Меню «Слои показателей»
|
||||
|
||||
В главном меню появляется раздел **Слои показателей**, где можно:
|
||||
|
||||
- Создавать **вершины** (узлы) — привязка к конкретному объекту (задаче, проекту и т.д.)
|
||||
- Управлять **показателями** (`hg.index`) — именованные метрики
|
||||
- Просматривать **значения** (`hg.value`) — конкретные числа с датами
|
||||
|
||||

|
||||
|
||||
### 3. Создание показателей для задачи
|
||||
|
||||
1. Откройте задачу и нажмите **«Показатели»**
|
||||
2. Создайте показатель: укажите название, код, плановую дату и плановое значение
|
||||
3. По мере выполнения вносите фактические значения
|
||||
4. Смотрите динамику в сводной таблице
|
||||
|
||||
### 4. Печать сводной ведомости
|
||||
|
||||
В настройках **Проекта** (не задачи) доступна печать **«Сводная ведомость по проекту»** — отчёт со всеми задачами и их показателями, плановыми датами и значениями. В колонтитуле — дата печати и ответственный по проекту.
|
||||
|
||||

|
||||
133
mklab_project_task_indicators/TECHNICAL.md
Normal file
133
mklab_project_task_indicators/TECHNICAL.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Техническая документация: mklab_project_task_indicators
|
||||
|
||||
**name:** `mklab_project_task_indicators`
|
||||
**version:** 19.0.2025.11.14
|
||||
**author:** MK.Lab
|
||||
**depends:** `base`, `project`, `mklab_base_indicators`
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
Модуль подключает стандартную модель `project.task` к инфраструктуре гиперграфных показателей через `hg.hg_mixin`. Новых моделей данных не вводит — только регистрирует наследование и добавляет представления.
|
||||
|
||||
```
|
||||
project.task
|
||||
|
||||
hg.hg_mixin (из mklab_base_indicators)
|
||||
├── hg.node — вершина графа (привязка к res_model / res_id)
|
||||
├── hg.index — показатель (именованная метрика)
|
||||
└── hg.value — значение показателя (план / факт / дата)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Модели
|
||||
|
||||
### `project.task` (расширение)
|
||||
|
||||
**Файл:** `models/project_task.py`
|
||||
|
||||
Регистрирует множественное наследование от `hg.hg_mixin`. После этого каждая задача при создании автоматически получает вершину графа (`hg.node`), к которой можно привязывать показатели и связи.
|
||||
|
||||
```python
|
||||
class ProjectTask(models.Model):
|
||||
_name = 'project.task'
|
||||
_inherit = ['project.task', 'hg.hg_mixin']
|
||||
```
|
||||
|
||||
Через `hg.hg_mixin` задача получает следующие поля:
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| `node_id` | Many2one к `hg.node` | Вершина графа данной задачи |
|
||||
| `index_ids` | Many2many к `hg.index` | Показатели, привязанные к вершине задачи |
|
||||
| `related_ids` | Many2many к `hg.node` | Связанные вершины из матрицы связности |
|
||||
|
||||
Полное описание полей и методов миксина — в документации модуля `mklab_base_indicators`.
|
||||
|
||||
---
|
||||
|
||||
## Представления
|
||||
|
||||
### Вкладка «Связи и показатели» на форме задачи
|
||||
|
||||
**Файл:** `views/project_task_views.xml`
|
||||
|
||||
Расширяет стандартную форму задачи (`project.view_task_form2`). Вкладка добавляется после вкладки «Описание» и содержит три блока:
|
||||
|
||||
**Блок «Показатели»** — список `index_ids` с колонками:
|
||||
|
||||
| Колонка | Поле | Описание |
|
||||
|---|---|---|
|
||||
| Название | `name` | Название показателя |
|
||||
| Внутренний код | `internal_code_id` | Классификатор |
|
||||
| Внешний код | `external_code` | Код для интеграций |
|
||||
| Текущее значение | `current_value` | Вычисленное актуальное значение |
|
||||
| Кнопка «Вычислить» | `calc` (object) | Запускает пересчёт значения по формуле |
|
||||
|
||||
**Блок «Связи»** — список `related_ids` с колонками:
|
||||
|
||||
| Колонка | Поле | Описание |
|
||||
|---|---|---|
|
||||
| Название | `name` | Название связанной вершины |
|
||||
| Модель | `res_model` | Техническое имя модели Odoo |
|
||||
| Кнопка «Открыть» | `goto_related` (object) | Открывает связанную запись в диалоге |
|
||||
|
||||
**Поле «Вершина графа»** — `node_id`, readonly, показывает привязанную вершину.
|
||||
|
||||
---
|
||||
|
||||
## Отчёт «Сводная ведомость по проекту»
|
||||
|
||||
**Файлы:** `reports/template.xml`, `reports/action.xml`
|
||||
**Модель:** `project.project`
|
||||
**ID шаблона:** `mklab_project_task_indicators.general_report`
|
||||
|
||||
Отчёт формируется из формы проекта и итерирует по `task_ids`. Структура вывода для каждой задачи:
|
||||
|
||||
- Строка-заголовок с названием задачи (серый фон) — выводится если у задачи больше одного показателя
|
||||
- Для каждого показателя задачи (`index_ids`) выводятся строки значений (`value_ids`):
|
||||
|
||||
| Колонка | Поле | Формат |
|
||||
|---|---|---|
|
||||
| Наименование показателя | `index.name` | текст |
|
||||
| На дату | `hg_value.date_due` | дд.мм.гггг |
|
||||
| Плановый показатель | `hg_value.value_float_plan` | число |
|
||||
|
||||
**Колонтитул отчёта:**
|
||||
|
||||
| Позиция | Содержимое |
|
||||
|---|---|
|
||||
| Левый | Дата печати в формате дд.мм.гггг |
|
||||
| Центр | Текст «Согласовано» |
|
||||
| Правый | Ответственный по проекту (`user_id`) |
|
||||
|
||||
---
|
||||
|
||||
## Демо-данные
|
||||
|
||||
**Файл:** `demo/demo.xml`
|
||||
|
||||
Создаёт демо-задачи с привязанными вершинами и показателями. Использует показатели из демо-данных `mklab_base_indicators`: `demo_index_revenue`, `demo_index_cost`, `demo_index_profit`.
|
||||
|
||||
---
|
||||
|
||||
## Тесты
|
||||
|
||||
**Файл:** `tests/`
|
||||
|
||||
| Файл | Что покрывает |
|
||||
|---|---|
|
||||
| `test_project_task.py` | Создание задачи, автосоздание вершины графа, привязка показателей |
|
||||
| `test_hg_mixin.py` | Работа полей и методов миксина на модели `project.task` |
|
||||
|
||||
---
|
||||
|
||||
## Связанные модули
|
||||
|
||||
| Модуль | Роль |
|
||||
|---|---|
|
||||
| `mklab_base_indicators` | Обязательная зависимость: содержит `hg.hg_mixin` и все `hg.*` модели |
|
||||
| `mklab_base_indicators_extended` | Надстройка: шаблоны смет, визард создания показателей, кнопки «Смета-анализ» и «Править смету» |
|
||||
| `mklab_base_indicators_report` | Граф-отчёт «Показатели по узлам» |
|
||||
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_1.png
Normal file
BIN
mklab_project_task_indicators/img_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 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: 52 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: 35 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