Public release from ruodoo-project: 19.0 - 2026-07-26 21:17:35 UTC
95
mklab_base_indicators_extended/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
# Слои показателей - Смета
|
||||
**name:** `mklab_base_indicators_extended`
|
||||
**version:** 19.0.2025.11.24
|
||||
**author:** MK.Lab
|
||||
|
||||
### 1. Общее описание
|
||||
|
||||
Модуль является **надстройкой над слоем показателей** и добавляет к задачам проекта полноценную систему **сметного планирования**. Если базовый модуль позволяет добавлять показатели вручную по одному, то этот модуль добавляет:
|
||||
|
||||
- **Шаблоны смет** — заранее подготовленные наборы показателей с плановыми датами и суммами
|
||||
- **Визард создания сметы** — быстрое применение шаблона к задаче в один клик
|
||||
- **Смета-анализ** — сводная таблица (pivot) со значениями показателей по неделям
|
||||
- **Редактор сметы** — список плановых и фактических значений с возможностью правки
|
||||
|
||||
### 2. Установка
|
||||
|
||||
1. Убедитесь, что установлены `mklab_base_indicators` и `mklab_project_task_indicators`
|
||||
|
||||
2. Скопируйте папку модуля в директорию `addons`
|
||||
|
||||
3. Перезапустите сервер Odoo
|
||||
|
||||
4. Обновите список приложений и установите **«Слои показателей — Смета»**
|
||||
|
||||
### 3. Функциональность
|
||||
---
|
||||
|
||||
### Кнопка «Смета-анализ»
|
||||
Открывает сводную таблицу (pivot) с показателями задачи, сгруппированными по неделям. Позволяет быстро увидеть общую картину: какие показатели, на какие даты, с какими значениями.
|
||||
|
||||
### Кнопка «Править смету»
|
||||
- Если у задачи **уже есть показатели** — открывает список `hg.value` (план/факт) для редактирования
|
||||
- Если показателей **ещё нет** — запускает **визард выбора шаблона сметы**
|
||||
|
||||
### Визард создания сметы
|
||||
Диалоговое окно с двумя полями:
|
||||
1. **Шаблон сметы** — выбор из предустановленных наборов показателей
|
||||
2. **Код показателей** — внутренний классификатор (группировка показателей)
|
||||
|
||||
После подтверждения все строки шаблона автоматически превращаются в показатели задачи с плановыми датами и суммами.
|
||||
|
||||
### Шаблоны смет
|
||||
Управляются отдельно (меню Слои показателей → Шаблоны). Каждый шаблон содержит строки:
|
||||
- Показатель (ссылка на `hg.index`)
|
||||
- Плановая дата
|
||||
- Плановая сумма
|
||||
|
||||
---
|
||||
|
||||
## Предустановленные показатели
|
||||
|
||||
При установке модуля создаются базовые показатели строительной сметы:
|
||||
|
||||
| Показатель | Внешний код |
|
||||
|---|---|
|
||||
| Работы по проектированию | EXT-DESIGN |
|
||||
| Подготовка котлована | EXT-EXCAV |
|
||||
| Фундамент | EXT-FOUND |
|
||||
| Монолитные работы | EXT-MONO |
|
||||
| Отделочные работы | EXT-FINISH |
|
||||
| Инженерные работы | EXT-ENG |
|
||||
|
||||
### Пример использования
|
||||
|
||||
### Создать шаблон сметы
|
||||
|
||||
1. Перейдите в **Слои показателей → Шаблоны смет**
|
||||
2. Создайте новый шаблон, укажите название
|
||||
3. Добавьте строки: выберите показатель, укажите плановую дату и сумму
|
||||
4. Сохраните
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Применить шаблон к задаче
|
||||
|
||||
1. Откройте задачу в модуле **Проект**
|
||||
2. Нажмите кнопку **«Править смету»**
|
||||
3. Если показателей нет — откроется визард. Выберите шаблон и код
|
||||
4. Нажмите **Подтвердить** — показатели создадутся автоматически
|
||||
|
||||

