Интегрирован 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
+180
View File
@@ -0,0 +1,180 @@
"""Каскадное мягкое удаление связанных строк и постановка sync_queue для потомков."""
from __future__ import annotations
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import select, update
from app import db
from app.models.component import Component, Ingredient
from app.models.equipment import (
FeedDispenser,
FeedMixer,
FeedingLocation,
FeedingPeriod,
FeedingPoint,
Trip,
)
from app.models.recipe import PeriodRecipe, Recipe, UnloadingGroup
from app.models.report import (
ComponentLoadingTime,
LoadingReport,
LoadingReportComponent,
UnloadingReport,
UnloadingReportGroup,
)
from app.models.sklad import ComponentStock
from app.timeutil import utc_now_naive
logger = logging.getLogger(__name__)
TABLE_MODEL_MAP: Dict[str, Any] = {
"component": Component,
"recipe": Recipe,
"ingredient": Ingredient,
"unloading_group": UnloadingGroup,
"loading_report": LoadingReport,
"loading_report_component": LoadingReportComponent,
"component_loading_time": ComponentLoadingTime,
"unloading_report": UnloadingReport,
"unloading_report_group": UnloadingReportGroup,
"feed_dispenser": FeedDispenser,
"feeding_period": FeedingPeriod,
"period_recipes": PeriodRecipe,
"feed_mixer": FeedMixer,
"feeding_location": FeedingLocation,
"feeding_point": FeedingPoint,
"trip": Trip,
}
CASCADE_CHILDREN: Dict[str, List[Tuple[str, str]]] = {
"recipe": [
("ingredient", "recipe_id"),
("unloading_group", "recipe_id"),
],
"component": [
("ingredient", "component_id"),
],
"feed_dispenser": [
("feeding_period", "dispenser_id"),
],
"feeding_period": [
("feeding_point", "period_id"),
("period_recipes", "period_id"),
],
"feed_mixer": [
("trip", "mixer_id"),
("feeding_location", "mixer_id"),
("feeding_point", "mixer_id"),
],
"loading_report": [
("loading_report_component", "report_id"),
("component_loading_time", "report_id"),
],
"unloading_report": [
("unloading_report_group", "report_id"),
],
}
def _active_child_record_ids(child_table: str, fk_column: str, parent_id: str) -> List[str]:
model = TABLE_MODEL_MAP.get(child_table)
if model is None:
return []
col = getattr(model, fk_column, None)
if col is None:
return []
q = select(model).where(col == parent_id)
if hasattr(model, "is_deleted"):
q = q.where(model.is_deleted.is_(False))
rows = db.session.execute(q).unique().scalars().all()
if child_table == "period_recipes":
return [f"{r.period_id}:{r.recipe_id}" for r in rows]
return [str(r.id) for r in rows if getattr(r, "id", None)]
CASCADE_ENQUEUE_INFO_KEY = "_wesp_cascade_enqueue_pending"
def _schedule_cascade_enqueues(rows: List[Tuple[str, str]]) -> None:
if not rows:
return
db.session.info.setdefault(CASCADE_ENQUEUE_INFO_KEY, []).extend(rows)
def cascade_soft_delete(
table_name: str,
record_id: str,
*,
deleted_by: str = "cascade",
deleted_at: Optional[datetime] = None,
enqueue: bool = True,
defer_enqueue: bool = False,
) -> List[Tuple[str, str]]:
"""Помечает потомков is_deleted и ставит sync_queue update для каждой строки."""
when = deleted_at or utc_now_naive()
affected: List[Tuple[str, str]] = []
seen: set[Tuple[str, str]] = set()
queue: List[Tuple[str, str]] = [(table_name, record_id)]
while queue:
parent_table, parent_id = queue.pop(0)
for child_table, fk_col in CASCADE_CHILDREN.get(parent_table, []):
model = TABLE_MODEL_MAP.get(child_table)
if model is None:
continue
child_ids = _active_child_record_ids(child_table, fk_col, parent_id)
if not child_ids:
continue
tbl = model.__table__
db.session.execute(
update(tbl)
.where(tbl.c[fk_col] == parent_id)
.where(tbl.c.is_deleted.is_(False))
.values(
is_deleted=True,
deleted_at=when,
deleted_by=deleted_by,
)
)
for cid in child_ids:
key = (child_table, cid)
if key in seen:
continue
seen.add(key)
affected.append(key)
queue.append(key)
if table_name == "component":
stock_tbl = ComponentStock.__table__
db.session.execute(
update(stock_tbl)
.where(stock_tbl.c.component_id == record_id)
.where(stock_tbl.c.is_deleted.is_(False))
.values(
is_deleted=True,
deleted_at=when,
deleted_by=deleted_by,
)
)
if enqueue and affected:
if defer_enqueue:
_schedule_cascade_enqueues(affected)
else:
from app.services.sync_manager import enqueue_sync_queue_task
with db.session.no_autoflush:
for tname, rid in affected:
enqueue_sync_queue_task(tname, rid, "update", priority=1)
if affected:
logger.info(
"[SYNC-CASCADE] %s.%s → помечено потомков: %s",
table_name,
(record_id or "")[:36],
len(affected),
)
return affected