Использование noop-метода корректировки для кастомных триггеров¶
Введение¶
noop_correction (no-operation correction) — это специальный метод корректировки, который не изменяет данные напрямую, а только регистрирует метаданные о корректировке и запускает триггеры. Этот паттерн используется когда обработка данных должна происходить в кастомном триггере, а не в стандартном методе корректировки.
Основные концепции¶
Работа с ревизиями vs. работа с корректировками¶
В Planiqum существует два подхода к обработке изменений данных:
1. Традиционный подход: Работа с ревизиями¶
Настройка параметра:
Parameter.use_revision = TrueParameter.track_corrections = False(опционально)
Как работает:
- Пользователь вводит данные на любом уровне иерархии (например, "Всего по стране = 1000")
- Метод корректировки (например,
split_proportionally_by_base) сразу каскадирует данные на базовый уровень иерархии (например, по регионам пропорционально весам) - Данные записываются в таблицу фактов (
fact__parameter_name) - Данные записываются в таблицу ревизий (
revision__measure_key) - Создаётся запись
RevisionMeasure(связь ревизия-мера) - Триггеры запускаются с уже обработанными данными на базовом уровне
Результат: Данные каскадированы стандартным методом на базовый уровень.
2. Паттерн "отложенной обработки": Работа с корректировками¶
Настройка параметра:
Parameter.track_corrections = True⚠️ Обязательно!Parameter.use_revision = False(таблицы ревизий не создаются)
Как работает:
- Пользователь вводит данные на любом уровне иерархии
- Метод корректировки
noop_correctionНЕ обрабатывает данные, а только: - ✅ Сохраняет метаданные в
RevisionCorrection(кто, когда, описание, фильтры) - ✅ Сохраняет детали в
RevisionCorrectionDetail(какая мера, какие изменения, UOM, валюта) - ✅ Регистрирует "пустое" изменение меры в
FactManager - ❌ НЕ записывает данные в таблицу фактов
- Триггер запускается и получает:
- Пустую
FactTable(len=0) - Информацию о мере
- Доступ к
RevisionCorrectionчерезrevision - Триггер самостоятельно:
- Читает оригинальные данные из
RevisionCorrectionDetail.changes - Применяет кастомную логику каскадирования/трансформации
- Записывает результат в таблицу фактов (или отправляет во внешнюю систему)
Результат: Данные обрабатываются кастомной логикой в триггере.
Сценарии использования¶
Когда использовать noop_correction + кастомный триггер:
- Сложная бизнес-логика каскадирования, которую нельзя выразить стандартными методами
- Интеграция с внешними системами (триггер отправляет данные во внешнюю систему для обработки)
- Условное каскадирование (разная логика в зависимости от контекста)
- Многошаговая обработка (чтение данных из нескольких параметров, сложные вычисления)
- Асинхронная обработка (триггер ставит задачу в очередь, обработка происходит позже)
Когда использовать традиционные методы:
- Стандартное пропорциональное распределение
- Простое копирование значений
- Типовые сценарии каскадирования
Настройка меры для работы с noop¶
Шаг 1: Активировать хранение истории корректировок¶
В админке параметра установите:
Parameter.track_corrections = True # ⚠️ Обязательно!
Это активирует сохранение метаданных корректировок в моделях RevisionCorrection и RevisionCorrectionDetail.
Шаг 2: Подключить noop-метод корректировки¶
В админке меры:
1. Перейдите в раздел "Методы корректировки"
2. Добавьте метод noop_correction
3. Установите его как метод по умолчанию (опционально)
Альтернативно через код:
from planiqum.core.scripts.models import CorrectionMethod, MeasureCorrectionMethod
noop_method = CorrectionMethod.objects.get(shortname='noop_correction')
MeasureCorrectionMethod.objects.create(
measure=your_measure,
correction_method=noop_method,
is_default=True
)
Шаг 3: Создать и подключить кастомный триггер¶
Создайте Python-скрипт триггера (например, в your_app/scripts/triggers.py):
from planiqum.core.scripts.decorators import pq_script, SCRIPT_TYPE
@pq_script(
shortname='process_custom_correction',
label='Обработка кастомной корректировки',
script_type=SCRIPT_TYPE.TRIGGER
)
def process_custom_correction(revision, fact_manager, measure, facts, user=None, **kwargs):
"""
Кастомный триггер для обработки корректировок через noop-метод
"""
from planiqum.core.parameters.models import RevisionCorrection
# Получаем метаданные корректировки
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return []
# Обрабатываем каждую изменённую меру
for detail in correction.details.all():
# Ваша кастомная логика обработки
process_measure_changes(detail, fact_manager)
return []
Подключите триггер к мере в админке:
1. Перейдите в раздел "Триггеры"
2. Добавьте ваш триггер
3. Установите auto_run = True
4. Установите приоритет (порядок выполнения)
Разработка триггеров на Python¶
Сигнатура триггера¶
def your_trigger(
revision=None, # Revision instance
fact_manager=None, # FactManager instance
measure=None, # Measure instance (может быть None при TRIGGER_BY_FACTS=False)
facts=None, # FactTable instance (может быть None)
user=None, # User ID
**kwargs
):
"""Ваш триггер"""
pass
⚠️ Важно: При настройке TRIGGER_BY_FACTS=False параметры measure и facts передаются как None. В этом случае информацию нужно получать из fact_manager.changed_data или из моделей.
Получение списка изменённых мер¶
from planiqum.core.parameters.models import RevisionCorrection
def your_trigger(revision, fact_manager, **kwargs):
# Получаем корректировку по ревизии
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return []
# Получаем все изменённые меры
for detail in correction.details.all():
measure = detail.measure
# Обрабатываем меру...
Чтение метаданных корректировки¶
# Общая информация о корректировке
correction = RevisionCorrection.objects.filter(revision=revision).first()
# Кто внёс изменения
user = correction.created_by
username = user.username
# Когда были внесены изменения
timestamp = correction.created_at
# Описание корректировки
description = correction.description
# Причина корректировки (если указана)
reason_code = correction.reason.code if correction.reason else None
# На каких уровнях иерархии вносились изменения
dimensions = correction.dimensions
# Формат: [{'name': 'Клиент', 'level': 35}, {'name': 'Товар', 'level': 15}]
# Применённые фильтры
import json
filter_chunks = json.loads(correction.filter_data) if correction.filter_data else []
# Можно восстановить объект Filter:
from planiqum.core.filters.libs.filter import Filter
filter_obj = Filter(filter_chunks)
Чтение данных изменений¶
# Для конкретной меры
detail = correction.details.filter(measure=your_measure).first()
# Данные изменений
changes = detail.changes
# Формат: [[[item_id1, item_id2, ...], new_value], ...]
# Пример обработки
for change in changes:
dimension_values = change[0] # Список ID элементов иерархий
new_value = change[1] # Введённое пользователем значение
# dimension_values соответствуют dimensions из RevisionCorrection
# Например, если dimensions = [{'name': 'Клиент', 'level': 35}, {'name': 'Товар', 'level': 15}]
# То dimension_values = [client_item_id, product_item_id]
# Ваша обработка...
# Единицы измерения и валюта
uom = detail.uom # Uom instance или None
currency = detail.currency # Currency instance или None
# Метод корректировки (будет 'noop_correction')
method = detail.correction_method
Полный пример триггера¶
from planiqum.core.scripts.decorators import pq_script, SCRIPT_TYPE
from planiqum.core.parameters.models import RevisionCorrection
from planiqum.core.hierarchy.models import Item
@pq_script(
shortname='custom_cascade_trigger',
label='Кастомное каскадирование данных',
script_type=SCRIPT_TYPE.TRIGGER
)
def custom_cascade_trigger(revision, fact_manager, measure=None, facts=None, **kwargs):
"""
Триггер для кастомной обработки корректировок через noop-метод.
Читает данные из RevisionCorrectionDetail и применяет
кастомную логику каскадирования на базовый уровень.
"""
# Получаем корректировку
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return []
# Обрабатываем каждую изменённую меру
for detail in correction.details.all():
current_measure = detail.measure
changes = detail.changes
# Получаем информацию об измерениях
dimensions_info = correction.dimensions
# Формат: [{'name': 'Horizon', 'level': 10}, {'name': 'Material', 'level': 4}]
# Обрабатываем каждое изменение
for change in changes:
dimension_values = change[0] # [item_id1, item_id2, ...]
user_value = change[1] # Введённое значение
# Получаем элементы иерархий
items = Item.objects.filter(id__in=dimension_values)
items_dict = {item.id: item for item in items}
# Ваша кастомная логика каскадирования
# Например, распределяем значение по дочерним элементам с учётом бизнес-правил
# 1. Получаем дочерние элементы для каскадирования
base_items = get_base_level_items(items_dict, dimensions_info)
# 2. Применяем кастомные веса/правила
weights = calculate_custom_weights(base_items, current_measure)
# 3. Распределяем значение
for base_item, weight in weights.items():
cascaded_value = user_value * weight
# Записываем в таблицу фактов через fact_manager
# ... ваш код записи ...
return []
def get_base_level_items(items_dict, dimensions_info):
"""Получение элементов базового уровня для каскадирования"""
# Ваша логика
pass
def calculate_custom_weights(items, measure):
"""Расчёт кастомных весов для распределения"""
# Ваша логика
pass
Работа с SQL-запросами (для внешних систем)¶
Получение списка изменённых мер по ревизии¶
SELECT DISTINCT
m.id AS measure_id,
m.key AS measure_key,
m.shortname AS measure_name,
p.id AS parameter_id,
p.key AS parameter_key,
p.shortname AS parameter_name
FROM
core_revision_correction_details rcd
JOIN core_revision_corrections rc ON rcd.correction_id = rc.id
JOIN core_parameter_measure m ON rcd.measure_id = m.id
JOIN core_parameter p ON m.parameter_id = p.id
WHERE
rc.revision_id = :revision_id;
Получение метаданных корректировки¶
SELECT
rc.id AS correction_id,
rc.revision_id,
rc.created_at,
rc.description,
rc.cells_changed,
rc.dimensions, -- JSON: [{"name": "Horizon", "level": 10}, ...]
rc.filter_data, -- JSON: chunks фильтра
u.username AS created_by_username,
cr.code AS reason_code,
cr.description AS reason_description
FROM
core_revision_corrections rc
JOIN auth_user u ON rc.created_by_id = u.id
LEFT JOIN core_parameter_correctionreason cr ON rc.reason_id = cr.id
WHERE
rc.revision_id = :revision_id;
Получение деталей изменений¶
SELECT
rcd.id AS detail_id,
rcd.layer_id,
rcd.changes, -- JSON: [[[item_id1, item_id2], value], ...]
rcd.cells_changed,
m.id AS measure_id,
m.key AS measure_key,
m.shortname AS measure_name,
cm.shortname AS correction_method,
uom.shortname AS uom,
c.shortname AS currency
FROM
core_revision_correction_details rcd
JOIN core_revision_corrections rc ON rcd.correction_id = rc.id
JOIN core_parameter_measure m ON rcd.measure_id = m.id
LEFT JOIN core_script_correction_method scm ON rcd.correction_method_id = scm.script_ptr_id
LEFT JOIN core_script cm ON scm.script_ptr_id = cm.id
LEFT JOIN core_conversion_uom uom ON rcd.uom_id = uom.id
LEFT JOIN core_conversion_currency c ON rcd.currency_id = c.id
WHERE
rc.revision_id = :revision_id;
Декодирование JSON-данных в SQL¶
Извлечение измерений¶
-- PostgreSQL: Распаковка dimensions
SELECT
rc.revision_id,
jsonb_array_elements(rc.dimensions::jsonb) AS dimension
FROM core_revision_corrections rc
WHERE rc.revision_id = :revision_id;
-- Результат:
-- {"name": "Horizon", "level": 10}
-- {"name": "Material", "level": 4}
-- Получение конкретных полей
SELECT
rc.revision_id,
dim->>'name' AS dimension_name,
(dim->>'level')::integer AS level_id
FROM
core_revision_corrections rc,
jsonb_array_elements(rc.dimensions::jsonb) AS dim
WHERE
rc.revision_id = :revision_id;
Извлечение изменений (changes)¶
-- PostgreSQL: Распаковка changes
SELECT
rcd.id AS detail_id,
m.key AS measure_key,
change AS change_data
FROM
core_revision_correction_details rcd
JOIN core_parameter_measure m ON rcd.measure_id = m.id,
jsonb_array_elements(rcd.changes::jsonb) AS change
WHERE
rcd.correction_id = :correction_id;
-- Результат (каждое изменение в отдельной строке):
-- detail_id | measure_key | change_data
-- 1 | qty | [[737, 713], 120]
-- 1 | qty | [[737, 644], 60]
-- Извлечение значений из change
SELECT
rcd.id AS detail_id,
m.key AS measure_key,
change->0 AS dimension_values, -- [737, 713]
(change->1)::numeric AS new_value -- 120
FROM
core_revision_correction_details rcd
JOIN core_parameter_measure m ON rcd.measure_id = m.id,
jsonb_array_elements(rcd.changes::jsonb) AS change
WHERE
rcd.correction_id = :correction_id;
Извлечение ID элементов иерархий¶
-- Получение отдельных ID элементов из dimension_values
SELECT
rcd.id AS detail_id,
m.key AS measure_key,
jsonb_array_elements(change->0)::integer AS item_id,
(change->1)::numeric AS new_value
FROM
core_revision_correction_details rcd
JOIN core_parameter_measure m ON rcd.measure_id = m.id,
jsonb_array_elements(rcd.changes::jsonb) AS change
WHERE
rcd.correction_id = :correction_id;
-- Результат:
-- detail_id | measure_key | item_id | new_value
-- 1 | qty | 737 | 120
-- 1 | qty | 713 | 120
-- 1 | qty | 737 | 60
-- 1 | qty | 644 | 60
Полный пример SQL-запроса для внешней системы¶
-- Получить все изменения для обработки во внешней системе
WITH correction_data AS (
SELECT
rc.id AS correction_id,
rc.revision_id,
rc.created_at,
rc.description,
rc.dimensions,
u.username AS created_by
FROM
core_revision_corrections rc
JOIN auth_user u ON rc.created_by_id = u.id
WHERE
rc.revision_id = :revision_id
),
changes_expanded AS (
SELECT
cd.correction_id,
cd.revision_id,
cd.created_at,
cd.description,
cd.created_by,
rcd.id AS detail_id,
m.id AS measure_id,
m.key AS measure_key,
m.shortname AS measure_name,
change->0 AS dimension_values,
(change->1)::numeric AS new_value,
uom.shortname AS uom,
c.shortname AS currency
FROM
correction_data cd
JOIN core_revision_correction_details rcd ON rcd.correction_id = cd.correction_id
JOIN core_parameter_measure m ON rcd.measure_id = m.id
LEFT JOIN core_conversion_uom uom ON rcd.uom_id = uom.id
LEFT JOIN core_conversion_currency c ON rcd.currency_id = c.id,
jsonb_array_elements(rcd.changes::jsonb) AS change
)
SELECT * FROM changes_expanded;
Формат данных в моделях¶
RevisionCorrection¶
| Поле | Тип | Описание | Пример |
|---|---|---|---|
revision_id |
FK | Ссылка на Revision | 90 |
created_by_id |
FK | Пользователь | 1 |
created_at |
DateTime | Время создания | 2025-10-03 14:13:28 |
description |
Text | Комментарий пользователя | "Корректировка плана" |
cells_changed |
Integer | Количество изменённых ячеек | 3 |
dimensions |
JSON | Измерения | [{"name": "Клиент", "level": 35}] |
filter_data |
JSON | Фильтр | [{"dimension": "region", "elements": [...]}] |
reason_id |
FK | Причина корректировки | 1 |
RevisionCorrectionDetail¶
| Поле | Тип | Описание | Пример |
|---|---|---|---|
correction_id |
FK | Ссылка на RevisionCorrection | 1 |
layer_id |
Integer | Идентификатор слоя | -2 |
measure_id |
FK | Мера которая изменялась | 45 |
changes |
JSON | Список изменений | [[[737, 713], 120], [[737, 644], 60]] |
cells_changed |
Integer | Количество ячеек | 2 |
uom_id |
FK | Единица измерения | 3 или NULL |
currency_id |
FK | Валюта | 2 или NULL |
correction_method_id |
FK | Метод корректировки | 7 |
Формат поля changes¶
changes = [
[[item_id_dim1, item_id_dim2, ...], new_value],
[[item_id_dim1, item_id_dim2, ...], new_value],
...
]
Пример:
# Для параметра с измерениями [Клиент (level=35), Товар (level=15)]
changes = [
[[737, 713], 120], # Клиент 737, Товар 713 → значение 120
[[737, 644], 60], # Клиент 737, Товар 644 → значение 60
]
# Порядок item_id соответствует порядку в dimensions
dimensions = [
{'name': 'Клиент', 'level': 35}, # 0-й элемент → dimension_values[0]
{'name': 'Товар', 'level': 15} # 1-й элемент → dimension_values[1]
]
Примеры использования¶
Пример 1: Простое каскадирование с логированием¶
@pq_script(shortname='simple_cascade_with_log', script_type=SCRIPT_TYPE.TRIGGER)
def simple_cascade_with_log(revision, fact_manager, **kwargs):
"""Каскадирует данные на базовый уровень с подробным логированием"""
import logging
from planiqum.core.parameters.models import RevisionCorrection
from planiqum.core.hierarchy.models import Item
logger = logging.getLogger(__name__)
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return []
for detail in correction.details.all():
measure = detail.measure
parameter = measure.parameter
logger.info(f"Обработка меры {measure.key}, изменений: {len(detail.changes)}")
for change in detail.changes:
dimension_values = change[0]
value = change[1]
# Получаем элементы иерархий
items = Item.objects.filter(id__in=dimension_values)
# Находим все дочерние элементы базового уровня
base_items = []
for item in items:
base_items.extend(item.get_descendants_at_base_level())
# Распределяем пропорционально (равномерно)
distributed_value = value / len(base_items) if base_items else 0
# Записываем факты через fact_manager
facts_table = fact_manager.get_facts(parameter, measure)
for base_item in base_items:
# Находим или создаём факт и устанавливаем значение
# ... код записи через fact_manager ...
pass
return []
Пример 2: Отправка данных во внешнюю систему¶
@pq_script(shortname='send_to_external_system', script_type=SCRIPT_TYPE.TRIGGER)
def send_to_external_system(revision, **kwargs):
"""Отправляет данные корректировки во внешнюю систему для обработки"""
import requests
from planiqum.core.parameters.models import RevisionCorrection
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return []
# Формируем данные для отправки
payload = {
'revision_id': revision.id,
'timestamp': correction.created_at.isoformat(),
'user': correction.created_by.username,
'description': correction.description,
'measures': []
}
for detail in correction.details.all():
measure_data = {
'measure_key': detail.measure.key,
'parameter_key': detail.measure.parameter.key,
'changes': detail.changes,
'uom': detail.uom.shortname if detail.uom else None,
'currency': detail.currency.shortname if detail.currency else None
}
payload['measures'].append(measure_data)
# Отправляем во внешнюю систему
response = requests.post(
'https://external-system.com/api/process-correction',
json=payload,
timeout=30
)
if response.status_code != 200:
return [(1, f"Ошибка отправки в внешнюю систему: {response.text}")]
return []
Пример 3: SQL-скрипт для внешней системы¶
-- Скрипт для получения всех данных корректировки для обработки
-- Использовать в хранимой процедуре или внешнем приложении
-- Шаг 1: Получить основную информацию о корректировке
SELECT
rc.id AS correction_id,
rc.revision_id,
rc.created_at,
rc.description,
u.username AS created_by,
rc.dimensions::text AS dimensions_json,
rc.filter_data AS filter_json
FROM
core_revision_corrections rc
JOIN auth_user u ON rc.created_by_id = u.id
WHERE
rc.revision_id = :revision_id;
-- Шаг 2: Получить изменения с расшифровкой элементов иерархий
WITH changes_raw AS (
SELECT
rcd.id AS detail_id,
m.key AS measure_key,
p.key AS parameter_key,
change->0 AS dimension_values_json,
(change->1)::numeric AS new_value,
uom.shortname AS uom,
c.shortname AS currency,
rc.dimensions AS dimensions_info
FROM
core_revision_correction_details rcd
JOIN core_revision_corrections rc ON rcd.correction_id = rc.id
JOIN core_parameter_measure m ON rcd.measure_id = m.id
JOIN core_parameter p ON m.parameter_id = p.id
LEFT JOIN core_conversion_uom uom ON rcd.uom_id = uom.id
LEFT JOIN core_conversion_currency c ON rcd.currency_id = c.id,
jsonb_array_elements(rcd.changes::jsonb) AS change
WHERE
rc.revision_id = :revision_id
),
items_expanded AS (
SELECT
cr.*,
item_index,
item_id::integer AS item_id
FROM
changes_raw cr,
jsonb_array_elements(cr.dimension_values_json) WITH ORDINALITY AS t(item_id, item_index)
)
SELECT
ie.detail_id,
ie.measure_key,
ie.parameter_key,
ie.item_index - 1 AS dimension_index,
(ie.dimensions_info->>(ie.item_index - 1))::jsonb->>'name' AS dimension_name,
(ie.dimensions_info->>(ie.item_index - 1))::jsonb->'level' AS level_id,
ie.item_id,
hi.shortname AS item_code,
hi.description AS item_name,
ie.new_value,
ie.uom,
ie.currency
FROM
items_expanded ie
JOIN core_hierarchy_item hi ON hi.id = ie.item_id
ORDER BY
ie.detail_id, ie.dimension_index;
Пример интеграции с внешней системой¶
Триггер в Planiqum¶
@pq_script(shortname='notify_external_system', script_type=SCRIPT_TYPE.TRIGGER)
def notify_external_system(revision, **kwargs):
"""
Уведомляет внешнюю систему о корректировке.
Отправляет только revision_id, внешняя система сама читает детали через SQL.
"""
import requests
from django.conf import settings
# Отправляем только ID ревизии
response = requests.post(
f"{settings.EXTERNAL_SYSTEM_URL}/api/process-correction",
json={'revision_id': revision.id},
headers={'Authorization': f'Bearer {settings.EXTERNAL_SYSTEM_TOKEN}'},
timeout=30
)
if response.status_code != 200:
return [(1, f"Ошибка уведомления внешней системы: {response.text}")]
return []
Обработчик во внешней системе (псевдокод)¶
# external_system/handlers.py
def process_correction(revision_id):
"""Обработчик корректировки во внешней системе"""
# Подключаемся к БД Planiqum
conn = psycopg2.connect(PLANIQUM_DB_CONN_STRING)
cursor = conn.cursor()
# Получаем метаданные
cursor.execute("""
SELECT rc.dimensions, rc.filter_data, rc.description
FROM core_revision_corrections rc
WHERE rc.revision_id = %s
""", [revision_id])
correction_meta = cursor.fetchone()
dimensions = json.loads(correction_meta[0])
# Получаем изменения
cursor.execute("""
SELECT
m.key AS measure_key,
rcd.changes,
uom.shortname AS uom,
c.shortname AS currency
FROM
core_revision_correction_details rcd
JOIN core_revision_corrections rc ON rcd.correction_id = rc.id
JOIN core_parameter_measure m ON rcd.measure_id = m.id
LEFT JOIN core_conversion_uom uom ON rcd.uom_id = uom.id
LEFT JOIN core_conversion_currency c ON rcd.currency_id = c.id
WHERE
rc.revision_id = %s
""", [revision_id])
for row in cursor.fetchall():
measure_key = row[0]
changes = json.loads(row[1])
# Обрабатываем каждое изменение
for change in changes:
dimension_values = change[0]
new_value = change[1]
# Ваша логика обработки в внешней системе
process_in_external_system(measure_key, dimension_values, new_value, dimensions)
conn.close()
Рекомендации и best practices¶
1. Обработка ошибок¶
def your_trigger(revision, fact_manager, **kwargs):
from planiqum.core.parameters.models import RevisionCorrection
try:
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction:
return [(1, "Корректировка не найдена для ревизии")]
# Ваша логика...
except Exception as e:
# Возвращаем ошибку для логирования
return [(2, f"Ошибка обработки: {str(e)}")]
return [] # Успех
2. Проверка наличия изменений¶
correction = RevisionCorrection.objects.filter(revision=revision).first()
if not correction or not correction.details.exists():
# Нет изменений для обработки
return []
3. Получение элементов иерархий¶
from planiqum.core.hierarchy.models import Item
# Собираем все ID элементов
all_item_ids = set()
for detail in correction.details.all():
for change in detail.changes:
dimension_values = change[0]
all_item_ids.update(dimension_values)
# Получаем все элементы одним запросом (эффективно)
items_dict = {item.id: item for item in Item.objects.filter(id__in=all_item_ids)}
# Используем в обработке
for change in detail.changes:
dimension_values = change[0]
items = [items_dict[item_id] for item_id in dimension_values]
4. Работа с фильтрами¶
import json
from planiqum.core.filters.libs.filter import Filter
# Восстановление объекта фильтра
filter_chunks = json.loads(correction.filter_data) if correction.filter_data else []
filter_obj = Filter(filter_chunks)
# Использование фильтра для получения фактов
facts = fact_manager.get_facts(
parameter=measure.parameter,
measure=measure,
filter_=filter_obj
)
Решение проблем¶
Триггер не запускается¶
Проблема: После корректировки с noop_correction триггер не выполняется.
Решение:
- Проверьте что триггер подключён к мере:
MeasureTrigger.objects.filter(measure=your_measure, auto_run=True) - Проверьте что
noop_correctionподключён как метод корректировки для меры - Проверьте логи: триггер может выполняться, но падать с ошибкой
RevisionCorrection не создаётся¶
Проблема: После корректировки нет данных в RevisionCorrection.
Решение:
- Убедитесь что
Parameter.track_corrections = True - Проверьте что хотя бы одна мера в корректировке имеет
track_corrections=Trueна уровне параметра
Данные записываются в факты при noop¶
Проблема: Несмотря на noop_correction, данные попадают в таблицу фактов.
Решение:
- Это невозможно при корректной реализации
noop_correction - Проверьте что используется именно
noop_correction, а не другой метод - Возможно триггер записывает данные — это нормально, триггер должен обработать корректировку
Сравнение подходов¶
| Аспект | Традиционный метод | noop + Триггер |
|---|---|---|
| Каскадирование | Автоматическое стандартными методами | Кастомное в триггере |
| Запись данных | Метод корректировки → факты | Триггер → факты |
| Гибкость | Ограничена методом | Полная свобода в триггере |
| Сложность | Простая настройка | Требует написания кода триггера |
| История | RevisionMeasure (опционально) | RevisionCorrection (обязательно) |
| Уровень данных | Базовый уровень иерархии | Уровень ввода пользователя |
| Интеграция | Только внутри Planiqum | Легко интегрировать с внешними системами |