@@ -0,0 +1,365 @@
|
||||
"""Уведомления центра «К» при изменениях плана на день."""
|
||||
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user