Files
2026-07-17 12:57:18 +03:00

366 lines
10 KiB
Python
Raw Permalink 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.
"""Уведомления центра «К» при изменениях плана на день."""
from __future__ import annotations
import logging
from datetime import date
from typing import Optional
from sqlalchemy import select
from app import db
from app.models import Component, Ingredient, Recipe, UnloadingGroup
from app.services.notification_center_service import create_notification, format_detail_timestamp
logger = logging.getLogger(__name__)
def _q(name: Optional[str]) -> str:
text = (name or "").strip()
return f{text}»" if text else "«без названия»"
def _recipe_name(recipe_id: str) -> str:
recipe = db.session.execute(
select(Recipe.name).where(Recipe.id == recipe_id, Recipe.is_deleted.is_(False))
).scalar_one_or_none()
return recipe or "без названия"
def _ingredient_name(ingredient_id: str) -> str:
row = db.session.execute(
select(Ingredient.name).where(
Ingredient.id == ingredient_id,
Ingredient.is_deleted.is_(False),
)
).scalar_one_or_none()
return row or "без названия"
def _group_name(group_id: str) -> str:
row = db.session.execute(
select(UnloadingGroup.name).where(
UnloadingGroup.id == group_id,
UnloadingGroup.is_deleted.is_(False),
)
).scalar_one_or_none()
return row or "без названия"
def _component_name(component_id: str) -> str:
row = db.session.execute(
select(Component.name).where(
Component.id == component_id,
Component.is_deleted.is_(False),
)
).scalar_one_or_none()
return row or "без названия"
def _plan_date_label(plan_date: date) -> str:
months = (
"января",
"февраля",
"марта",
"апреля",
"мая",
"июня",
"июля",
"августа",
"сентября",
"октября",
"ноября",
"декабря",
)
return f"{plan_date.day} {months[plan_date.month - 1]} {plan_date.year}"
def _duration_hint(*, plan_date: date, valid_until: Optional[date]) -> str:
if valid_until and valid_until > plan_date:
return f", действует до {_plan_date_label(valid_until)}"
return ""
def _on_date(plan_date: date) -> str:
return f"на {_plan_date_label(plan_date)}"
def _actor(user: str) -> str:
name = (user or "").strip()
if not name or name == "system":
return "система"
return name
def _detail_body(*parts: str, when: str, user: str) -> str:
core = ". ".join(p for p in parts if p)
return f"{core}. {when}, {_actor(user)}"
def _notify(
*,
title: str,
detail: str,
kind: str,
plan_date: date,
user: str,
) -> None:
try:
create_notification(
title=title,
detail=detail,
kind=kind,
category="daily_plan",
page="daily_plan",
link_kind="daily_plan",
link_id=plan_date.isoformat(),
user_login=user or None,
)
except Exception:
logger.exception("[DAILY-PLAN-NOTIFY] %s", title)
def notify_trip_skipped(
recipe_id: str,
plan_date: date,
*,
valid_until: Optional[date] = None,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
dur = _duration_hint(plan_date=plan_date, valid_until=valid_until)
_notify(
title=f"Рейс {recipe} убран из плана",
detail=_detail_body(
f"Рейс {recipe} исключён из плана {_on_date(plan_date)}{dur}",
when=when,
user=user,
),
kind="warning",
plan_date=plan_date,
user=user,
)
def notify_trip_unskipped(recipe_id: str, plan_date: date, *, user: str = "system") -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
_notify(
title=f"Рейс {recipe} снова в плане",
detail=_detail_body(
f"Рейс {recipe} снова включён в план {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_ingredient_skipped(
recipe_id: str,
ingredient_id: str,
plan_date: date,
*,
valid_until: Optional[date] = None,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
component = _q(_ingredient_name(ingredient_id))
dur = _duration_hint(plan_date=plan_date, valid_until=valid_until)
_notify(
title=f"{component} убран из плана {recipe}",
detail=_detail_body(
f"Компонент {component} убран из плана рейса {recipe} {_on_date(plan_date)}{dur}",
when=when,
user=user,
),
kind="warning",
plan_date=plan_date,
user=user,
)
def notify_ingredient_unskipped(
recipe_id: str,
ingredient_id: str,
plan_date: date,
*,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
component = _q(_ingredient_name(ingredient_id))
_notify(
title=f"{component} снова в плане {recipe}",
detail=_detail_body(
f"Компонент {component} снова в плане рейса {recipe} {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_ingredients_unskipped_all(recipe_id: str, plan_date: date, *, count: int, user: str) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
_notify(
title=f"Компоненты снова в плане {recipe}",
detail=_detail_body(
f"Сняты все исключения компонентов ({count}) рейса {recipe} {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_unloading_group_skipped(
recipe_id: str,
group_id: str,
plan_date: date,
*,
valid_until: Optional[date] = None,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
group = _q(_group_name(group_id))
dur = _duration_hint(plan_date=plan_date, valid_until=valid_until)
_notify(
title=f"Группа {group} убрана из плана {recipe}",
detail=_detail_body(
f"Группа {group} убрана из плана рейса {recipe} {_on_date(plan_date)}{dur}",
when=when,
user=user,
),
kind="warning",
plan_date=plan_date,
user=user,
)
def notify_unloading_group_unskipped(
recipe_id: str,
group_id: str,
plan_date: date,
*,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
group = _q(_group_name(group_id))
_notify(
title=f"Группа {group} снова в плане {recipe}",
detail=_detail_body(
f"Группа {group} снова в плане рейса {recipe} {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_unloading_groups_unskipped_all(recipe_id: str, plan_date: date, *, count: int, user: str) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
_notify(
title=f"Группы снова в плане {recipe}",
detail=_detail_body(
f"Сняты все исключения групп ({count}) рейса {recipe} {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_ingredient_replaced(
recipe_id: str,
ingredient_id: str,
replacement_component_id: str,
plan_date: date,
*,
valid_until: Optional[date] = None,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
original = _q(_ingredient_name(ingredient_id))
replacement = _q(_component_name(replacement_component_id))
dur = _duration_hint(plan_date=plan_date, valid_until=valid_until)
_notify(
title=f"{original}{replacement} в {recipe}",
detail=_detail_body(
f"В рейсе {recipe} вместо {original} {_on_date(plan_date)} будет {replacement}{dur}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_component_replaced_in_plan(
component_id: str,
replacement_component_id: str,
plan_date: date,
*,
recipe_count: int,
valid_until: Optional[date] = None,
user: str = "system",
) -> None:
when = format_detail_timestamp()
original = _q(_component_name(component_id))
replacement = _q(_component_name(replacement_component_id))
dur = _duration_hint(plan_date=plan_date, valid_until=valid_until)
count = max(int(recipe_count or 0), 1)
recipes_word = "рецепте" if count == 1 else "рецептах"
_notify(
title=f"{original}{replacement} в {count} {recipes_word}",
detail=_detail_body(
f"Во всех рейсах плана вместо {original} {_on_date(plan_date)} будет {replacement}{dur}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)
def notify_ingredient_replacement_undone(
recipe_id: str,
ingredient_id: str,
plan_date: date,
*,
user: str = "system",
) -> None:
when = format_detail_timestamp()
recipe = _q(_recipe_name(recipe_id))
original = _q(_ingredient_name(ingredient_id))
_notify(
title=f"{original} снова в плане {recipe}",
detail=_detail_body(
f"Замена отменена: {original} снова в плане рейса {recipe} {_on_date(plan_date)}",
when=when,
user=user,
),
kind="info",
plan_date=plan_date,
user=user,
)