2026-09-19 13:13:29 +03:00
|
|
|
from sqlalchemy import func, update
|
|
|
|
|
|
2026-09-19 17:01:52 +03:00
|
|
|
from app import ids
|
2026-09-19 13:13:29 +03:00
|
|
|
from app.db import session_scope
|
|
|
|
|
from app.models import Device, Group
|
2026-09-19 17:01:52 +03:00
|
|
|
from app.services import events
|
2026-09-19 13:13:29 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _clean(name: str) -> str:
|
|
|
|
|
name = (name or "").strip()
|
|
|
|
|
if not name or len(name) > 64:
|
|
|
|
|
raise ValueError("Название группы: от 1 до 64 символов")
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def list_groups() -> list[dict]:
|
|
|
|
|
"""Группы с числом устройств, по алфавиту."""
|
|
|
|
|
with session_scope() as s:
|
|
|
|
|
counts = dict(s.query(Device.group_id, func.count(Device.id)).group_by(Device.group_id))
|
|
|
|
|
return [{"id": g.id, "name": g.name, "device_count": counts.get(g.id, 0)}
|
|
|
|
|
for g in s.query(Group).order_by(Group.name)]
|
|
|
|
|
|
|
|
|
|
|
2026-09-19 17:01:52 +03:00
|
|
|
def get_group(group_id: str) -> Group:
|
|
|
|
|
ids.check(group_id, "grp")
|
2026-09-19 13:13:29 +03:00
|
|
|
with session_scope() as s:
|
|
|
|
|
g = s.get(Group, group_id)
|
|
|
|
|
if g is None:
|
|
|
|
|
raise LookupError(f"Группа {group_id} не найдена")
|
|
|
|
|
return g
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_group(name: str) -> Group:
|
|
|
|
|
name = _clean(name)
|
|
|
|
|
with session_scope() as s:
|
|
|
|
|
if s.query(Group).filter_by(name=name).first():
|
|
|
|
|
raise ValueError(f"Группа «{name}» уже существует")
|
|
|
|
|
g = Group(name=name)
|
|
|
|
|
s.add(g)
|
|
|
|
|
s.flush()
|
2026-09-19 17:01:52 +03:00
|
|
|
events.record("group.created", "group", g.id, f"Группа «{name}» создана", data={"name": name}, s=s)
|
2026-09-19 13:13:29 +03:00
|
|
|
return g
|
|
|
|
|
|
|
|
|
|
|
2026-09-19 17:01:52 +03:00
|
|
|
def rename_group(group_id: str, name: str) -> Group:
|
|
|
|
|
ids.check(group_id, "grp")
|
2026-09-19 13:13:29 +03:00
|
|
|
name = _clean(name)
|
|
|
|
|
with session_scope() as s:
|
|
|
|
|
g = s.get(Group, group_id)
|
|
|
|
|
if g is None:
|
|
|
|
|
raise LookupError(f"Группа {group_id} не найдена")
|
|
|
|
|
if s.query(Group).filter(Group.name == name, Group.id != group_id).first():
|
|
|
|
|
raise ValueError(f"Группа «{name}» уже существует")
|
2026-09-19 17:01:52 +03:00
|
|
|
old, g.name = g.name, name
|
|
|
|
|
if old != name:
|
|
|
|
|
events.record("group.renamed", "group", g.id, f"Группа «{old}» переименована в «{name}»",
|
|
|
|
|
data={"old": old, "new": name}, s=s)
|
2026-09-19 13:13:29 +03:00
|
|
|
return g
|
|
|
|
|
|
|
|
|
|
|
2026-09-19 17:01:52 +03:00
|
|
|
def delete_group(group_id: str) -> None:
|
2026-09-19 13:13:29 +03:00
|
|
|
"""Удаляет группу; устройства остаются и становятся «Без группы» (FK в SQLite не включены)."""
|
2026-09-19 17:01:52 +03:00
|
|
|
ids.check(group_id, "grp")
|
2026-09-19 13:13:29 +03:00
|
|
|
with session_scope() as s:
|
|
|
|
|
g = s.get(Group, group_id)
|
|
|
|
|
if g is None:
|
|
|
|
|
raise LookupError(f"Группа {group_id} не найдена")
|
2026-09-19 17:01:52 +03:00
|
|
|
detached = [d for (d,) in s.query(Device.id).filter(Device.group_id == group_id)]
|
2026-09-19 13:13:29 +03:00
|
|
|
s.execute(update(Device).where(Device.group_id == group_id).values(group_id=None))
|
2026-09-19 17:01:52 +03:00
|
|
|
events.record("group.deleted", "group", g.id, f"Группа «{g.name}» удалена", data={"name": g.name, "detached_devices": detached}, s=s)
|
2026-09-19 13:13:29 +03:00
|
|
|
s.delete(g)
|
|
|
|
|
|
|
|
|
|
|
2026-09-19 17:01:52 +03:00
|
|
|
def move_devices(device_ids: list[str], group_id: str | None) -> None:
|
2026-09-19 13:13:29 +03:00
|
|
|
"""Переносит устройства в группу (None — «Без группы»)."""
|
2026-09-19 17:01:52 +03:00
|
|
|
for i in device_ids:
|
|
|
|
|
ids.check(i, "dev")
|
2026-09-19 13:13:29 +03:00
|
|
|
with session_scope() as s:
|
2026-09-19 17:01:52 +03:00
|
|
|
if group_id is not None:
|
|
|
|
|
ids.check(group_id, "grp")
|
|
|
|
|
if s.get(Group, group_id) is None:
|
|
|
|
|
raise LookupError(f"Группа {group_id} не найдена")
|
2026-09-19 13:13:29 +03:00
|
|
|
s.execute(update(Device).where(Device.id.in_(device_ids)).values(group_id=group_id))
|
2026-09-19 17:01:52 +03:00
|
|
|
events.record("devices.moved", "group", group_id, f"Перенесено устройств: {len(device_ids)}",
|
|
|
|
|
data={"device_ids": device_ids, "group_id": group_id}, s=s)
|