"""Демо-данные портала: склады и инструмент, заявки, инциденты, суточные задания, карта завода, карточки сотрудников.""" import random import struct import zlib from datetime import datetime, time, timedelta from sqlalchemy.orm import Session from . import media, service, settings_store as ss from .models import (DailyTask, Equipment, Incident, IncidentUnit, InventoryItem, InventoryMove, Position, ServiceUnit, Stock, TaskEvent, ToolRequest, User, Warehouse) # план завода: помещения в % от картинки (x, y, w, h, цвет заливки) ROOMS = [ (3, 4, 44, 46, (226, 232, 240)), # Цех №1 (с компрессорной) (50, 4, 47, 46, (224, 231, 255)), # Цех №2 (3, 55, 26, 41, (254, 226, 226)), # Котельная (32, 55, 28, 41, (220, 252, 231)), # Насосная станция (63, 55, 16, 41, (254, 249, 195)), # Подстанция (82, 55, 15, 41, (237, 233, 254)), # Склады ] # где стоит оборудование на карте (ключи — как в demo.seed) SPOTS = {"lathe": (14, 20), "mill": (30, 20), "comp": (36, 40), "press": (62, 22), "fan": (84, 18), "boiler": (16, 76), "pump1": (40, 72), "pump2": (52, 82), "trans": (71, 76), # краны — на отдельной карте кранов (в демо она включена и использует тот же план) "crane": (70, 30), "crane2": (55, 14), "crane3": (22, 35)} CRANES = ("crane", "crane2", "crane3") # за каким отделом закреплено оборудование (ключ отдела — как в seed) EQ_DEPS = {"lathe": "mech", "mill": "mech", "press": "mech", "fan": "mech", "comp": "mech", "pump1": "mech", "pump2": "mech", "boiler": "mech", "trans": "elec", "crane": "crane", "crane2": "crane", "crane3": "crane"} ICONS = {"lathe": "lathe", "mill": "lathe", "comp": "compressor", "press": "press", "fan": "fan", "crane": "crane", "boiler": "boiler", "pump1": "pump", "pump2": "pump", "trans": "transformer", "crane2": "crane", "crane3": "crane"} def plan_png(w: int = 1200, h: int = 750) -> bytes: """Схема цехов без подписей (подписи — у иконок и зон складов). Чистый PNG без внешних библиотек.""" px = [[(246, 247, 249)] * w for _ in range(h)] def rect(x0, y0, x1, y1, col): for y in range(max(0, y0), min(h, y1)): row = px[y] for x in range(max(0, x0), min(w, x1)): row[x] = col rect(10, 10, w - 10, h - 10, (51, 65, 85)) rect(18, 18, w - 18, h - 18, (246, 247, 249)) for x, y, rw, rh, col in ROOMS: X0, Y0, X1, Y1 = x * w // 100, y * h // 100, (x + rw) * w // 100, (y + rh) * h // 100 rect(X0, Y0, X1, Y1, (100, 116, 139)) rect(X0 + 6, Y0 + 6, X1 - 6, Y1 - 6, col) rect((X0 + X1) // 2 - 40, Y1 - 6, (X0 + X1) // 2 + 40, Y1, col) # проём двери for x in range(30, w - 30, 60): # разметка проезда rect(x, h * 52 // 100, x + 30, h * 52 // 100 + 6, (148, 163, 184)) raw = bytearray() for row in px: raw.append(0) for c in row: raw += bytes(c) def chunk(t, d): return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xFFFFFFFF) return (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(bytes(raw), 6)) + chunk(b"IEND", b"")) def seed(db: Session, *, users: dict, eq: dict, pos: dict, sch_store, pw: str, now: datetime, rnd: random.Random) -> dict: today = now.date() # --- администратор склада, навыки и телефоны --- p_store = Position(name="Кладовщик", kind="technician", schedule_id=sch_store.id) db.add(p_store) db.flush() users["lebedev"] = User(username="lebedev", full_name="Лебедев Павел", password_hash=pw, is_storekeeper=True, position_id=p_store.id, phone="+7 900 555-01-07", skills="Учёт ТМЦ, выдача инструмента, работа с поставщиками") db.add(users["lebedev"]) skills = {"ivanov": ("+7 900 555-01-01", "Слесарные работы, сварка (НАКС), гидравлика, центровка валов"), "kozlov": ("+7 900 555-01-02", "Слесарные работы, станки с ЧПУ, пневматика"), "petrov": ("+7 900 555-01-03", "Электромонтаж до и выше 1000 В, релейная защита, ПЛК Siemens"), "sidorov": ("+7 900 555-01-04", "Насосы и компрессоры, вибродиагностика, такелаж"), "morozova": ("+7 900 555-01-05", "Планирование ТО, анализ отказов, промышленная безопасность"), "volkov": ("+7 900 555-01-06", "Регламенты ТО, учёт наработки, закупки запчастей"), "orlov": ("+7 900 555-01-08", "Крановое электрооборудование, приборы безопасности кранов, допуск Ростехнадзора")} for u, (ph, sk) in skills.items(): users[u].phone, users[u].skills = ph, sk db.flush() # --- отделы --- from .models import Department chief = Department(name="Служба главного инженера", head_user_id=users["morozova"].id, notes="Планирование и контроль ТО") db.add(chief) db.flush() deps = {"mech": Department(name="Механическая служба", parent_id=chief.id, head_user_id=users["volkov"].id), "elec": Department(name="Электрослужба", parent_id=chief.id, head_user_id=users["petrov"].id), "store": Department(name="Склад инструмента", parent_id=chief.id, head_user_id=users["lebedev"].id), "crane": Department(name="Крановое хозяйство", parent_id=chief.id, head_user_id=users["orlov"].id, notes="Краны и грузоподъёмные механизмы; станки этому отделу не показываются")} db.add_all(deps.values()) db.flush() for u, d in (("morozova", chief), ("kuznetsova", chief), ("volkov", deps["mech"]), ("ivanov", deps["mech"]), ("kozlov", deps["mech"]), ("sidorov", deps["mech"]), ("petrov", deps["elec"]), ("lebedev", deps["store"]), ("orlov", deps["crane"])): users[u].department_id = d.id db.flush() # --- иконки и карта завода --- for k, e in eq.items(): e.icon = "builtin:" + ICONS.get(k, "gear") if k in SPOTS: e.map_x, e.map_y = SPOTS[k] e.is_crane = k in CRANES if k in EQ_DEPS: e.department_id = deps[EQ_DEPS[k]].id ss.set_(db, "map_image", media.save(db, "map", plan_png(), ".png")) ss.set_(db, "crane_map", "1") # --- склады и инструмент --- wh = {"tool": Warehouse(name="Инструментальная кладовая", zone={"x": 82.5, "y": 56, "w": 14, "h": 19}, notes="Ручной и электроинструмент, измерительный"), "cons": Warehouse(name="Склад расходников", zone={"x": 82.5, "y": 76, "w": 14, "h": 19}, notes="Смазки, СИЗ, крепёж"), "shop": Warehouse(name="Кладовая цеха №2", zone={"x": 88, "y": 6, "w": 8, "h": 9}, notes="Оперативный запас у участка")} db.add_all(wh.values()) db.flush() catalog = [ # (ключ, название, категория, ед., инв. №, количество, склад) ("drill", "Дрель аккумуляторная Makita DDF485", "Электроинструмент", "шт", "ИН-001", 3, "tool"), ("grinder", "УШМ Bosch GWS 9-125", "Электроинструмент", "шт", "ИН-002", 2, "tool"), ("mm", "Мультиметр Fluke 117", "Измерительный", "шт", "ИН-010", 3, "tool"), ("clamp", "Токоизмерительные клещи UNI-T UT203", "Измерительный", "шт", "ИН-011", 1, "tool"), ("vibro", "Виброметр Vibrotest 60", "Измерительный", "шт", "ИН-012", 1, "tool"), ("keys", "Набор ключей комбинированных 8–32", "Ручной инструмент", "компл", "", 6, "tool"), ("torque", "Ключ динамометрический 40–200 Нм", "Ручной инструмент", "шт", "ИН-020", 2, "tool"), ("puller", "Съёмник подшипников 3-захватный", "Ручной инструмент", "шт", "ИН-021", 1, "tool"), ("gloves", "Перчатки х/б с ПВХ", "СИЗ", "пар", "", 200, "cons"), ("glasses", "Очки защитные", "СИЗ", "шт", "", 25, "cons"), ("litol", "Смазка Литол-24, 800 г", "Смазки", "шт", "", 30, "cons"), ("oil", "Масло компрессорное КС-19, 20 л", "Смазки", "кан", "", 6, "cons"), ("bolts", "Болт М12×40, оцинк.", "Крепёж", "шт", "", 400, "cons"), ("tape", "Изолента ПВХ 19 мм", "Расходники", "шт", "", 40, "shop"), ] items = {} t0 = now - timedelta(days=40) for k, name, cat, unit, inv, qty, w in catalog: it = InventoryItem(name=name, category=cat, unit=unit, inventory_no=inv, created_at=t0) db.add(it) db.flush() items[k] = it db.add(Stock(item_id=it.id, warehouse_id=wh[w].id, qty=qty, since=t0)) db.add(InventoryMove(item_id=it.id, qty=qty, kind="receive", to_warehouse_id=wh[w].id, by_user_id=users["lebedev"].id, at=t0, note="Начальные остатки")) db.flush() def move(k, qty, *, src_wh=None, src_user=None, dst_wh=None, dst_user=None, days_ago=0, note=""): it = items[k] at = now - timedelta(days=days_ago, hours=rnd.randint(1, 6)) for w, u, sign in ((src_wh, src_user, -1), (dst_wh, dst_user, 1)): if w is None and u is None: continue st = db.query(Stock).filter(Stock.item_id == it.id, Stock.warehouse_id == (wh[w].id if w else None), Stock.user_id == (users[u].id if u else None)).first() if not st: st = Stock(item_id=it.id, warehouse_id=wh[w].id if w else None, user_id=users[u].id if u else None, qty=0) db.add(st) st.qty += sign * qty st.since = at kind = "writeoff" if dst_wh is None and dst_user is None else "issue" if src_wh and dst_user else "return" if src_user and dst_wh else "transfer" db.add(InventoryMove(item_id=it.id, qty=qty, kind=kind, from_warehouse_id=wh[src_wh].id if src_wh else None, from_user_id=users[src_user].id if src_user else None, to_warehouse_id=wh[dst_wh].id if dst_wh else None, to_user_id=users[dst_user].id if dst_user else None, by_user_id=users["lebedev"].id, at=at, note=note)) db.flush() move("drill", 1, src_wh="tool", dst_user="ivanov", days_ago=30) move("keys", 1, src_wh="tool", dst_user="ivanov", days_ago=30) move("keys", 1, src_wh="tool", dst_user="kozlov", days_ago=29) move("torque", 1, src_wh="tool", dst_user="kozlov", days_ago=12, note="На время ТО пресса") move("mm", 1, src_wh="tool", dst_user="petrov", days_ago=35) move("clamp", 1, src_wh="tool", dst_user="petrov", days_ago=35) move("vibro", 1, src_wh="tool", dst_user="sidorov", days_ago=20) move("gloves", 10, src_wh="cons", dst_user="sidorov", days_ago=7) move("glasses", 1, src_wh="cons", dst_user="ivanov", days_ago=7) move("grinder", 1, src_wh="tool", dst_user="kozlov", days_ago=5) move("grinder", 1, src_user="kozlov", dst_wh="tool", days_ago=3, note="Возврат после ремонта рамы") move("litol", 6, src_wh="cons", dst_wh="shop", days_ago=10, note="Пополнение запаса у участка") move("bolts", 25, src_wh="cons", days_ago=8, note="Израсходовано при ремонте кран-балки") # --- заявки на инструмент --- reqs = [("ivanov", "Съёмник подшипников гидравлический 10 т", "Механический съёмник не справляется с подшипниками насосов Н-1/Н-2", "https://www.vseinstrumenti.ru/", 1, "new", "", None, 1), ("sidorov", "Тепловизор UNI-T UTi260B", "Контроль нагрева подшипников и контактов без остановки оборудования", "https://www.chipdip.ru/", 1, "approved", "Согласовано, ждём счёт", "volkov", 4), ("petrov", "Набор отвёрток диэлектрических 1000 В", "Старый набор изношен, нужен для работ в щитах", "", 2, "ordered", "Заказано у поставщика, срок 5 дней", "lebedev", 6), ("kozlov", "Перчатки антивибрационные", "Работа с УШМ и перфоратором", "", 4, "done", "Выданы со склада", "lebedev", 12), ("ivanov", "Шуруповёрт с ударным режимом", "Уже есть аккумуляторная дрель — по заявке отказ", "", 1, "rejected", "Есть свободная дрель Makita в инструментальной", "lebedev", 15)] for who, what, why, link, qty, st, ans, by, ago in reqs: t = now - timedelta(days=ago, hours=2) db.add(ToolRequest(author_id=users[who].id, what=what, why=why, link=link, qty=qty, status=st, answer=ans, handled_by_id=users[by].id if by else None, created_at=t, updated_at=t + timedelta(hours=3))) # --- инциденты --- def units_of(k): return db.query(ServiceUnit).filter(ServiceUnit.equipment_id == eq[k].id).order_by(ServiceUnit.id).all() inc_open = Incident(equipment_id=eq["pump2"].id, author_id=users["sidorov"].id, occurred_at=now - timedelta(hours=3), created_at=now - timedelta(hours=3), title="Течь по торцевому уплотнению", description="При обходе обнаружена течь воды по валу, около 1 л/ч. Насос в работе, нагрузку снизили.") for su in units_of("pump2")[:1]: inc_open.units.append(IncidentUnit(unit_id=su.id, unit_name=su.name, replaced=False)) inc_open2 = Incident(equipment_id=eq["crane"].id, author_id=users["orlov"].id, occurred_at=now - timedelta(days=1, hours=2), created_at=now - timedelta(days=1, hours=2), title="Не срабатывает концевой выключатель подъёма", description="Крюк доходит до упора. Кран выведен из работы до ремонта, вывешен плакат.") for su in units_of("crane")[:1]: inc_open2.units.append(IncidentUnit(unit_id=su.id, unit_name=su.name, replaced=False)) comp_units = units_of("comp") inc_closed = Incident(equipment_id=eq["comp"].id, author_id=users["sidorov"].id, occurred_at=now - timedelta(days=9), created_at=now - timedelta(days=9), title="Аварийная остановка по температуре масла", description="Компрессор остановился защитой, t масла 105 °C. Засорён масляный фильтр.", replaced=True, replacement_note="Масляный фильтр и масло заменены", status="closed", closed_at=now - timedelta(days=9) + timedelta(hours=5), closed_by_id=users["volkov"].id, close_note="Запущен, температура в норме") db.add_all([inc_open, inc_open2, inc_closed]) # сообщения о неисправности: замечено, но оборудование работает for k, who, hours_ago, title, desc, closed in ( ("lathe", "kozlov", 5, "Посторонний шум в шпиндельной бабке", "На оборотах выше 1000 слышен гул, станок работает. Нужна диагностика подшипников.", None), ("fan", "ivanov", 26, "Повышенная вибрация корпуса", "Вибрация заметна рукой, крепёж затянут. Работает, наблюдаем до планового ТО.", None), ("crane2", "orlov", 30, "Скрип колёс тележки при движении", "Скрип на правой стороне моста, кран в работе. Проверить смазку и реборды.", None), ("press", "volkov", 24 * 6, "Подтекает фитинг гидролинии", "Капельная течь на фитинге высокого давления.", "Фитинг подтянут, течи нет")): if k not in eq: continue at = now - timedelta(hours=hours_ago) db.add(Incident(kind="fault", equipment_id=eq[k].id, author_id=users[who].id, occurred_at=at, created_at=at, title=title, description=desc, **({"status": "closed", "closed_at": at + timedelta(hours=20), "closed_by_id": users["volkov"].id, "close_note": closed} if closed else {}))) db.flush() if comp_units: su = comp_units[0] inc_closed.units.append(IncidentUnit(unit_id=su.id, unit_name=su.name, replaced=True)) at = inc_closed.occurred_at + timedelta(hours=4) if at > (su.last_service_at or at): service.reset_unit(db, su, at=at, hours=max(su.last_service_hours, eq["comp"].meter_hours - 300), kind="incident", user_id=users["sidorov"].id, note=f"Замена при отказе №{inc_closed.id}: {inc_closed.title}") db.add(Incident(equipment_id=eq["boiler"].id, author_id=users["ivanov"].id, occurred_at=now - timedelta(days=16), created_at=now - timedelta(days=16), title="Срыв пламени горелки", description="Повторный розжиг успешен, причина — загрязнение фотодатчика.", status="closed", closed_at=now - timedelta(days=16, hours=-2), closed_by_id=users["morozova"].id, close_note="Датчик очищен")) # --- суточные задания --- def task(day_ago, title, who, by, status, *, k=None, unit_idx=None, desc="", flow=None): day = today - timedelta(days=day_ago) created = datetime.combine(day, time(8, 10)) + timedelta(minutes=rnd.randint(0, 40)) su = None if k and unit_idx is not None: us = units_of(k) su = us[unit_idx] if len(us) > unit_idx else None t = DailyTask(title=title, description=desc, day=day, assignee_id=users[who].id, created_by_id=users[by].id, equipment_id=eq[k].id if k else None, unit_id=su.id if su else None, status="assigned", created_at=created, updated_at=created) db.add(t) db.flush() db.add(TaskEvent(task_id=t.id, user_id=users[by].id, at=created, from_status="", to_status="assigned")) at, cur = created, "assigned" for st, comment in (flow or []): at = at + timedelta(minutes=rnd.randint(25, 140)) if at > now: at = now - timedelta(minutes=5) db.add(TaskEvent(task_id=t.id, user_id=users[who].id, at=at, from_status=cur, to_status=st, comment=comment)) cur = st t.status, t.updated_at = cur, at t.finished_at = at if cur in ("done", "cancelled") else None return t # сегодня task(0, "Проверить натяжение ремней вентилятора", "kozlov", "volkov", "in_progress", k="fan", flow=[("in_progress", "")]) task(0, "Подтянуть сальник насоса Н-1", "sidorov", "volkov", "done", k="pump1", unit_idx=1, flow=[("in_progress", ""), ("done", "Сальник подтянут, течи нет")]) task(0, "Замер сопротивления изоляции двигателя насоса Н-2", "petrov", "morozova", "assigned", k="pump2") task(0, "Ремонт концевика кран-балки", "orlov", "morozova", "paused", k="crane", desc="После ремонта — проверка на холостом ходу", flow=[("in_progress", ""), ("paused", "Ждём концевой выключатель со склада, заявка у кладовщика")]) task(0, "Уборка инструментальной кладовой", "lebedev", "volkov", "assigned") task(0, "Замер вибрации насоса Н-2", "sidorov", "volkov", "assigned", k="pump2", unit_idx=0, desc="После устранения течи: вибрация подшипников, результат — в комментарии") task(0, "Осмотр подшипников дымососа", "ivanov", "ivanov", "assigned", k="boiler", unit_idx=1, desc="Поставил себе по итогам обхода") # прошлые дни — для отчёта «Выполнение работ» plan = [("Смазать направляющие станка", "kozlov", "lathe"), ("Очистить фильтр компрессора", "sidorov", "comp"), ("Проверить заземление ТП", "petrov", "trans"), ("Подтянуть крепёж пресса", "ivanov", "press"), ("Осмотр каната кран-балки", "orlov", "crane"), ("Продувка теплообменника", "sidorov", "boiler")] for d in range(1, 15): for title, who, k in rnd.sample(plan, rnd.randint(1, 3)): r = rnd.random() if r < 0.72: flow = [("in_progress", ""), ("done", rnd.choice(["Выполнено", "Сделано, замечаний нет", "Выполнено, отклонений нет"]))] elif r < 0.86: flow = [("in_progress", ""), ("paused", rnd.choice(["Нет запчасти, жду склад", "Отвлекли на аварию"])), ("in_progress", ""), ("done", "Завершено на следующей смене")] elif r < 0.94: flow = [("cancelled", rnd.choice(["Оборудование в ремонте у подрядчика", "Перенесено на плановый останов"]))] else: flow = [("in_progress", ""), ("paused", "Требуется допуск, ждём наряд-допуск")] task(d, title, who, rnd.choice(["volkov", "morozova"]), flow[-1][0], k=k, flow=flow) db.flush() _reliability(db, users=users, eq=eq, now=now) _link_and_regular(db, users=users, eq=eq, now=now, rnd=rnd) return {"warehouses": len(wh), "items": len(items)} def _reliability(db: Session, *, users: dict, eq: dict, now: datetime): """История для «Анализа надёжности»: показания счётчиков за год и закрытые отказы с полной записью.""" from .models import MeterReading rates = {"comp": 4.2, "pump1": 12.6, "pump2": 12.4, "boiler": 8.9, "lathe": 2.3, "mill": 6.5, "press": 7.0, "fan": 18.0, "trans": 24.0, "crane": 0.8, "crane2": 3.5, "crane3": 2.5} cur = {} for k, rate in rates.items(): if k not in eq: continue e = eq[k] if not e.meter_hours: # у этих машин счётчик заводим с нуля: «текущая» наработка — год работы e.meter_hours, e.meter_updated_at = round(rate * 420, 1), now db.add(MeterReading(equipment_id=e.id, hours=e.meter_hours, at=now, source="manual")) cur[k] = e.meter_hours for d in (365, 300, 240, 180, 120, 75, 45): db.add(MeterReading(equipment_id=e.id, hours=max(0.0, round(cur[k] - rate * d, 1)), at=now - timedelta(days=d), source="manual")) db.flush() units = {k: db.query(ServiceUnit).filter(ServiceUnit.equipment_id == eq[k].id).order_by(ServiceUnit.id).all() for k in rates if k in eq} # (машина, дней назад, простой ч, ожидание запчастей ч, причина, характер, что сделано, признаки, № узла-причины, что случилось, как устранено) history = [ ("pump1", 320, 6.0, 0, "wear", "gradual", "replace", "inspection", 0, "Течь по сальнику, остановлен", "Заменена сальниковая набивка"), ("pump1", 190, 3.5, 0, "operation", "sudden", "adjust", "none", None, "Сухой ход, сработала защита", "Восстановлен подпор, обучение смены"), ("pump1", 70, 26.0, 18.0, "wear", "gradual", "replace", "fault_report", 1, "Разрушение подшипника", "Подшипник заменён (ждали со склада)"), ("pump2", 250, 4.0, 0, "power", "sudden", "restart", "none", None, "Отключение по перегрузке", "Перезапуск, напряжение в норме"), ("pump2", 95, 12.0, 6.0, "wear", "gradual", "replace", "inspection", 0, "Повышенная вибрация, остановлен", "Заменена муфта"), ("comp", 280, 8.0, 0, "external", "gradual", "repair", "inspection", 0, "Перегрев: забит радиатор", "Радиатор промыт"), ("boiler", 200, 30.0, 20.0, "defect", "sudden", "replace", "none", 1, "Течь трубы экрана", "Вварена вставка, опрессовка"), ("fan", 150, 2.0, 0, "secondary", "sudden", "repair", "none", None, "Остановка при отказе пускателя", "Заменён пускатель"), ("fan", 40, 5.0, 0, "wear", "gradual", "replace", "fault_report", 0, "Обрыв ремня", "Комплект ремней заменён"), ("press", 110, 9.0, 4.0, "defect", "sudden", "repair", "none", None, "Не держит давление", "Перебран клапан после прошлого ремонта"), ("crane2", 160, 7.0, 0, "external", "sudden", "repair", "none", None, "Отказ концевика (влага)", "Концевик осушен, установлен кожух"), ("trans", 230, 1.5, 0, "power", "sudden", "restart", "none", None, "Срабатывание защиты по сети", "Включён после проверки"), ] who = {"pump1": "sidorov", "pump2": "sidorov", "comp": "sidorov", "boiler": "ivanov", "fan": "ivanov", "press": "kozlov", "crane2": "orlov", "trans": "petrov"} for k, d, down, wait, cause, nature, action, prec, ui, title, note in history: if k not in eq: continue at = now - timedelta(days=d, hours=3) hrs = max(0.0, round(cur[k] - rates[k] * d, 1)) su = units.get(k, [])[ui] if ui is not None and ui < len(units.get(k, [])) else None i = Incident(kind="incident", equipment_id=eq[k].id, author_id=users[who[k]].id, occurred_at=at, created_at=at, title=title, hours_at_open=hrs, hours_at_failure=hrs, repair_started_at=at + timedelta(minutes=40), restored_at=at + timedelta(hours=down), parts_wait_hours=wait or None, cause=cause, nature=nature, action=action, precursor=prec, cause_unit_id=su.id if su else None, status="closed", closed_at=at + timedelta(hours=down, minutes=20), closed_by_id=users["volkov"].id, close_note=note, replaced=action == "replace") if su and action == "replace": i.units.append(IncidentUnit(unit_id=su.id, unit_name=su.name, replaced=True)) db.add(i) db.flush() def _link_and_regular(db: Session, *, users: dict, eq: dict, now: datetime, rnd: random.Random): """Для отчёта «Выполнение работ»: наряды из ежедневного расписания — суточные осмотры; плюс история нарядов ТО.""" from . import logic from .models import Checklist, Order, OrderTemplate daily = {(t.equipment_id, t.checklist_id): t.id for t in db.query(OrderTemplate).filter(OrderTemplate.recurrence == "daily")} for o in db.query(Order).filter(Order.template_id.is_(None)): tid = daily.get((o.equipment_id, o.checklist_id)) if tid: o.template_id = tid reg = db.query(Checklist).filter(Checklist.kind == "regular").first() if not reg: return duty = users["sidorov"] for k, days, status in (("comp", 13, "completed"), ("pump1", 11, "completed"), ("boiler", 9, "completed"), ("comp", 6, "completed"), ("pump2", 4, "completed"), ("crane", 3, "cancelled"), ("lathe", 2, "completed"), ("boiler", 1, "in_progress")): created = now - timedelta(days=days, hours=rnd.randint(1, 4)) o = logic.create_order(db, title=f"Регламентное ТО: {eq[k].name}", checklist_id=reg.id, equipment_id=eq[k].id, location_id=eq[k].location_id, assignee_user_id=duty.id, due_at=created + timedelta(hours=48)) db.flush() o.created_at, o.status = created, status if status in ("completed", "in_progress"): o.executor_id, o.started_at = duty.id, created + timedelta(minutes=40) for n, it in enumerate(o.items): if status == "in_progress" and n > 1: break it.value = str(round(eq[k].meter_hours - days * 8, 1)) if it.is_meter else "done" it.answered_at = created + timedelta(minutes=50 + n * 5) if status == "completed": o.submitted_at = o.completed_at = created + timedelta(hours=2) db.flush()