Files
olaper/utils.py
SERTY 62115fcd36
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
init commit
2025-07-25 03:04:51 +03:00

180 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import logging
from jinja2 import Template
from datetime import datetime
# Настройка логирования
logger = logging.getLogger(__name__)
# Уровень логирования уже должен быть настроен в app.py или основном модуле
# logger.setLevel(logging.DEBUG) # Можно убрать, если настраивается глобально
# Функция load_temps удалена, так как пресеты загружаются из API RMS
def generate_template_from_preset(preset):
"""
Генерирует один шаблон запроса OLAP на основе пресета,
подставляя плейсхолдеры для дат в соответствующий фильтр.
Args:
preset (dict): Словарь с пресетом OLAP-отчета из API RMS.
Returns:
dict: Словарь, представляющий шаблон запроса OLAP, готовый для рендеринга.
Возвращает None, если входной preset некорректен.
Raises:
ValueError: Если preset не является словарем или не содержит необходимых ключей.
Exception: Другие непредвиденные ошибки при обработке.
"""
if not isinstance(preset, dict):
logger.error("Ошибка генерации шаблона: входной 'preset' не является словарем.")
raise ValueError("Preset должен быть словарем.")
if not all(k in preset for k in ["reportType", "groupByRowFields", "aggregateFields", "filters"]):
logger.error(f"Ошибка генерации шаблона: пресет {preset.get('id', 'N/A')} не содержит всех необходимых ключей.")
raise ValueError("Пресет не содержит необходимых ключей (reportType, groupByRowFields, aggregateFields, filters).")
try:
# Копируем основные поля из пресета
template = {
"reportType": preset["reportType"],
"groupByRowFields": preset.get("groupByRowFields", []), # Используем get для необязательных полей
"aggregateFields": preset.get("aggregateFields", []),
"filters": preset.get("filters", {}) # Работаем с копией фильтров
}
# --- Обработка фильтров дат ---
# Создаем копию словаря фильтров, чтобы безопасно удалять элементы
current_filters = dict(template.get("filters", {})) # Используем get с default
filters_to_remove = []
date_filter_found_and_modified = False
# Сначала найдем и удалим все существующие фильтры типа DateRange
for key, value in current_filters.items():
if isinstance(value, dict) and value.get("filterType") == "DateRange":
filters_to_remove.append(key)
for key in filters_to_remove:
del current_filters[key]
logger.debug(f"Удален существующий DateRange фильтр '{key}' из пресета {preset.get('id', 'N/A')}.")
# Теперь добавляем правильный фильтр дат в зависимости от типа отчета
report_type = template["reportType"]
if report_type in ["SALES", "DELIVERIES"]:
# Для отчетов SALES и DELIVERIES используем "OpenDate.Typed"
# См. https://ru.iiko.help/articles/api-documentations/olap-2/a/h3__951638809
date_filter_key = "OpenDate.Typed"
logger.debug(f"Для отчета {report_type} ({preset.get('id', 'N/A')}) будет использован фильтр '{date_filter_key}'.")
current_filters[date_filter_key] = {
"filterType": "DateRange",
"from": "{{ from_date }}",
"to": "{{ to_date }}",
"includeLow": True,
"includeHigh": True
}
date_filter_found_and_modified = True # Считаем, что мы успешно добавили нужный фильтр
elif report_type == "TRANSACTIONS":
# Для отчетов по проводкам (TRANSACTIONS) используем "DateTime.DateTyped"
# См. комментарий пользователя и общие практики iiko API
date_filter_key = "DateTime.DateTyped"
logger.debug(f"Для отчета {report_type} ({preset.get('id', 'N/A')}) будет использован фильтр '{date_filter_key}'.")
current_filters[date_filter_key] = {
"filterType": "DateRange",
"from": "{{ from_date }}",
"to": "{{ to_date }}",
"includeLow": True,
"includeHigh": True
}
date_filter_found_and_modified = True # Считаем, что мы успешно добавили нужный фильтр
else:
# Для ВСЕХ ОСТАЛЬНЫХ типов отчетов:
# Пытаемся найти *любой* ключ, который может содержать дату (логика по умолчанию).
# Это менее надежно, чем явное указание ключей для SALES/DELIVERIES/TRANSACTIONS.
# Если в пресете для других типов отчетов нет стандартного поля даты,
# или оно называется иначе, этот блок может не сработать корректно.
# Мы уже удалили все DateRange фильтры. Если для этого типа отчета
# нужен был какой-то специфический DateRange фильтр, он был удален.
# Это потенциальная проблема, если неизвестные типы отчетов полагаются
# на предопределенные DateRange фильтры с другими ключами.
# Пока оставляем так: если тип отчета неизвестен, DateRange фильтр не добавляется.
logger.warning(f"Для неизвестного типа отчета '{report_type}' ({preset.get('id', 'N/A')}) не удалось автоматически определить стандартный ключ фильтра даты. "
f"Фильтр по дате не будет добавлен автоматически. Если он нужен, пресет должен содержать его с другим filterType или его нужно добавить вручную.")
# В этом случае date_filter_found_and_modified останется False
# Обновляем фильтры в шаблоне
template["filters"] = current_filters
# Логируем результат
if date_filter_found_and_modified:
logger.info(f"Шаблон для пресета {preset.get('id', 'N/A')} ('{preset.get('name', '')}') успешно сгенерирован с фильтром даты.")
else:
logger.warning(f"Шаблон для пресета {preset.get('id', 'N/A')} ('{preset.get('name', '')}') сгенерирован, но фильтр даты не был добавлен/модифицирован (тип отчета: {report_type}).")
return template
except Exception as e:
logger.error(f"Непредвиденная ошибка при генерации шаблона из пресета {preset.get('id', 'N/A')}: {str(e)}", exc_info=True)
raise # Перевыбрасываем ошибку
def render_temp(template_dict, context):
"""
Рендерит шаблон (представленный словарем) с использованием Jinja2.
Args:
template_dict (dict): Словарь, представляющий шаблон OLAP-запроса.
context (dict): Словарь с переменными для рендеринга (например, {'from_date': '...', 'to_date': '...'}).
Returns:
dict: Словарь с отрендеренным OLAP-запросом.
Raises:
Exception: Ошибки при рендеринге или парсинге JSON.
"""
try:
# Преобразуем словарь шаблона в строку JSON для Jinja
template_str = json.dumps(template_dict)
# Рендерим строку с помощью Jinja
rendered_str = Template(template_str).render(context)
# Преобразуем отрендеренную строку обратно в словарь Python
rendered_dict = json.loads(rendered_str)
logger.info('Шаблон OLAP-запроса успешно отрендерен.')
return rendered_dict
except Exception as e:
logger.error(f"Ошибка рендеринга шаблона: {str(e)}", exc_info=True)
raise
def get_dates(start_date, end_date):
"""
Проверяет даты на корректность и формат YYYY-MM-DD.
Args:
start_date (str): Дата начала в формате 'YYYY-MM-DD'.
end_date (str): Дата окончания в формате 'YYYY-MM-DD'.
Returns:
tuple: Кортеж (start_date, end_date), если даты корректны.
Raises:
ValueError: Если формат дат некорректен или дата начала позже даты окончания.
"""
date_format = "%Y-%m-%d"
try:
start = datetime.strptime(start_date, date_format)
end = datetime.strptime(end_date, date_format)
except ValueError:
logger.error(f"Некорректный формат дат: start='{start_date}', end='{end_date}'. Ожидается YYYY-MM-DD.")
raise ValueError("Некорректный формат даты. Используйте YYYY-MM-DD.")
if start > end:
logger.error(f"Дата начала '{start_date}' не может быть позже даты окончания '{end_date}'.")
raise ValueError("Дата начала не может быть позже даты окончания.")
return start_date, end_date