Работа с отчетами в Planiqum¶
Базовые понятия¶
Перед началом работы с отчетами рекомендуется ознакомиться с базовыми понятиями Planiqum, особенно с разделами: - Иерархии - Параметры - Измерения - Меры - Таблицы фактов
Структура отчета¶
Отчет в Planiqum состоит из: - Набора параметров, из которых берутся данные - Набора измерений, по которым группируются данные - Набора мер, которые отображаются в отчете - Фильтров, которые ограничивают данные
Формат AJAX запроса¶
Для получения данных отчета используется AJAX запрос к эндпоинту /reports/data/. Запрос должен содержать:
{
"layers": [
{
"parameter": "parameter1", // ключ параметра
"measure": "1", // ID меры (в виде строки!)
"dimensions": ["dpu", "month"] // ключи измерений
}
],
"filters": {
"dpu": ["DPU1", "DPU2"],
"month": ["2024-01", "2024-02"]
}
}
Важные моменты при формировании запроса¶
- ID меры всегда должен передаваться в виде строки, даже если это числовой ID
- Ключи измерений должны соответствовать ключам в таблице фактов (без префикса
dim_) - Значения в фильтрах должны соответствовать формату элементов иерархии
- Порядок измерений в запросе может отличаться от порядка в таблице фактов
Формат ответа¶
Ответ API содержит: - Массив строк с данными - Массив заголовков - Информацию о правах доступа
{
"data": [
["DPU1", "2024-01", 100.0],
["DPU1", "2024-02", 200.0],
["DPU2", "2024-01", 300.0],
["DPU2", "2024-02", 400.0]
],
"headers": ["DPU", "Month", "Quantity"],
"canEdit": true
}
Особенности ответа¶
- Порядок колонок в ответе может отличаться от порядка в запросе
- Значения в ответе всегда приводятся к строковому типу
- Числовые значения могут быть округлены
- Пустые значения представляются как
null
Тестирование¶
При написании тестов для отчетов важно учитывать:
- Необходимость создания всех зависимых объектов:
- Типы и уровни иерархий
- Элементы иерархий
- Параметры и их измерения
- Меры параметров
-
Данные в таблицах фактов
-
Правильную последовательность действий:
- Создание иерархий
- Создание параметров
- Синхронизация параметров
- Заполнение данными
-
Отправка запроса
-
Проверку прав доступа:
- Создание пользователя с нужными правами
- Проверка доступности параметров
- Проверка возможности редактирования
Пример теста¶
def test_report_data_ajax_basic(login_client, parameter1_data, parameter2_data):
"""
Тест проверяет базовую функциональность получения данных отчета через AJAX.
Подробнее о работе с отчетами в Planiqum:
docs/dev/reports.md
"""
# Подготовка данных
parameter1, measure1 = parameter1_data
parameter2, measure2 = parameter2_data
# Формирование запроса
data = {
"layers": [
{
"parameter": parameter1.key,
"measure": str(measure1.pk), # ID меры как строка
"dimensions": ["dpu", "month"]
}
],
"filters": {
"dpu": ["DPU1", "DPU2"],
"month": ["2024-01", "2024-02"]
}
}
# Отправка запроса
response = login_client.post(
"/reports/data/",
data=json.dumps(data),
content_type="application/json"
)
# Проверка ответа
assert response.status_code == 200
result = json.loads(response.content)
assert "data" in result
assert "headers" in result
assert "canEdit" in result
Поддержка единиц измерения (UOM) и валют (Currency)¶
Planiqum поддерживает автоматическую конвертацию значений мер в различные единицы измерения и валюты.
- UOM (единицы измерения) позволяют отображать значения в разных физических единицах (например, кг, шт, паллеты).
- Currency (валюты) позволяют отображать значения в разных денежных единицах (например, RUR, kRUR, mlnRUR).
Модели и их взаимосвязи¶
-
Модель
Measure:class Measure(models.Model): parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE) name = models.CharField(max_length=255) supports_currency = models.BooleanField(default=False) # Поддержка валют # ... другие поля -
Модель
HierarchyLevel:class HierarchyLevel(models.Model): hierarchy = models.ForeignKey(Hierarchy, on_delete=models.CASCADE) name = models.CharField(max_length=255) use_conversion_factors = models.BooleanField(default=False) # Поддержка UOM # ... другие поля -
Модель
Currency:class Currency(models.Model): code = models.CharField(max_length=10) # Например, "RUR", "USD" name = models.CharField(max_length=255) factor = models.DecimalField(max_digits=20, decimal_places=10) # Коэффициент конвертации # ... другие поля -
Модель
UOM:class UOM(models.Model): code = models.CharField(max_length=10) # Например, "kg", "pcs" name = models.CharField(max_length=255) # ... другие поля -
Модель
ConversionFactor:class ConversionFactor(models.Model): element = models.ForeignKey(Element, on_delete=models.CASCADE) uom = models.ForeignKey(UOM, on_delete=models.CASCADE) factor = models.DecimalField(max_digits=20, decimal_places=10) # ... другие поля
Как это работает¶
- Поддержка валют:
- Для меры параметра может быть включена поддержка валюты (флаг
supports_currency). -
В запросе данных для слоя (
layer) можно указать полеcurrency:{ "layers": [ { "parameter": "parameter1", "measure": "1", "dimensions": ["dpu", "month"], "currency": 2 // ID валюты (опционально) } ], "filters": { ... } } -
Поддержка единиц измерения:
- Для уровня иерархии может быть включена поддержка единиц измерения (флаг
use_conversion_factors). - В запросе данных для слоя (
layer) можно указать полеuom:{ "layers": [ { "parameter": "parameter1", "measure": "1", "dimensions": ["dpu", "month"], "uom": 13 // ID единицы измерения (опционально) } ], "filters": { ... } }
Примеры использования¶
-
Конвертация валют:
# Пример запроса с конвертацией валют data = { "layers": [ { "parameter": "sales", "measure": "1", "dimensions": ["dpu", "month"], "currency": 2 # Конвертация в USD } ], "filters": { "dpu": ["DPU1"], "month": ["2024-01"] } } -
Конвертация единиц измерения:
# Пример запроса с конвертацией единиц измерения data = { "layers": [ { "parameter": "production", "measure": "1", "dimensions": ["dpu", "month"], "uom": 13 # Конвертация в тонны } ], "filters": { "dpu": ["DPU1"], "month": ["2024-01"] } } -
Комбинированная конвертация:
# Пример запроса с конвертацией валют и единиц измерения data = { "layers": [ { "parameter": "sales", "measure": "1", "dimensions": ["dpu", "month"], "currency": 2, # Конвертация в USD "uom": 13 # Конвертация в тонны } ], "filters": { "dpu": ["DPU1"], "month": ["2024-01"] } }
Особенности реализации¶
- Порядок конвертации:
- Сначала применяется конвертация единиц измерения (UOM)
-
Затем применяется конвертация валют (Currency)
-
Коэффициенты конвертации:
- Для валют: коэффициент берется из модели
Currency -
Для UOM: коэффициент берется из модели
ConversionFactorдля конкретного элемента иерархии -
Обработка ошибок:
- Если указана валюта для меры без поддержки валют, возвращается ошибка
- Если указана единица измерения для уровня без поддержки UOM, возвращается ошибка
- Если не найден коэффициент конвертации, возвращается ошибка
Подробнее о моделях и настройках см. Конвертации: Валюты и Единицы измерения.
Для пользователей доступна подробная документация по работе с валютами и единицами измерения в разделе Конвертации.
Класс Filter ¶
Класс /src/planiqum/core/filters/libs/filter.py: Filter предоставляет функциональность для работы с фильтрами данных в системе.
Основные методы¶
get_readable()¶
Возвращает читаемое представление фильтра с текстовыми названиями элементов и уровней иерархии.
Возвращаемая структура:
[
{
'dimension': 'День',
'level': 1,
'level_key': 'horizon__day',
'elements': [
{
'id': 165,
'level': 1,
'level_key': 'horizon__day',
'item_str': 'День - 2024-01-01' # Формат: "Имя уровня - shortname элемента"
}
],
'isnot': False,
'group': 'Build no forecast' # Опционально, если элемент принадлежит группе
}
]
get_multistring() ¶
Преобразует фильтр в многострочное текстовое представление для отображения пользователям.
Принцип работы:
1. Использует существующий метод get_readable() для получения читаемого представления фильтра
2. Преобразует полученные данные в форматированную многострочную строку
3. Группирует элементы по:
- Группам фильтра (если есть поле group)
- Измерениям
- Уровням иерархии
Формат вывода:
- Элементы без группы: • {Измерение}: {Уровень1} - [элементы], {Уровень2} - [элементы]
- Элементы с группой:
Группа "название":
• {Измерение}: {Уровень1} - [элементы]
Использование:
from planiqum.core.filters.libs.filter import Filter
# Инициализация из JSON-строки
filter_json = '[{"dimension":"День","level":1,"elements":[{"id":165,"level":1}],"isnot":false}]'
filter_obj = Filter(filter_json)
# Получение многострочного текстового представления
text = filter_obj.get_multistring()
print(text)
# Вывод:
# • День: День - [2024-01-01]
Пример с несколькими уровнями и группами:
filter_json = '''[
{"dimension":"День","level":1,"elements":[{"id":165,"level":1},{"id":166,"level":1}],"isnot":false},
{"dimension":"Товар","level":10,"elements":[{"id":100,"level":10},{"id":101,"level":10}],"isnot":false},
{"dimension":"Товар","level":11,"elements":[{"id":200,"level":11}],"isnot":false},
{"name":"Клиент","level":36,"elements":[{"level":39,"id":563}],"dimension":"Клиент","group":"Build no forecast"}
]'''
text = Filter(filter_json).get_multistring()
print(text)
# Вывод:
# • День: День - [2024-01-01, 2024-01-02]
# • Товар: Неделя - [2024-08-12, 2024-08-19], Номер недели - [2024-02-05]
# Группа "Build no forecast":
# • Клиент: Подход к прогнозированию - [no_fcst_client]
Применение в системе¶
Метод get_multistring() используется для отображения фильтров в Excel-отчетах:
Пример использования в коде: - /src/planiqum/core/parameters/models.py: RevisionCorrection.format_filter — форматирование фильтра корректировки для отображения пользователю - /src/planiqum/core/parameters/libs/revisions_history/corrections.py: build_correction_details_dataframe — построение Excel-отчета с деталями корректировки
Код из RevisionCorrection.format_filter():
def format_filter(self):
"""
Форматирует фильтр в читаемый вид с учетом групп.
"""
try:
if not self.filter_data:
return None
from planiqum.core.filters.libs.filter import Filter
# Создаем объект Filter и используем его метод get_multistring()
filter_obj = Filter(self.filter_data)
return filter_obj.get_multistring()
except Exception as e:
return f"Фильтр применен (детали недоступны)"
Код из build_correction_details_dataframe():
# Форматируем filter_data в читаемый вид на лету
filter_data_row = meta_df[meta_df['Ключ'] == 'filter_data']
if not filter_data_row.empty:
filter_json = filter_data_row['Значение'].iloc[0]
if filter_json:
try:
from planiqum.core.filters.libs.filter import Filter
filter_obj = Filter(filter_json)
formatted_text = filter_obj.get_multistring()
meta_df.loc[filter_data_row.index, 'Значение'] = formatted_text
except Exception as e:
meta_df.loc[filter_data_row.index, 'Значение'] = "Фильтр применен (детали недоступны)"
Рекомендации по использованию¶
- Для отображения фильтров пользователям: Используйте
get_multistring()вместо сырого JSON - Для хранения в БД: Храните только
filter_dataв формате JSON, не дублируйте отформатированное представление - Для Excel-отчетов: Форматируйте фильтр на лету при построении отчета
- Для обработки старых данных: Метод автоматически обрабатывает фильтры без поля
dimension, получая его из уровня иерархии
Особенности реализации¶
- Без дополнительных запросов к БД: Метод использует данные из
get_readable(), который уже содержит всю необходимую информацию - Извлечение имени уровня: Имя уровня извлекается из
item_str(часть до " - ") - Извлечение shortname элемента: Shortname элемента извлекается из
item_str(часть после " - ") - Обработка ошибок: В случае ошибки возвращается строка "Фильтр применен (детали недоступны)"