|
||||

|
||||
|
||||
### Анализировать смету
|
||||
|
||||
- Кнопка **«Смета-анализ»** → сводная таблица по неделям
|
||||
|
||||

|
||||
|
||||
- Кнопка **«Править смету»** (при наличии показателей) → список план/факт
|
||||
|
||||

|
||||
---
|
||||
168
mklab_base_indicators_extended/TECHNICAL.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Техническая документация: mklab_base_indicators_extended
|
||||
|
||||
**name:** `mklab_base_indicators_extended`
|
||||
**version:** 19.0.2025.11.24
|
||||
**author:** MK.Lab
|
||||
**depends:** `base`, `mklab_project_task_indicators`
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
Модуль надстраивается поверх `mklab_base_indicators` и `mklab_project_task_indicators`. Вводит две новые модели для хранения шаблонов смет, визард применения шаблона к задаче и расширяет `project.task` двумя методами-действиями.
|
||||
|
||||
```
|
||||
mklab_base_indicators_extended
|
||||
├── hg.templates — шаблон сметы (заголовок)
|
||||
├── hg.templates.line — строка шаблона (показатель + дата + сумма)
|
||||
├── estimate.wizard — визард применения шаблона к задаче
|
||||
└── project.task — расширение: методы «Смета-анализ» и «Править смету»
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Модели
|
||||
|
||||
### `hg.templates` — Шаблон сметы
|
||||
|
||||
**Файл:** `models/hg_templates.py`
|
||||
|
||||
Хранит именованный набор строк показателей — шаблон для быстрого создания сметы задачи.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| `name` | Char | Название шаблона |
|
||||
| `line_ids` | One2many к `hg.templates.line` | Строки шаблона |
|
||||
|
||||
---
|
||||
|
||||
### `hg.templates.line` — Строка шаблона
|
||||
|
||||
**Файл:** `models/hg_templates.py`
|
||||
|
||||
Одна строка шаблона: конкретный показатель с плановой датой и суммой.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| `template_id` | Many2one к `hg.templates` (required) | Родительский шаблон |
|
||||
| `index_id` | Many2one к `hg.index` (required) | Показатель из справочника |
|
||||
| `date_due` | Date | Плановая дата |
|
||||
| `value_float_plan` | Float | Плановая сумма |
|
||||
|
||||
---
|
||||
|
||||
### `estimate.wizard` — Визард создания сметы
|
||||
|
||||
**Файл:** `wizard/estimate_wizard.py`
|
||||
**Тип:** `models.TransientModel`
|
||||
|
||||
Диалог выбора шаблона и кода показателей. Открывается из формы задачи когда у неё ещё нет показателей.
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|---|---|---|
|
||||
| `template_id` | Many2one к `hg.templates` (required) | Выбранный шаблон сметы |
|
||||
| `code_id` | Many2one к `hg.index.code` (required) | Код, присваиваемый всем создаваемым показателям |
|
||||
|
||||
#### Метод `confirm_action()`
|
||||
|
||||
Получает ID задачи из контекста (`active_id`), затем для каждой строки шаблона выполняет три шага:
|
||||
|
||||
1. Вызывает `_get_node()` — находит или создаёт `hg.node` для задачи
|
||||
2. Создаёт `hg.index` с `node_id`, именем из строки шаблона и `internal_code_id` из `code_id`
|
||||
3. Создаёт `hg.value` с `type='alone'`, `date_due` и `value_float_plan` из строки шаблона
|
||||
|
||||
После обхода всех строк закрывает диалог через `ir.actions.act_window_close`.
|
||||
|
||||
#### Метод `_get_node(res_model, res_id, name)`
|
||||
|
||||
Ищет существующий `hg.node` по паре `(res_model, res_id)`. Если не найден — создаёт новый. Гарантирует что для одной задачи не создаётся дублирующих вершин.
|
||||
|
||||
---
|
||||
|
||||
### `project.task` — Расширение
|
||||
|
||||
**Файл:** `models/project_task.py`
|
||||
|
||||
Добавляет два метода-действия в модель задачи, вызываемых через кнопки на форме.
|
||||
|
||||
#### `action_estimate_analysis()`
|
||||
|
||||
Открывает `hg.value` в режиме **pivot** в диалоге. Фильтрует только значения показателей текущей задачи (`index_id in self.index_ids`). Использует кастомный pivot-вид `view_estimate_pivot`.
|
||||
|
||||
#### `action_edit_estimate()`
|
||||
|
||||
Условная логика в зависимости от наличия показателей у задачи:
|
||||
|
||||
| Условие | Результат |
|
||||
|---|---|
|
||||
| `self.index_ids` не пустой | Открывает `hg.value` в list-режиме (`view_edit_estimate_list`) в диалоге |
|
||||
| `self.index_ids` пустой | Открывает визард `estimate.wizard` с `active_id` задачи в контексте |
|
||||
|
||||
---
|
||||
|
||||
## Представления
|
||||
|
||||
| Файл | Содержание |
|
||||
|---|---|
|
||||
| `views/hg_templates.xml` | Форма и список шаблонов смет (`hg.templates`) |
|
||||
| `views/hg_value.xml` | Кастомные виды `hg.value`: pivot (`view_estimate_pivot`) и list (`view_edit_estimate_list`) |
|
||||
| `views/project_task.xml` | Кнопки «Смета-анализ» и «Править смету» на форме задачи |
|
||||
| `wizard/estimate_wizard.xml` | Форма визарда выбора шаблона |
|
||||
|
||||
---
|
||||
|
||||
## Данные при установке
|
||||
|
||||
**Файл:** `data/data.xml`
|
||||
|
||||
Создаёт один классификатор и 6 предустановленных показателей `hg.index`:
|
||||
|
||||
| XML ID | Название | Внешний код |
|
||||
|---|---|---|
|
||||
| `work_design` | Работы по проектированию | EXT-DESIGN |
|
||||
| `work_excavation` | Подготовка котлована | EXT-EXCAV |
|
||||
| `work_foundation` | Фундамент | EXT-FOUND |
|
||||
| `work_monolithic` | Монолитные работы | EXT-MONO |
|
||||
| `work_finishing` | Отделочные работы | EXT-FINISH |
|
||||
| `work_engineering` | Инженерные работы | EXT-ENG |
|
||||
|
||||
---
|
||||
|
||||
## Демо-данные
|
||||
|
||||
**Файл:** `demo/demo.xml`
|
||||
|
||||
Создаёт шаблон «Смета проекта (базовая)» с тремя строками:
|
||||
|
||||
| Показатель | Плановая сумма | Дата |
|
||||
|---|---|---|
|
||||
| Выручка (`demo_index_revenue`) | 2 000 000 | 2026-12-31 |
|
||||
| Себестоимость (`demo_index_cost`) | 1 200 000 | 2026-12-31 |
|
||||
| Прибыль (`demo_index_profit`) | 800 000 | 2026-12-31 |
|
||||
|
||||
Использует показатели из демо-данных модуля `mklab_base_indicators`.
|
||||
|
||||
---
|
||||
|
||||
## Права доступа
|
||||
|
||||
**Файл:** `security/ir.model.access.csv`
|
||||
|
||||
| Модель | Группа | Права |
|
||||
|---|---|---|
|
||||
| `hg.templates` | `group_indicators_admin` | CRUD |
|
||||
| `hg.templates.line` | `group_indicators_admin` | CRUD |
|
||||
| `estimate.wizard` | `group_indicators_admin` | CRUD |
|
||||
|
||||
---
|
||||
|
||||
## Тесты
|
||||
|
||||
**Файл:** `tests/`
|
||||
|
||||
| Файл | Что покрывает |
|
||||
|---|---|
|
||||
| `test_hg_templates.py` | Создание шаблонов, добавление строк, корректность полей |
|
||||
| `test_estimate_wizard.py` | Применение шаблона к задаче, создание `hg.node` / `hg.index` / `hg.value`, идемпотентность `_get_node` |
|
||||
|
||||
---
|
||||
4
mklab_base_indicators_extended/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
37
mklab_base_indicators_extended/__manifest__.py
Normal file
@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Слои показателей - Смета",
|
||||
|
||||
'summary': """
|
||||
Заготовленный блок показателей стоимостной сметы задач""",
|
||||
|
||||
'description': """
|
||||
Модуль, добавляющий сквозные показатели, шаблоны показателей,
|
||||
а также сводные отчеты по показателям в задачах проектов,
|
||||
обеспечивая удобный и наглядный интерфейс для их анализа и редактирования.
|
||||
""",
|
||||
|
||||
'author': "MK.Lab",
|
||||
'website': "https://www.inf-centre.ru",
|
||||
|
||||
'category': 'Uncategorized',
|
||||
'version': '19.0.2025.11.24',
|
||||
|
||||
'depends': ['base', 'mklab_project_task_indicators'],
|
||||
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/data.xml',
|
||||
'views/hg_templates.xml',
|
||||
'views/hg_value.xml',
|
||||
'views/project_task.xml',
|
||||
'wizard/estimate_wizard.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'test': [
|
||||
'tests/test_hg_templates.py',
|
||||
'tests/test_estimate_wizard.py',
|
||||
],
|
||||
}
|
||||
37
mklab_base_indicators_extended/data/data.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<record id="base_code_001" model="hg.index.code">
|
||||
<field name="name">base_code_001</field>
|
||||
</record>
|
||||
<record id="work_design" model="hg.index">
|
||||
<field name="name">Работы по проектированию</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-DESIGN</field>
|
||||
</record>
|
||||
<record id="work_excavation" model="hg.index">
|
||||
<field name="name">Подготовка котлована</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-EXCAV</field>
|
||||
</record>
|
||||
<record id="work_foundation" model="hg.index">
|
||||
<field name="name">Фундамент</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-FOUND</field>
|
||||
</record>
|
||||
<record id="work_monolithic" model="hg.index">
|
||||
<field name="name">Монолитные работы</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-MONO</field>
|
||||
</record>
|
||||
<record id="work_finishing" model="hg.index">
|
||||
<field name="name">Отделочные работы</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-FINISH</field>
|
||||
</record>
|
||||
<record id="work_engineering" model="hg.index">
|
||||
<field name="name">Инженерные работы</field>
|
||||
<field name="internal_code_id" ref="mklab_base_indicators_extended.base_code_001"/>
|
||||
<field name="external_code">EXT-ENG</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
31
mklab_base_indicators_extended/demo/demo.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Шаблон показателей для сметы задачи -->
|
||||
<record id="demo_hg_template_project_budget" model="hg.templates">
|
||||
<field name="name">Смета проекта (базовая)</field>
|
||||
</record>
|
||||
|
||||
<!-- Строки шаблона показателей -->
|
||||
<record id="demo_hg_template_line_revenue" model="hg.templates.line">
|
||||
<field name="template_id" ref="demo_hg_template_project_budget"/>
|
||||
<field name="index_id" ref="mklab_base_indicators.demo_index_revenue"/>
|
||||
<field name="date_due">2026-12-31</field>
|
||||
<field name="value_float_plan">2000000.0</field>
|
||||
</record>
|
||||
<record id="demo_hg_template_line_cost" model="hg.templates.line">
|
||||
<field name="template_id" ref="demo_hg_template_project_budget"/>
|
||||
<field name="index_id" ref="mklab_base_indicators.demo_index_cost"/>
|
||||
<field name="date_due">2026-12-31</field>
|
||||
<field name="value_float_plan">1200000.0</field>
|
||||
</record>
|
||||
<record id="demo_hg_template_line_profit" model="hg.templates.line">
|
||||
<field name="template_id" ref="demo_hg_template_project_budget"/>
|
||||
<field name="index_id" ref="mklab_base_indicators.demo_index_profit"/>
|
||||
<field name="date_due">2026-12-31</field>
|
||||
<field name="value_float_plan">800000.0</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
BIN
mklab_base_indicators_extended/img_1.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
mklab_base_indicators_extended/img_2.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
mklab_base_indicators_extended/img_3.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
mklab_base_indicators_extended/img_4.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
mklab_base_indicators_extended/img_5.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
mklab_base_indicators_extended/img_6.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
mklab_base_indicators_extended/img_7.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
4
mklab_base_indicators_extended/models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hg_templates
|
||||
from . import project_task
|
||||
18
mklab_base_indicators_extended/models/hg_templates.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class HypergraphTemplates(models.Model):
|
||||
_name = 'hg.templates'
|
||||
|
||||
name = fields.Char(string='Название')
|
||||
line_ids = fields.One2many(comodel_name='hg.templates.line', inverse_name='template_id', string='Строки шаблона')
|
||||
|
||||
|
||||
class HypergraphTemplatesLine(models.Model):
|
||||
_name = 'hg.templates.line'
|
||||
|
||||
template_id = fields.Many2one(comodel_name='hg.templates', string='Шаблон', required=True, ondelete='cascade')
|
||||
index_id = fields.Many2one('hg.index', string='Показатель', required=True)
|
||||
date_due = fields.Date(string='Плановая дата')
|
||||
value_float_plan = fields.Float(string='Плановая сумма')
|
||||
46
mklab_base_indicators_extended/models/project_task.py
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = 'project.task'
|
||||
|
||||
# Смета-анализ
|
||||
def action_estimate_analysis(self):
|
||||
context = dict(self.env.context or {})
|
||||
domain = [('index_id', 'in', self.index_ids.ids)]
|
||||
return {
|
||||
'name': 'Смета-Анализ',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'hg.value',
|
||||
'view_mode': 'pivot',
|
||||
'target': 'new',
|
||||
'views': [(self.env.ref('mklab_base_indicators_extended.view_estimate_pivot').id, 'pivot')],
|
||||
'domain': domain,
|
||||
'context': context,
|
||||
}
|
||||
|
||||
# Править смету
|
||||
def action_edit_estimate(self):
|
||||
if self.index_ids:
|
||||
context = dict(self.env.context or {})
|
||||
domain = [('index_id', 'in', self.index_ids.ids)]
|
||||
return {
|
||||
'name': 'Править смету',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'hg.value',
|
||||
'view_mode': 'list',
|
||||
'target': 'new',
|
||||
'views': [(self.env.ref('mklab_base_indicators_extended.view_edit_estimate_list').id, 'list')],
|
||||
'domain': domain,
|
||||
'context': context,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'name': 'Выбор шаблона сметы',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'estimate.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_template_id': False, 'active_id': self.id},
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mklab_hg_templates,mklab_base_indicators_extended.hg.templates,model_hg_templates,mklab_base_indicators.group_indicators_admin,1,1,1,1
|
||||
access_mklab_hg_templates_line,mklab_base_indicators_extended.hg.templates.line,model_hg_templates_line,mklab_base_indicators.group_indicators_admin,1,1,1,1
|
||||
access_mklab_hg_estimate_wizard,mklab_base_indicators_extended.estimate_wizard,model_estimate_wizard,mklab_base_indicators.group_indicators_admin,1,1,1,1
|
||||
|
4
mklab_base_indicators_extended/tests/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_hg_templates
|
||||
from . import test_estimate_wizard
|
||||
76
mklab_base_indicators_extended/tests/test_estimate_wizard.py
Normal file
@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestEstimateWizard(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.project = cls.env['project.project'].create({'name': 'Проект для сметы'})
|
||||
cls.code = cls.env['hg.index.code'].create({'name': 'Код сметы'})
|
||||
cls.index = cls.env['hg.index'].create({
|
||||
'name': 'Показатель сметы',
|
||||
'internal_code_id': cls.code.id,
|
||||
})
|
||||
cls.template = cls.env['hg.templates'].create({'name': 'Шаблон сметы'})
|
||||
cls.env['hg.templates.line'].create({
|
||||
'template_id': cls.template.id,
|
||||
'index_id': cls.index.id,
|
||||
'date_due': '2025-06-30',
|
||||
'value_float_plan': 300000.0,
|
||||
})
|
||||
|
||||
def _create_task(self, name='Задача сметы'):
|
||||
return self.env['project.task'].create({
|
||||
'name': name,
|
||||
'project_id': self.project.id,
|
||||
})
|
||||
|
||||
def test_confirm_action_creates_index_and_value(self):
|
||||
task = self._create_task()
|
||||
wizard = self.env['estimate.wizard'].with_context(active_id=task.id).create({
|
||||
'template_id': self.template.id,
|
||||
'code_id': self.code.id,
|
||||
})
|
||||
wizard.confirm_action()
|
||||
|
||||
# Должен создаться показатель, привязанный к узлу задачи
|
||||
indexes = self.env['hg.index'].search([('node_id', '=', task.node_id.id)])
|
||||
self.assertTrue(indexes, 'Показатель должен быть создан для узла задачи')
|
||||
|
||||
# Должно создаться значение для каждого показателя
|
||||
for index in indexes:
|
||||
values = self.env['hg.value'].search([('index_id', '=', index.id)])
|
||||
self.assertTrue(values, 'Значение должно быть создано для показателя')
|
||||
|
||||
def test_confirm_action_value_plan_matches_template(self):
|
||||
task = self._create_task('Задача проверки плана')
|
||||
wizard = self.env['estimate.wizard'].with_context(active_id=task.id).create({
|
||||
'template_id': self.template.id,
|
||||
'code_id': self.code.id,
|
||||
})
|
||||
wizard.confirm_action()
|
||||
|
||||
indexes = self.env['hg.index'].search([('node_id', '=', task.node_id.id)])
|
||||
value = self.env['hg.value'].search([('index_id', 'in', indexes.ids)], limit=1)
|
||||
self.assertEqual(value.value_float_plan, 300000.0)
|
||||
self.assertEqual(str(value.date_due), '2025-06-30')
|
||||
|
||||
def test_confirm_action_returns_close(self):
|
||||
task = self._create_task('Задача закрытия')
|
||||
wizard = self.env['estimate.wizard'].with_context(active_id=task.id).create({
|
||||
'template_id': self.template.id,
|
||||
'code_id': self.code.id,
|
||||
})
|
||||
result = wizard.confirm_action()
|
||||
self.assertEqual(result['type'], 'ir.actions.act_window_close')
|
||||
|
||||
def test_confirm_action_without_active_id(self):
|
||||
wizard = self.env['estimate.wizard'].create({
|
||||
'template_id': self.template.id,
|
||||
'code_id': self.code.id,
|
||||
})
|
||||
# Без active_id в контексте — должен вернуть None без ошибок
|
||||
result = wizard.confirm_action()
|
||||
self.assertIsNone(result)
|
||||
56
mklab_base_indicators_extended/tests/test_hg_templates.py
Normal file
@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestHgTemplates(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.code = self.env['hg.index.code'].create({'name': 'Код шаблона'})
|
||||
self.index = self.env['hg.index'].create({
|
||||
'name': 'Показатель шаблона',
|
||||
'internal_code_id': self.code.id,
|
||||
})
|
||||
self.template = self.env['hg.templates'].create({'name': 'Тестовый шаблон'})
|
||||
|
||||
def test_template_create(self):
|
||||
self.assertEqual(self.template.name, 'Тестовый шаблон')
|
||||
self.assertFalse(self.template.line_ids)
|
||||
|
||||
def test_add_line_to_template(self):
|
||||
line = self.env['hg.templates.line'].create({
|
||||
'template_id': self.template.id,
|
||||
'index_id': self.index.id,
|
||||
'date_due': '2025-03-31',
|
||||
'value_float_plan': 250000.0,
|
||||
})
|
||||
self.assertIn(line, self.template.line_ids)
|
||||
self.assertEqual(line.value_float_plan, 250000.0)
|
||||
self.assertEqual(line.index_id, self.index)
|
||||
|
||||
def test_multiple_lines(self):
|
||||
index2 = self.env['hg.index'].create({'name': 'Второй показатель'})
|
||||
self.env['hg.templates.line'].create({
|
||||
'template_id': self.template.id,
|
||||
'index_id': self.index.id,
|
||||
'date_due': '2025-01-31',
|
||||
'value_float_plan': 100000.0,
|
||||
})
|
||||
self.env['hg.templates.line'].create({
|
||||
'template_id': self.template.id,
|
||||
'index_id': index2.id,
|
||||
'date_due': '2025-02-28',
|
||||
'value_float_plan': 200000.0,
|
||||
})
|
||||
self.assertEqual(len(self.template.line_ids), 2)
|
||||
|
||||
def test_line_cascade_delete(self):
|
||||
line = self.env['hg.templates.line'].create({
|
||||
'template_id': self.template.id,
|
||||
'index_id': self.index.id,
|
||||
'date_due': '2025-01-31',
|
||||
'value_float_plan': 50000.0,
|
||||
})
|
||||
line_id = line.id
|
||||
self.template.unlink()
|
||||
self.assertFalse(self.env['hg.templates.line'].browse(line_id).exists())
|
||||
60
mklab_base_indicators_extended/views/hg_templates.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<odoo>
|
||||
<record id="mklab_base_indicators_extended.hg_templates_line_list" model="ir.ui.view">
|
||||
<field name="name">hg.templates.line.list</field>
|
||||
<field name="model">hg.templates.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="index_id"/>
|
||||
<field name="date_due"/>
|
||||
<field name="value_float_plan"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_base_indicators_extended.hypergraph_templates_view_list" model="ir.ui.view">
|
||||
<field name="name">hg.templates.list</field>
|
||||
<field name="model">hg.templates</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="line_ids"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_base_indicators_extended.hypergraph_templates_view_form" model="ir.ui.view">
|
||||
<field name="name">hg.templates.form</field>
|
||||
<field name="model">hg.templates</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page name="value_line" string="Значения">
|
||||
<field name="line_ids">
|
||||
<list editable="bottom" create="true" delete="true">
|
||||
<field name="index_id"/>
|
||||
<field name="date_due"/>
|
||||
<field name="value_float_plan"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_base_indicators_extended.hg_templates_window" model="ir.actions.act_window">
|
||||
<field name="name">Шаблон показателей</field>
|
||||
<field name="res_model">hg.templates</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Шаблоны показателей" id="hypergraph_base_shows_templates" parent="mklab_base_indicators.hypergraph_base_shows_list"
|
||||
action="mklab_base_indicators_extended.hg_templates_window"/>
|
||||
|
||||
</odoo>
|
||||
45
mklab_base_indicators_extended/views/hg_value.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<odoo>
|
||||
<record id="view_estimate_pivot" model="ir.ui.view">
|
||||
<field name="name">Отчет по показателям</field>
|
||||
<field name="model">hg.value</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot>
|
||||
<field name="date_due" type="col" interval="week"/>
|
||||
<field name="index_id" type="row"/>
|
||||
<field name="value_float_actual" type="measure" operator="sum" string="Факт (Сумма)"/>
|
||||
<field name="value_float_plan" type="measure" operator="sum" string="План (Сумма)"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_edit_estimate_list" model="ir.ui.view">
|
||||
<field name="name">Править смету</field>
|
||||
<field name="model">hg.value</field>
|
||||
<field name="arch" type="xml">
|
||||
<list editable="bottom">
|
||||
<field name="value_float_actual"/>
|
||||
<field name="value_float_plan"/>
|
||||
<field name="date_due"/>
|
||||
<field name="index_id" string="Показатель"/>
|
||||
<button name="calc" type="object" string="Вычислить"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_base_indicators_extended.hg_reports_window" model="ir.actions.act_window">
|
||||
<field name="name">Отчетность</field>
|
||||
<field name="res_model">hg.value</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_base_indicators_extended.hypergraph_view_estimate_pivot_window" model="ir.actions.act_window">
|
||||
<field name="name">Отчет по показателям</field>
|
||||
<field name="res_model">hg.value</field>
|
||||
<field name="view_mode">pivot</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Отчетность" id="hypergraph_reports" parent="mklab_base_indicators.hypergraph_base_menu_root"
|
||||
action="mklab_base_indicators_extended.hg_reports_window"/>
|
||||
<menuitem name="Отчет по показателям" id="hypergraph_reports_value" parent="hypergraph_reports"
|
||||
action="mklab_base_indicators_extended.hypergraph_view_estimate_pivot_window"/>
|
||||
</odoo>
|
||||
23
mklab_base_indicators_extended/views/project_task.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<odoo>
|
||||
<record id="mklab_view_task_form2_inherit_extended" model="ir.ui.view">
|
||||
<field name="name">project.task.form.replace.hypergraph_page</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="mklab_project_task_indicators.mklab_view_task_form2_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='hypergraph_info']/h3" position="replace"/>
|
||||
<xpath expr="//page[@name='hypergraph_info']/field[@name='index_ids']" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mklab_view_task_form2_extended" model="ir.ui.view">
|
||||
<field name="name">project.task.form.replace.hypergraph_page</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="header" position="inside">
|
||||
<button name="action_estimate_analysis" type="object" string="Смета-анализ" class="btn-primary"/>
|
||||
<button name="action_edit_estimate" type="object" string="Править смету" class="btn-primary"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
3
mklab_base_indicators_extended/wizard/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import estimate_wizard
|
||||
45
mklab_base_indicators_extended/wizard/estimate_wizard.py
Normal file
@ -0,0 +1,45 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class EstimateWizard(models.Model):
|
||||
_name = "estimate.wizard"
|
||||
|
||||
template_id = fields.Many2one(comodel_name='hg.templates', string='Шаблон сметы', required=True)
|
||||
code_id = fields.Many2one(comodel_name='hg.index.code', string='Код показателей', required=True)
|
||||
|
||||
def confirm_action(self):
|
||||
self.ensure_one()
|
||||
task = self.env.context.get('active_id') and self.env['project.task'].browse(self.env.context['active_id'])
|
||||
if not task:
|
||||
return
|
||||
for line in self.template_id.line_ids:
|
||||
node = self._get_node(res_model='project.task', res_id=task.id, name=task.name)
|
||||
index = self.env['hg.index'].create({
|
||||
'node_id': node.id,
|
||||
'name': line.index_id.name,
|
||||
'internal_code_id': self.code_id.id,
|
||||
})
|
||||
self.env['hg.value'].create({
|
||||
'index_id': index.id,
|
||||
'type': 'alone',
|
||||
'date_due': line.date_due,
|
||||
'value_float_plan': line.value_float_plan,
|
||||
})
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
def _get_node(self, res_model, res_id, name):
|
||||
Node = self.env['hg.node']
|
||||
|
||||
node = Node.search([
|
||||
('res_model', '=', res_model),
|
||||
('res_id', '=', res_id)
|
||||
], limit=1)
|
||||
|
||||
if not node:
|
||||
node = Node.create({
|
||||
'name': name,
|
||||
'res_model': 'project.task',
|
||||
'res_id': res_id,
|
||||
})
|
||||
return node
|
||||
18
mklab_base_indicators_extended/wizard/estimate_wizard.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<odoo>
|
||||
<record id="wizard_estimate_form" model="ir.ui.view">
|
||||
<field name="name">wizard.estimate.form</field>
|
||||
<field name="model">estimate.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="template_id"/>
|
||||
<field name="code_id"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="confirm_action" string="Подтвердить" class="btn-primary" type="object" />
|
||||
<button name="cancel" string="Отмена" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||