Интегрирован wesp в сайт
CI / quality (push) Canceled after 0s

This commit is contained in:
влад
2026-07-17 12:57:18 +03:00
parent 5dfa06ddbe
commit 355c0ef9f1
883 changed files with 194576 additions and 177 deletions
+365
View File
@@ -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,
)