Интегрирован 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
@@ -0,0 +1,190 @@
"""Уведомления центра зоотехника при событиях синхронизации."""
from __future__ import annotations
import logging
from typing import Any, Dict, Iterable, List, Optional, Set
from sqlalchemy import select
from app import db
from app.models import (
Ingredient,
Recipe,
SyncClient,
SyncClientDisplayName,
UnloadingGroup,
)
from app.models.report import LoadingReport, UnloadingReport
from app.models.sync import SyncQueue
from app.services.notification_center_service import create_notification, format_detail_timestamp
logger = logging.getLogger(__name__)
RECIPE_DELIVERY_TABLES = frozenset(
{"recipe", "ingredient", "unloading_group", "period_recipes"}
)
REPORT_PARENT_TABLES = frozenset({"loading_report", "unloading_report"})
def resolve_sync_client_label(node_id: Optional[str]) -> str:
if not node_id:
return "терминал"
display = db.session.execute(
select(SyncClientDisplayName.display_name).where(
SyncClientDisplayName.node_id == node_id
)
).scalar_one_or_none()
if display and str(display).strip():
return str(display).strip()
client_name = db.session.execute(
select(SyncClient.client_name).where(SyncClient.node_id == node_id)
).scalar_one_or_none()
if client_name and str(client_name).strip():
return str(client_name).strip()
return f"терминал {node_id[:8]}"
def _q(name: Optional[str]) -> str:
text = (name or "").strip()
return f"«{text}»" if text else "«без названия»"
def _recipe_name(recipe_id: Optional[str]) -> str:
if not recipe_id:
return "без названия"
recipe = db.session.get(Recipe, recipe_id)
if recipe and not getattr(recipe, "is_deleted", False):
return recipe.name or "без названия"
return "без названия"
def _recipe_id_from_sync_task(task: SyncQueue) -> Optional[str]:
table_name = task.table_name
record_id = task.record_id
if table_name == "recipe":
return record_id
if table_name == "ingredient":
row = db.session.get(Ingredient, record_id)
return row.recipe_id if row else None
if table_name == "unloading_group":
row = db.session.get(UnloadingGroup, record_id)
return row.recipe_id if row else None
if table_name == "period_recipes":
parts = str(record_id).split(":", 1)
return parts[1] if len(parts) == 2 else None
return None
def notify_tasks_delivered_to_client(
client_node_id: str, tasks: Iterable[SyncQueue]
) -> None:
"""Рейс (или его состав) доставлен на терминал клиента."""
terminal = resolve_sync_client_label(client_node_id)
when = format_detail_timestamp()
notified: Set[str] = set()
for task in tasks:
if task.table_name not in RECIPE_DELIVERY_TABLES:
continue
recipe_id = _recipe_id_from_sync_task(task)
if not recipe_id or recipe_id in notified:
continue
notified.add(recipe_id)
recipe_label = _q(_recipe_name(recipe_id))
try:
create_notification(
title="Рейс на терминале",
detail=(
f"Рейс {recipe_label} передан на терминал {terminal}{when}"
),
kind="info",
category="sync",
page="recipes",
link_kind="recipe",
link_id=recipe_id,
)
except Exception:
logger.exception(
"[SYNC-NOTIFY] recipe delivered client=%s recipe=%s",
client_node_id,
recipe_id,
)
def _report_notification_from_row(
*,
table_name: str,
report_id: str,
client_node_id: str,
) -> None:
terminal = resolve_sync_client_label(client_node_id)
when = format_detail_timestamp()
if table_name == "loading_report":
report = db.session.get(LoadingReport, report_id)
if not report or getattr(report, "is_deleted", False):
return
recipe_label = _q(report.recipe_name or _recipe_name(report.recipe_id))
create_notification(
title="Отчёт о загрузке",
detail=(
f"С терминала {terminal} пришёл отчёт о загрузке по рейсу {recipe_label}{when}"
),
kind="success",
category="sync",
page="reports",
link_kind="report_loading",
link_id=report_id,
)
return
if table_name == "unloading_report":
report = db.session.get(UnloadingReport, report_id)
if not report or getattr(report, "is_deleted", False):
return
recipe_label = _q(report.recipe_name or _recipe_name(report.recipe_id))
link_id = report.loading_report_id or report_id
create_notification(
title="Отчёт о выгрузке",
detail=(
f"С терминала {terminal} пришёл отчёт о выгрузке по рейсу {recipe_label}{when}"
),
kind="success",
category="sync",
page="reports",
link_kind="report_loading",
link_id=link_id,
)
def notify_reports_pushed_from_client(
client_node_id: str, applied_changes: Iterable[Dict[str, Any]]
) -> None:
"""Отчёт с полевого терминала принят на сервер."""
notified: Set[str] = set()
for change in applied_changes:
table_name = change.get("table_name")
if table_name not in REPORT_PARENT_TABLES:
continue
action = change.get("action")
if action not in ("create", "update"):
continue
record_id = change.get("record_id")
if not record_id:
continue
key = f"{table_name}:{record_id}"
if key in notified:
continue
notified.add(key)
try:
_report_notification_from_row(
table_name=table_name,
report_id=record_id,
client_node_id=client_node_id,
)
except Exception:
logger.exception(
"[SYNC-NOTIFY] report received client=%s table=%s id=%s",
client_node_id,
table_name,
record_id,
)