Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android

This commit is contained in:
darkorion
2026-10-13 09:09:53 +03:00
commit 67e411eea8
246 changed files with 23152 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
# API для клиентов (Android)
Базовый адрес: `https://<домен>/api/v1`. Интерактивная схема: `/docs`, машинная: `/openapi.json`.
Старый путь `/api/...` (без версии) работает как алиас, но в новых клиентах используйте `/api/v1`.
## Общие правила
- Формат — JSON, UTF-8. Ошибки: HTTP-код + `{"detail": "текст"}` (для 422 валидации `detail` — список `{loc, msg}`).
- **Время** — ISO 8601 со смещением: `2026-10-07T11:25:00+03:00`. На вход принимается любое смещение или `Z`;
без смещения время считается серверным локальным.
- Коды: 401 — нет/просрочен токен, 403 — нет прав, 404 — не найдено *или не видно вам*, 409 — конфликт данных,
422 — неверные данные, 429 — слишком много попыток входа (см. `Retry-After`).
## Авторизация
| Запрос | Назначение |
|---|---|
| `POST /auth/login` `{username, password, device?}` | → `{access_token, refresh_token, expires_in}` |
| `POST /auth/refresh` `{refresh_token}` | новая пара токенов; **старый refresh становится недействительным** |
| `POST /auth/logout` `{refresh_token}` | завершить сессию |
| `POST /auth/change-password` `{old_password, new_password}` | смена пароля, остальные сессии завершаются, возвращает новую пару |
| `GET /me` | профиль, должность (`position.kind`: technician/engineer), `on_shift`, `is_admin`, `is_moderator`, `demo_mode` (сессия в демо), `crane_map` (краны — отдельная служба) |
Заголовок: `Authorization: Bearer <access_token>`. Access живёт 60 мин (`ACCESS_TTL_MIN`), refresh — 90 дней.
Рекомендуемая схема клиента: при 401 один раз вызвать `/auth/refresh` (сериализуя параллельные вызовы!) и повторить запрос;
если и refresh вернул 401 — показать экран входа. refresh хранить в `EncryptedSharedPreferences`/Keystore.
Повторное предъявление уже использованного refresh трактуется как кража и отзывает **все** сессии пользователя.
После 5 неудачных входов с одного IP или на один логин вход блокируется на 5 минут.
## Наряды
| Запрос | Назначение |
|---|---|
| `GET /orders` | список видимых нарядов. Параметры: `status=new,in_progress`, `mine`, `overdue`, `to_review`, `equipment_id`, `q` (поиск по названию наряда, оборудованию или номеру; без учёта регистра), `updated_since`, `include_items`, `limit` (≤1000), `offset` |
| `GET /orders/{id}` | наряд с пунктами (`items[]`: тип ответа, варианты, границы, `photo`, `photos[]`, `value`) |
| `POST /orders/{id}/start` | взять в работу |
| `PUT /orders/{id}/items/{item_id}` `{value}` | ответ на пункт. status: `in_progress`/`done`; choice — значение из `options`; number — число (запятая допустима); пустое значение стирает ответ |
| `POST /orders/{id}/items/{item_id}/photos` | тело — файл, `Content-Type: image/jpeg\|png\|webp`; опционально `Idempotency-Key` |
| `GET /photos/{id}` | файл (нужен Bearer) |
| `POST /orders/{id}/submit` | сдать наряд |
| `POST /orders/{id}/sync` | пакетная отправка офлайн-данных (ниже) |
| `GET/POST /orders/{id}/comments`, `DELETE /comments/{id}` | комментарии |
| `POST /orders/{id}/review` `{approve, comment}` | проверка (флаг `can_review` в наряде) |
Права вычислять не нужно: в наряде есть `can_execute` и `can_review`.
Статусы: `new → in_progress → review → completed`, `cancelled`; `is_overdue` вычисляется сервером.
## Офлайн-режим
Типичный обход идёт там, где нет связи. Схема клиента:
1. **Загрузка.** При наличии сети: `GET /orders?include_items=true&status=new,in_progress` — наряды с чек-листами
в локальную БД. Справочники (`/equipment`, `/locations`) — по необходимости. Запомнить курсор
`max(updated_at)` по всем полученным нарядам.
2. **Инкрементально.** `GET /orders?include_items=true&updated_since=<курсор>` — вернёт только изменённое
(ответы, статусы, комментарии, фото двигают `updated_at`). Курсор — значение `updated_at` из ответа сервера,
а не часы телефона.
3. **Работа без сети.** Ответы копятся локально вместе с моментом ввода (`answered_at`), фото — в очереди с
сгенерированным UUID.
4. **Отправка.** `POST /orders/{id}/sync`:
```json
{"items": [{"item_id": 12, "value": "done", "answered_at": "2026-10-07T12:00:00+03:00"},
{"item_id": 13, "value": "4,5", "answered_at": "2026-10-07T12:01:10+03:00"}],
"submit": true}
```
Ответ: `results[]` по каждому пункту (`ok` / `stale` / `error` + `detail`), `submitted`, `submit_error`,
актуальный `order`. Ответы применяются независимо: ошибка одного не блокирует остальные.
`stale` — на сервере уже есть **более свежий** ответ (другой ввод позже по времени), значение не изменено.
Пакет можно безопасно отправить повторно. Фото: `POST .../photos` с заголовком `Idempotency-Key: <uuid>` —
повтор после обрыва связи не создаёт дубликат. Рекомендуется сначала отправить фото, затем `sync` с `submit`
(для обязательных фото иначе наряд не сдастся).
## Push (Android, FCM)
1. Админ кладёт JSON-ключ сервисного аккаунта Firebase на сервер и указывает путь в «Настройки сервера».
2. Приложение при входе и в `onNewToken` вызывает `POST /push/device` `{token, platform:"android"}`;
при выходе — `DELETE /push/device` с тем же телом.
3. Сообщения содержат `notification{title, body}` и `data{url}` (`/#/order/<id>`). Парсите id из `url`
либо открывайте `GET /orders?updated_since=...`.
Уведомления: новый наряд, наряд на проверку, возврат на доработку, комментарий.
## Графики работы (смены)
`GET/POST /schedules`, `PATCH/DELETE /schedules/{id}`. Три вида (`kind`): `weekly` (дни недели), `cycle` (N через M),
`pattern` — конструктор: `pattern` = список шагов `{type: "work"|"off", days, start_time, end_time, label}`, повторяется
по кругу от `cycle_start`. Конец смены раньше начала — ночная смена, равные время — сутки (08:00–08:00).
Пример «3 дня, отсыпной, 3 ночи, отсыпной, 3 выходных» — 5 шагов, цикл 11 дней.
`POST /schedules/preview` `{schedule, from_date, days}` — календарь без сохранения (для конструктора).
## Отчёты (инженер/модератор/админ)
`GET /reports` — список отчётов. `GET /reports/completed-orders?from=…&to=…` — **период обязателен** (дата и время).
Фильтры: `equipment_id`, `position_id` (должность исполнителя **на момент выполнения**), `user_id`, `include_review`.
`format=json|pdf|csv` (pdf — файл; csv — UTF-8 с BOM, разделитель `;`, открывается в Excel).
`GET /reports/execution?from=…&to=…` — «Выполнение работ» по машинам. `GET /reports/people?from=…&to=…` — «По сотрудникам»:
фильтры `department_id` (с вложенными), `position_id`, `user_id`; `format=json|pdf|csv|csv_calendar`. В JSON: `days`, `totals`,
`rows: [{user_id, name, position, department, done, orders_done, tasks_done, tasks_total, tasks_open, on_time_pct, overdue, returns,
deviations, tasks_cancelled, tasks_paused, hours, calendar: {"2026-10-12": {n, h}}}]`. Трудоёмкость задачи — `labor_hours` (норма),
если задана, иначе фактическое время «в работе»; наряда — от начала до сдачи.
## Регламентное обслуживание (узлы машин, наработка)
Чтение — любому авторизованному; изменение — инженер, модератор, администратор.
| Метод | Путь | Описание |
|---|---|---|
| GET | `/equipment/{id}/service-units` | Узлы машины с расчётом: `state` (`ok`/`soon`/`due`/`nodata`), `percent`, `remaining_hours`, `remaining_days`, `due_date`, `forecast_date`, `rule` |
| POST | `/equipment/{id}/service-units` | Создать узел: `name`, `mode` (`hours`/`calendar`/`both`), `interval_hours`, `interval_value` + `interval_unit` (`day`/`week`/`month`), `warn_percent`, начальное состояние `last_service_at`, `last_service_hours` |
| PATCH | `/service-units/{id}` | Изменить правило, название, `is_active` |
| DELETE | `/service-units/{id}` | Удалить узел (пункты старых нарядов остаются) |
| POST | `/service-units/{id}/reset` | Сброс: `{at?, hours?, note}` — обслужено отдельно от наряда / при внештатной ситуации |
| GET | `/service-units/{id}/events` | История обслуживания: `initial` / `order` / `reset` |
| POST | `/equipment/{id}/meter` | Показание наработки `{hours, at?, correction?, note}`; меньше текущего — только с `correction: true` |
| GET | `/equipment/{id}/meter` | История показаний |
| GET | `/reports/equipment-state` | Отчёт «Текущее состояние»: `equipment_id`, `location_id`, `state` (`due`/`soon`/`attention`/`ok`), `include_inactive`, `format` (`json`/`pdf`/`csv`) |
Чек-лист: поля `kind` (`inspection` | `regular`) и `only_due`. Для `regular` наряд требует `equipment_id`; в наряде появляются пункты с
`is_meter: true` (число, м/ч) и `service_unit_id` (статус `done` = узел обслужен). При завершении наряда сервер обновляет наработку и сбрасывает узлы.
## Инциденты и сообщения о неисправности
Одна сущность с полем `kind`: `incident` — авария, оборудование остановлено (машина в состоянии `alarm`); `fault` — сообщение о
неисправности: замечена проблема, но оборудование работает (состояние `fault`). Замена узлов (`replaced`) у `fault` не применяется.
- `POST /api/v1/incidents` `{kind, equipment_id, title, description?, occurred_at?, units: [{unit_id, replaced}], replaced?, replacement_note?}`.
- `GET /api/v1/incidents?kind=incident|fault&status=open|closed&mine&source&cranes&q&equipment_id`.
- `POST /api/v1/incidents/{id}/close` — закрыть (автор или инженер). Тело (всё, кроме `note`, необязательно; для отказа незаполненное
подставляется: начало работ — момент отказа, ввод — сейчас, наработка — снимок при регистрации, причина — `unknown`):
`{note, repair_started_at, restored_at, parts_wait_hours, hours_at_failure, meter_at_restore, cause_unit_id | cause_unit_other,
action: repair|replace|adjust|restart, nature: sudden|gradual, cause: wear|operation|external|power|defect|secondary|unknown,
precursor: inspection|fault_report|none, replaced_units: [unit_id]}`. Проверка хронологии: отказ ≤ начало работ ≤ ввод ≤ сейчас.
`meter_at_restore` записывается показанием счётчика; `replaced_units` сбрасывают ресурс узлов.
- `PATCH /api/v1/incidents/{id}/failure` — те же поля: инженер или руководитель уточняет данные отказа (и у закрытой записи).
- При регистрации сохраняется `hours_at_open` (наработка машины); при переводе неисправности в отказ — `escalated_at`, `hours_at_failure`
и `precursor: fault_report`. В интерфейсе сайта `kind=incident` называется «Отказ».
- `POST /api/v1/incidents/{id}/escalate` `{note}` — перевести сообщение о неисправности в инцидент (оборудование остановилось).
- `GET /api/v1/equipment-health` → `{id: {state: alarm|fault|due|soon|ok, open_incidents, open_faults, due, soon}}`;
приоритет состояния: авария > неисправность > требуется ТО > скоро ТО. То же состояние — в `GET /map`.
## Анализ надёжности (инженер, руководитель; настройка `reliability`)
- `GET /api/v1/reliability?from&to&department_id&cranes&equipment_id&confidence=0.9&horizon_days=30` — показатели за период:
`fleet` (по парку) и `rows` по машинам: `hours` (наработка; `basis: meter|calendar`), `failures`, `faults`, `escalated`, `lambda`,
`lambda_upper`, `theta`, `theta_lower`, `theta_median`, `mttr`, `repair_mean`, `wait_mean`, `downtime`, `availability`,
`forecast_mu`, `forecast_p`; разбивки `causes`, `natures`, `actions`, `precursors`, `cause_units`, `months`.
Оценки по малоотказному опыту: λ_верх = χ²(Pα; 2n+2) / 2T (при n = 0 — −ln(1−Pα)/T).
- `GET /api/v1/reliability/drift?days=180&horizon=90` — числовые пункты чек-листов с нормой: тренд, `days_to_bound`, `out_of_range`, `nu_per_30d`.
- `GET /api/v1/reliability/export?kind=failures|service&from&to` — CSV (UTF-8 с BOM, `;`): отказы и неисправности / история обслуживания узлов.
- `GET /api/v1/reliability/dictionaries` — справочники причин, характера, действий, признаков. В `GET /me` — флаг `reliability`.
## Инциденты извне (внешние системы и публичная форма)
Внешняя система (диспетчерская, SCADA, сервис заявок) заводит инцидент по **ключу доступа**. Ключ выпускает администратор:
«Администрирование → Настройки сервера → Ключи доступа к API» (ключ показывается один раз; в БД хранится только хэш; отзыв — там же).
- `GET /api/v1/external/equipment` — справочник машин: `[{id, name, inventory_no, location}]`.
- `POST /api/v1/external/incidents` — завести инцидент. Заголовок `X-API-Key: cl_…`. Тело:
```json
{"inventory_no": "НС-402", "title": "Перегрев двигателя", "description": "t обмотки 135 °C",
"occurred_at": "2026-10-09T11:20:00+03:00", "reporter_name": "SCADA цех 1", "reporter_contact": "+7 …",
"external_id": "ALARM-1234", "kind": "incident"}
```
`kind`: `incident` (по умолчанию) или `fault` — сообщение о неисправности. Машина — `equipment_id` (id портала) или `inventory_no`. Повторный запрос с тем же `external_id` от того же ключа вернёт уже
созданный инцидент (без дубликата). Ответ — инцидент (`source: "api"`). Ошибки: 401 — нет/неверный/отозванный ключ,
404 — машина не найдена, 422 — неверные данные.
Публичная форма **`/report`** (без входа) использует `GET /api/v1/public/report-options` (места и машины) и
`POST /api/v1/public/incidents` (`location_id`, `equipment_id`, `description`, `reporter_name`, `reporter_contact` — обязательны,
`kind` — `fault` «работает, но есть неисправность» или `incident` «остановлено, авария»;
машина должна стоять в выбранном месте или вложенном в него). Не более 5 отправок с адреса за 10 минут. Форму выключает
администратор (настройка `public_incidents`).
## Задачи и поручения (бывшие «суточные задания»)
`GET /api/v1/tasks` — фильтры `day`, `from`, `to`, `assignee_id`, `equipment_id`, `status`, `mine` (я исполнитель), `created_by_me`
(поставил я), `active`. `POST/PATCH /api/v1/tasks` — поле `labor_hours` (трудоёмкость-норма, ч, необязательно; 0–1000).
`POST /api/v1/tasks/{id}/transfer` `{"assignee_id": 7, "comment": "причина"}` — исполнитель, поставивший задание или инженер;
незавершённое задание у нового исполнителя становится «назначен»; в истории — событие `kind: "transfer"`.
## Оборудование: отделы и краны
У оборудования поля `department_id` (за каким отделом закреплено) и `is_crane` (кран). Сотрудник с отделом видит оборудование своего
отдела и вложенных, а также без отдела; администратор, модератор и сотрудник без отдела — всё. Это касается `GET /equipment`,
`GET /incidents`, `GET /equipment-health`, `GET /map`, отчёта о состоянии узлов и уведомлений об инцидентах.
При удалении отдела его оборудование переходит к вышестоящему отделу.
Краны — отдельная служба: настройка `crane_map` (`PUT /api/v1/settings {"crane_map": true}`, в `GET /me` — `crane_map`).
Тогда `GET /map` (`kind=plant`) показывает оборудование без кранов, `GET /map?kind=crane` — только краны (при выключенной настройке — 404);
картинка карты кранов: `GET/PUT /map/image?kind=crane` (если своей нет — отдаётся план завода; в ответе `/map` — `own_image`).
`GET /incidents?cranes=true|false` — только краны / всё, кроме кранов. Также у инцидентов фильтры `mine`, `source=staff|public|api`, `q`.
## История машины
`GET /api/v1/equipment/{id}/history?types=incident,service,meter,order,task&from=2026-10-01&to=2026-10-09&q=текст` —
хронология (новые сверху): `{total, counts, rows: [{at, type, type_ru, title, text, user_name, order_id?, task_id?, incident_id?}]}`.
## Отделы
`GET/POST /api/v1/departments`, `GET/PATCH/DELETE /api/v1/departments/{id}` — `{name, parent_id, head_user_id, notes}`, в ответе `members`
(число активных сотрудников). Править — модератор/админ. У пользователя поле `department_id` (в ответе ещё `department: {id, name}`).
При удалении отдела его сотрудники остаются без отдела, вложенные отделы поднимаются на уровень выше.
## Резервные копии (администратор)
- `GET /api/v1/backups` — список копий и настройки автокопирования (`auto_hours`, `keep`, `media`, `next_auto`).
- `POST /api/v1/backups` `{"include_media": false, "note": "перед обновлением"}` — создать копию.
- `GET /api/v1/backups/{name}` — скачать zip; `DELETE /api/v1/backups/{name}` — удалить.
- `POST /api/v1/backups/upload` (тело — zip) — загрузить копию, например с другого сервера.
- `POST /api/v1/backups/{name}/restore` `{"confirm": true}` — заменить ВСЕ данные продуктовой базы содержимым копии
(текущее состояние сначала сохраняется копией `…_before-restore.zip`; после восстановления все входят заново).
Состав zip: `manifest.json`, `db/<таблица>.json` (переносимый формат — PostgreSQL ↔ SQLite), `config/environment.json`
(настройки сервера и несекретные переменные окружения; секреты не сохраняются — только их названия), `media/…` — фото и картинки,
если выбрано. Автокопии: настройки `backup_auto_hours` (0 — выключено), `backup_keep`, `backup_media` в `PUT /api/v1/settings`.
Копии лежат в `DATA_DIR/backups` (в Docker — том `appdata`). В демо-режиме копирование и восстановление недоступны.
## Демонстрационный режим (личный)
Демо включается для конкретного пользователя: его запросы идут в отдельную тестовую базу, остальные работают с рабочей.
Тип базы определяется токеном: после включения/выключения клиент получает новую пару токенов (или обновляет её через `/auth/refresh`)
и должен очистить локальные данные, если `GET /me` → `demo_mode` изменился.
- `GET /demo/status` (без входа) → `{active}` — есть ли демо-данные (для подсказки на экране входа).
- `GET /demo/me` → `{in_demo, can_leave}`; `POST /demo/leave` — выйти из демо (возвращает токены рабочей базы).
- Администратор: `GET /demo` — кто в демо и тестовые учётные записи; `POST /demo/enable {"confirm": true}` — войти самому (токены);
`POST /demo/users/{id} {"on": true|false}` — включить/выключить демо другому сотруднику; `POST /demo/reset` — пересоздать
тестовые данные; `POST /demo/destroy` — удалить их (все возвращаются к рабочим данным); `POST /demo/impersonate {"user_id"}` —
войти под тестовым пользователем (только из демо-сессии).
## Документы
`/guide.pdf` — руководство пользователя, `/presentation.pdf` и `/presentation.pptx` — презентация, `/app.apk` — приложение для Android,
`/report` — публичная форма заявки (все без входа).
## Обратная связь и администраторы портала
- `POST /api/v1/feedback` `{kind: idea|bug|other, text, page?, client: web|m|android}` — любой сотрудник (не больше 20 в час).
- `GET /api/v1/feedback?status=open|new|taken|done|rejected`, `PATCH /api/v1/feedback/{id}` `{status?, answer?}`, `DELETE /api/v1/feedback/{id}` —
только **администраторы портала**.
- Администратор портала: `is_admin` и (`portal_admin` или логин `ADMIN_USERNAME`, обычно admin). В `GET /me` — `portal_admin`, `is_root`.
Флаг `portal_admin` у пользователя меняет только admin; admin нельзя лишить прав администратора или отключить.
- Только администраторам портала доступны `PUT /settings` (кроме отметок мастера `setup_done`), полный `GET /settings`, `/api-keys`,
`/assistant/config` и `/assistant/test`. Остальные администраторы получают в `GET /settings` сокращённый ответ (`limited: true`).
## AI-помощник
Включает администратор: «Администрирование → Настройки сервера → AI-помощник» (`GET/PUT /api/v1/assistant/config`:
`enabled, provider (openai | anthropic | gigachat), base_url, model, api_key, scope`; ключ в ответах не возвращается и не попадает
в резервные копии). Проверка: `POST /api/v1/assistant/test`.
- `GET /api/v1/assistant/status` → `{enabled}` — показывать ли кнопку помощника.
- `POST /api/v1/assistant/chat` `{"messages": [{"role": "user", "content": "Как зафиксировать аварию?"}]}` → `{reply, offtopic, tools}`.
Помощник отвечает только о работе в портале (иначе — вежливый отказ, `offtopic: true`), знает роль пользователя и читает данные
только через этот же API от имени пользователя (его задания, наряды, инструмент, оборудование, инциденты) — ничего не меняет.
Ограничения: вопрос до 1000 символов, история — последние 10 сообщений, не более 30 вопросов в час на пользователя.
OpenAI-совместимый провайдер подходит для OpenAI, YandexGPT (`https://llm.api.cloud.yandex.net/v1`), DeepSeek, OpenRouter
и локальных моделей (Ollama: `http://localhost:11434/v1`, ключ не нужен).
## Версия Android-приложения
`GET /api/v1/app/version` (без входа) → `{available, version, size, url}` — версия берётся из имени файла `checklists-<версия>.apk`.
Приложение проверяет её при запуске и предлагает скачать обновление, если на сервере версия новее.
+149
View File
@@ -0,0 +1,149 @@
# Установка на свой сервер (Docker Compose, без Coolify)
Портал поставляется как обычный проект Docker Compose: приложение (Python, FastAPI) + PostgreSQL 16.
Никаких внешних сервисов не нужно — Coolify, Kubernetes и т. п. необязательны. Ниже — полная установка с нуля,
обновление, резервные копии, откат и перенос на другой сервер.
## 1. Что нужно
- Сервер Linux (Debian 12/13, Ubuntu 22.04/24.04 и т. п.), 2 ядра, 2 ГБ ОЗУ, от 10 ГБ диска (+ место под фото).
- Docker Engine 24+ с плагином Compose v2: `docker --version`, `docker compose version`.
Установка: <https://docs.docker.com/engine/install/> (на Debian и Ubuntu — `curl -fsSL https://get.docker.com | sh`).
- Git (или архив с исходниками).
- Доменное имя и обратный прокси с HTTPS (nginx, Nginx Proxy Manager, Caddy, Traefik — любой). Без HTTPS не работают
уведомления в браузере и установка мобильной версии на iPhone; приложение для Android в релизной сборке ходит только по HTTPS.
## 2. Получить проект
```bash
sudo mkdir -p /opt/portal && sudo chown $USER /opt/portal
git clone <адрес репозитория> /opt/portal
cd /opt/portal
git checkout main
```
## 3. Настроить переменные
```bash
cp .env.example .env
nano .env
```
| Переменная | Что указать |
|---|---|
| `DB_PASSWORD` | Пароль базы (случайный: `openssl rand -hex 24`). После первого запуска **не меняйте** — он записан в базу. |
| `SECRET_KEY` | Ключ подписи токенов, **не короче 32 символов**: `openssl rand -hex 32`. Смена завершит все сессии. |
| `ADMIN_PASSWORD` | Пароль пользователя `admin`, создаётся при первом запуске, **не короче 10 символов**. |
| `APP_TZ` | Часовой пояс предприятия (`Europe/Moscow`, `Asia/Yekaterinburg` …): смены, расписание нарядов, отчёты. |
| `APP_PORT` | Порт на хосте, куда смотрит прокси (по умолчанию 8080). |
| `FORWARDED_ALLOW_IPS` | Адрес обратного прокси, как его видит приложение (см. п. 6). |
В боевом режиме (`APP_ENV=production` прописан в `docker-compose.yml`) приложение **не запустится** со слабыми `SECRET_KEY`
или `ADMIN_PASSWORD` — это защита, а не ошибка: задайте значения по таблице.
## 4. Запустить
```bash
docker compose up -d --build
docker compose ps # app и db — running (healthy)
curl -s http://127.0.0.1:8080/api/v1/health
```
Первая сборка занимает несколько минут. Логи: `docker compose logs -f app`.
Данные хранятся в томах Docker: `pgdata` (база), `photos` (фото нарядов и задач), `appdata` (резервные копии, демо-база,
загруженный APK). Они переживают пересборку и перезапуск контейнеров. Удаляет их только `docker compose down -v` — **не делайте так**
на рабочем сервере.
## 5. Первый вход
Откройте `http://<сервер>:8080` (до настройки прокси) или ваш домен, войдите как `admin` с паролем из `ADMIN_PASSWORD`
и **сразу смените пароль**: «Администрирование → Пользователи» → admin → «Изменить» → новый пароль. Пустая система предложит **мастер первичной настройки**:
графики, должности, отделы, места, оборудование, сотрудники, карта, склады, чек-листы, резервные копии.
## 6. Обратный прокси и HTTPS
Приложение слушает обычный HTTP на `APP_PORT`; HTTPS даёт прокси. Пример для nginx на том же сервере
(сертификат — certbot: `sudo certbot --nginx -d portal.example.ru`):
```nginx
server {
listen 443 ssl http2;
server_name portal.example.ru;
# ssl_certificate / ssl_certificate_key — пропишет certbot
client_max_body_size 20m; # фото и картинки карт; иначе ошибка 413
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s; # PDF-отчёты и AI-помощник могут отвечать долго
}
}
server { listen 80; server_name portal.example.ru; return 301 https://$host$request_uri; }
```
- **Nginx Proxy Manager:** Proxy Host → Forward `http://<IP сервера>:8080`, SSL (Let's Encrypt), Force SSL; во вкладке Advanced —
`client_max_body_size 20m;`.
- **Caddy:** `portal.example.ru { reverse_proxy 127.0.0.1:8080 }` и `request_body { max_size 20MB }`.
**Адрес прокси (`FORWARDED_ALLOW_IPS`).** Приложение верит заголовку `X-Forwarded-For` только от этого адреса — иначе любой мог бы
подделать свой IP и обойти ограничение попыток входа. Как настроить: зайдите на портал через прокси администратором →
«Администрирование → Настройки сервера», блок «Состояние», строка «Адрес клиента, каким его видит сервер». Пока прокси не указан,
там виден **адрес самого прокси** (если прокси на том же хосте — обычно шлюз сети Docker вида `172.18.0.1`, если на другой машине —
её IP). Впишите его в `FORWARDED_ALLOW_IPS` и примените: `docker compose up -d`. После этого в той же строке должен появиться
**ваш собственный** адрес — значит, всё настроено.
**Файрвол.** Порт `APP_PORT` должен быть доступен только прокси. Docker публикует порты в обход `ufw`, поэтому либо привяжите
порт к localhost (в `docker-compose.yml`: `"127.0.0.1:${APP_PORT:-8080}:8000"` — если прокси на этом же сервере), либо закройте
его правилом в цепочке `DOCKER-USER` (iptables) или на внешнем файрволе.
## 7. Приложение для Android
Готовый подписанный APK лежит в `release/` и входит в образ: ссылка «Скачать приложение для Android» на странице входа и адрес
`/app.apk` работают сразу. Новую версию можно выложить без пересборки — положить файл в том `appdata`:
```bash
docker compose cp checklists-1.7.0.apk app:/srv/data/apk/
```
Версия берётся из имени файла (`checklists-<версия>.apk`); приложения сами предложат обновиться. В приложении на телефоне
указывается адрес вашего сервера (`https://portal.example.ru`).
## 8. Обновление
```bash
cd /opt/portal
git pull
docker compose up -d --build
```
Перед обновлением сделайте резервную копию (п. 9). Структура базы обновляется сама при запуске (только добавление новых полей),
поэтому откат на предыдущую версию кода возможен: `git checkout <прежний коммит> && docker compose up -d --build`.
## 9. Резервные копии
1. **Встроенные (рекомендуется):** «Администрирование → Резервные копии». Копия — zip с базой в переносимом формате и настройками
(по желанию — с фото); есть автокопии по расписанию (по умолчанию раз в сутки, хранится 14). Копии лежат в томе `appdata`
(`/srv/data/backups`) — **регулярно забирайте их на другой носитель**:
```bash
docker compose cp app:/srv/data/backups ./backups-$(date +%F)
```
2. **Средствами PostgreSQL:**
```bash
docker compose exec -T db pg_dump -U checklists -Fc checklists > portal-$(date +%F).dump
docker run --rm -v portal_photos:/p -v "$PWD":/b alpine tar czf /b/photos-$(date +%F).tgz -C /p .
```
Имя тома — `<имя проекта>_photos` (имя проекта по умолчанию — имя каталога, здесь `portal`): `docker volume ls`.
Восстановление дампа: `docker compose exec -T db pg_restore -U checklists -d checklists --clean --if-exists < portal-ДАТА.dump`.
## 10. Перенос на другой сервер
1. На старом сервере: «Резервные копии» → «Создать копию» с фото → скачать zip.
2. На новом: установка по пп. 1–6 (новые `DB_PASSWORD`/`SECRET_KEY` допустимы), вход `admin`.
3. «Резервные копии» → «Загрузить копию» → «Восстановить». Все данные, пользователи и пароли станут как на старом сервере;
после восстановления все входят заново.
## 11. Обслуживание
| Задача | Команда |
|---|---|
| Состояние | `docker compose ps` |
| Логи приложения | `docker compose logs -f --tail 200 app` |
| Перезапуск | `docker compose restart app` |
| Остановить / запустить | `docker compose stop` / `docker compose start` |
| Место на диске | `docker system df`, «Настройки сервера» → свободно на диске |
| Забыт пароль admin | другой администратор задаёт новый в «Пользователях»; если администратор один — восстановите из копии или обратитесь к разработчику |
Важно: приложение работает **одним процессом** (так настроено в образе) — планировщик нарядов и демо-режим живут внутри него.
Не масштабируйте сервис `app` на несколько копий.
## 12. Если что-то не так
- **Контейнер app перезапускается**, в логах `SECRET_KEY не задан или слишком короткий` / `ADMIN_PASSWORD слабый` — задайте значения по п. 3.
- **Фото не загружаются (413)** — не задан `client_max_body_size 20m` в прокси.
- **Все входы «блокируются на 5 минут»** — прокси не указан в `FORWARDED_ALLOW_IPS`, и все клиенты видны с одного адреса (п. 6).
- **Неверное время нарядов и смен** — проверьте `APP_TZ` и время сервера («Настройки сервера» → время сервера).
- **Нет уведомлений на телефоне** — нужен HTTPS; для Android-push через Firebase — ключ в «Настройках сервера».
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

+88
View File
@@ -0,0 +1,88 @@
{
"a_login": [
"Адрес сервера — его сообщит администратор (например, https://checklists.example.ru)",
"Ваш логин и пароль",
"«Войти»"
],
"a_work": [
"Шапка: быстрые действия (задача, авария, неисправность, заявка) и сводка",
"Сводка: мои наряды и задачи, открытые аварии и неисправности",
"Блок «Наряды»: фильтры и ближайшие наряды; «Все →» — полный список",
"Меню: все разделы приложения"
],
"a_list": [
"Время последней синхронизации",
"Меню: все разделы приложения",
"Поиск и обновление",
"Фильтры: активные, мои, завершённые",
"Карточка наряда: статус, срок, прогресс. «▶ можно выполнять» — наряд ваш"
],
"a_order_top": [
"Назад к списку",
"Статус наряда и кто назначен",
"Число: введите значение, ниже — допустимая норма"
],
"a_order_bottom": [
"Выбор варианта — нажатием на нужный",
"Фото: откроется камера телефона",
"«Сдать наряд» — когда всё заполнено"
],
"a_offline": [
"Сверху видно, сколько нарядов ещё не отправлено: данные сохранены на телефоне и уйдут сами",
"Приложение сообщает, что связи нет",
"Наряд помечен «не отправлено»"
],
"a_menu": [
"Кто вошёл: ФИО и должность",
"Работа: вкладка «Задачи», наряды, задачи и поручения",
"Оборудование: состояние машин и фиксация инцидентов",
"Инструмент: что выдано вам и заявки на покупку",
"Карта завода и сотрудники открываются в полной версии сайта (в браузере)"
],
"a_tasks": [
"Кнопка меню — переход к другим разделам",
"Показать все задания или только активные",
"Карточка задания: статус, день, оборудование",
"Поставить задание себе"
],
"a_task": [
"Статус задания",
"Комментарий: обязателен для «приостановлен», «выполнен», «отменён»",
"Кнопки смены статуса",
"Фото к заданию — с камеры телефона"
],
"a_equipment": [
"Состояние машины: «Авария» — есть открытый инцидент",
"Зафиксировать аварию (машина остановлена)",
"Сообщить о неисправности (машина работает)",
"Инцидент: что случилось, узлы, была ли замена; «Закрыть» — после устранения"
],
"a_history": [
"Фильтры по типам событий",
"События: что произошло, когда, кто"
],
"a_incident": [
"Что произошло и подробности",
"Затронутые узлы (заведены в карточке машины); отметка «заменён» сбрасывает ресурс узла",
"«Зафиксировать» — машина получит статус «Авария»"
],
"a_assistant": [
"Вопрос пользователя",
"Ответ со ссылками: нажмите — откроется нужный экран",
"Поле вопроса"
],
"a_settings": [
"Ваша должность и статус смены",
"Принудительная синхронизация",
"Выход из аккаунта"
],
"a_review_list": [
"Фильтр «На проверку» — наряды, которые ждут вашего решения",
"«✔ ждёт вашей проверки» — откройте наряд"
],
"a_review": [
"Комментарий проверяющего (обязателен при возврате)",
"«Принять» — наряд завершён",
"«Вернуть» — на доработку"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.
Binary file not shown.
+169
View File
@@ -0,0 +1,169 @@
<!doctype html><html lang="ru"><meta charset="utf-8"><title>Портал технической службы</title>
<style>
@page{size:1280px 720px;margin:0}
*{box-sizing:border-box;margin:0}
body{font-family:"Segoe UI",Arial,sans-serif;color:#1d2330}
.s{width:1280px;height:720px;page-break-after:always;position:relative;overflow:hidden;padding:48px 64px;background:#f5f7fb}
.s:last-child{page-break-after:auto}
h1{font-size:54px;line-height:1.1}h2{font-size:38px;margin-bottom:6px}
.sub{color:#5b6475;font-size:20px;margin-bottom:26px}
.dark{background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff}
.dark .sub{color:#c7d6ff}
.row{display:flex;gap:36px;align-items:flex-start}
.col{flex:1}
ul{list-style:none;padding:0}
li{font-size:20px;line-height:1.35;margin:0 0 14px;padding-left:30px;position:relative}
li:before{content:"";position:absolute;left:0;top:8px;width:14px;height:14px;border-radius:4px;background:#2563eb}
li b{display:block;font-size:21px}
li span{color:#5b6475;font-size:17px}
.shot{border-radius:12px;box-shadow:0 8px 30px rgba(20,30,60,.22);border:1px solid #d5dbe8;background:#fff;overflow:hidden}
.shot img{display:block;width:100%}
.cards{display:flex;gap:20px;margin-top:40px}
.card{flex:1;background:rgba(255,255,255,.14);border-radius:16px;padding:22px}
.card b{font-size:24px;display:block;margin-bottom:8px}.card span{font-size:17px;color:#dbe6ff}
.tag{display:inline-block;background:#e6edff;color:#1d4ed8;border-radius:20px;padding:4px 14px;font-size:15px;margin:0 6px 6px 0}
.num{position:absolute;right:64px;bottom:24px;color:#8a93a6;font-size:14px}
</style>
<div class="s dark">
<div style="font-size:20px;letter-spacing:2px;opacity:.8;margin-top:40px">ДЛЯ РЕМОНТНЫХ СЛУЖБ</div>
<h1 style="margin-top:14px">Портал<br>технической службы</h1>
<div class="sub" style="margin-top:22px;font-size:24px">Задачи, наряды и чек-листы, отказы, карты завода и кранов, инструмент и люди — в одной системе</div>
<div class="cards">
<div class="card"><b>Отчётность</b><span>Кто, на какой машине, какой чек-лист и когда выполнил — за любой период, в PDF и Excel</span></div>
<div class="card"><b>Гибкая настройка</b><span>Любые графики смен, чек-листы с разными типами ответов, автозапуск нарядов по расписанию</span></div>
<div class="card"><b>Приложение</b><span>Android и мобильная версия в браузере. Работает без связи, данные уходят на сервер сами</span></div>
</div></div>
<div class="s">
<h2>Вкладка «Задачи»: всё в одном месте</h2>
<div class="sub">Сотруднику не нужно ходить по разделам — главное на одном экране</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_work.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Наряды</b><span>Мои, на проверку, просроченные — со статусом и поиском</span></li><li><b>Задачи и поручения</b><span>Мои, поставленные мной, на сегодня или все дни</span></li><li><b>Отказы и неисправности</b><span>Два блока, списком или на карте завода и кранов</span></li><li><b>Шапка быстрых действий</b><span>Задача, авария, неисправность, заявка — в одно касание</span></li></ul></div>
</div></div>
<div class="s">
<h2>Отчётность: ответы за минуты, а не за часы</h2>
<div class="sub">«Выполнение работ» и «Выполненные наряды» — для руководителя, инженера и проверок</div>
<div class="row">
<div class="col" style="flex:.8"><ul>
<li><b>% выполнения по каждой машине</b><span>Суточные осмотры, чек-листы, ТО и задания; отклонения и комментарии</span></li>
<li><b>Машина · чек-лист · исполнитель · должность · время</b><span>Каждая выполненная проверка — одной строкой</span></li>
<li><b>Обязательный период с точностью до минуты</b><span>Быстрые кнопки: сегодня, вчера, 7 и 30 дней, месяц</span></li>
<li><b>Отклонения от нормы видны сразу</b><span>Колонка «Откл.» — число пунктов вне допуска</span></li>
<li><b>Экспорт в PDF и CSV (Excel)</b><span>Готовый документ с кириллицей одним нажатием</span></li>
</ul></div>
<div class="shot" style="flex:1.25;height:490px"><img src="../guide/img/d_exec_report.png" style="width:100%"></div>
</div></div>
<div class="s">
<h2>Отчёт по сотрудникам</h2>
<div class="sub">Количество и качество выполненных задач за период</div>
<div class="row">
<div class="col" style="flex:.8"><ul><li><b>Список и календарь</b><span>Сколько задач выполнено по дням и суммарная трудоёмкость</span></li><li><b>Качество, а не только количество</b><span>В срок, просрочки, возвраты с проверки, отклонения от норм</span></li><li><b>Трудоёмкость</b><span>Норма часов в задаче или фактическое время работы</span></li><li><b>Фильтры и выгрузка</b><span>Период, отдел, должность, сотрудник; PDF и CSV</span></li></ul></div><div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_people_cal.png" style="width:100%"></div>
</div></div>
<div class="s">
<h2>Анализ надёжности</h2>
<div class="sub">Наработка на отказ, восстановление, готовность и прогноз — по реальным отказам и счётчикам</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_reliability.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Показатели по машинам и парку</b><span>θ, λ, время восстановления, коэффициент готовности, простой</span></li><li><b>Честно при малом числе отказов</b><span>Консервативная граница с доверием 90 % — даже без отказов</span></li><li><b>Причины и признаки</b><span>Что ломается, почему и какие отказы можно было предотвратить</span></li><li><b>Дрейф параметров и CSV</b><span>Прогноз выхода за норму по чек-листам; выгрузки для своих расчётов</span></li></ul></div>
</div></div>
<div class="s">
<h2>Гибкая настройка под ваше производство</h2>
<div class="sub">Всё настраивается администратором в интерфейсе, без программиста</div>
<div class="row">
<div class="shot" style="flex:1.15;height:520px"><img src="../guide/img/d_schedule.png" style="width:100%"></div>
<div class="col" style="flex:.9"><ul>
<li><b>Конструктор графиков смен</b><span>Дни, ночи, отсыпной, сутки через трое, любой цикл — с календарём-предпросмотром</span></li>
<li><b>Чек-листы любой структуры</b><span>Ответ: статус, выбор из списка, число с границами нормы, текст; фото — обязательно или по желанию</span></li>
<li><b>Наряды вручную и автоматически</b><span>Шаблон + периодичность: ежедневно, по дням недели, раз в месяц, каждые N дней</span></li>
<li><b>Исполнитель — человек или должность на смене</b><span>Наряд видит тот, кто сейчас работает</span></li>
<li><b>Роли и проверка</b><span>Техник, инженер, модератор, администратор; приёмка или возврат на доработку</span></li>
</ul>
<div style="margin-top:6px"><span class="tag">Оборудование и места</span><span class="tag">Каталог фото</span><span class="tag">Комментарии</span><span class="tag">Уведомления</span></div></div>
</div></div>
<div class="s">
<h2>Приложение всегда с техником</h2>
<div class="sub">Нативное Android-приложение и мобильная версия в браузере (в том числе для iPhone)</div>
<div class="row">
<div class="shot" style="width:230px;height:490px"><img src="../guide/img/a_menu.png" style="width:100%"></div>
<div class="shot" style="width:230px;height:490px"><img src="../guide/img/a_task.png" style="width:100%"></div>
<div class="col" style="padding-left:10px"><ul>
<li><b>Полностью без связи</b><span>Подвал, цех, подстанция: ответы, фото и комментарии копятся и уходят на сервер сами</span></li>
<li><b>Всё для работы в поле</b><span>Крупные кнопки, контроль нормы, фото с камеры, «Взять в работу», «Сдать наряд»</span></li>
<li><b>Боковое меню</b><span>Наряды, задания, отказы, инструмент и заявки — без лишних панелей</span></li>
<li><b>Установка за минуту</b><span>APK скачивается прямо с экрана входа; Google Play не нужен</span></li>
<li><b>iPhone и любой браузер</b><span>Мобильная версия /m ставится на экран «Домой» и тоже работает офлайн</span></li>
</ul></div>
</div></div>
<div class="s">
<h2>Карта завода: состояние машин с одного взгляда</h2>
<div class="sub">План предприятия с иконками оборудования, обновляется сам</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_map.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>«!» — авария</b><span>Сотрудник зафиксировал отказ — значок появляется сразу</span></li><li><b>Жёлтое свечение — пора на ТО</b><span>Узел машины приближается к пределу по наработке или календарю</span></li><li><b>Настраивает администратор</b><span>Загружает план и расставляет иконки перетаскиванием</span></li><li><b>Карта складов</b><span>Зоны складов на том же плане; нажатие показывает содержимое склада</span></li></ul></div>
</div></div>
<div class="s">
<h2>Краны и закрепление за отделами</h2>
<div class="sub">Каждая служба видит своё оборудование</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_crane_map.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Краны — отдельная служба</b><span>Свой раздел меню, список, карта кранов и отказы по кранам</span></li><li><b>Включается одной галочкой</b><span>В настройках сервера; своя картинка карты или план завода</span></li><li><b>Оборудование за отделами</b><span>Крановый электрик не получает списков и аварий по станкам</span></li><li><b>И в приложении</b><span>Раздел «Краны» в Android появляется автоматически</span></li></ul></div>
</div></div>
<div class="s">
<h2>Отказы, неисправности и задачи</h2>
<div class="sub">Аварии фиксируются на месте, мелкие поручения — без нарядов</div>
<div class="row">
<div class="col" style="flex:.8"><ul><li><b>Отказ в карточке машины</b><span>Что произошло, затронутые узлы, была ли замена</span></li><li><b>Сообщение о неисправности</b><span>Машина работает, но есть проблема: оранжевый «⚠» на карте; при остановке — перевод в отказ</span></li><li><b>Задачи и поручения</b><span>Назначен → в работе → приостановлен / выполнен / отменён</span></li><li><b>Комментарий обязателен</b><span>При приостановке, выполнении и отмене; можно приложить фото</span></li></ul></div><div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_incident.png" style="width:100%"></div>
</div></div>
<div class="s">
<h2>Инструмент и склады</h2>
<div class="sub">Учёт инвентаря: что где лежит и что за кем числится</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_inventory.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Каталог и склады</b><span>Поступление, выдача, возврат, перемещение, списание — с журналом</span></li><li><b>Выдача лично сотруднику</b><span>Инструмент числится за человеком, а не за должностью</span></li><li><b>Роль «Администратор склада»</b><span>Ведёт учёт; сотрудник видит только выданное ему</span></li><li><b>Заявки на инструмент</b><span>Что купить, зачем, ссылка на товар; ответ и статус — автору</span></li></ul></div>
</div></div>
<div class="s">
<h2>Карточка сотрудника</h2>
<div class="sub">Кто есть в смене и что умеет</div>
<div class="row">
<div class="col" style="flex:.8"><ul><li><b>ФИО, фото, должность</b><span>И отметка, на смене ли человек сейчас</span></li><li><b>График</b><span>Ближайшие смены по графику должности</span></li><li><b>«Что умею»</b><span>Навыки и допуски заполняет сам сотрудник</span></li><li><b>Текущие задачи и инструмент</b><span>Задания, наряды и выданный инвентарь</span></li></ul></div><div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_user_card.png" style="width:100%"></div>
</div></div>
<div class="s">
<h2>AI-помощник по порталу</h2>
<div class="sub">Подскажет, где что найти и как сделать, — и только по делу</div>
<div class="row">
<div class="col" style="flex:.8"><ul><li><b>Знает портал и права пользователя</b><span>Отвечает по шагам со ссылками на нужные разделы</span></li><li><b>Видит только данные пользователя</b><span>Его задания, наряды, инструмент, состояние машин — и ничего не меняет</span></li><li><b>Только по работе</b><span>На посторонние вопросы вежливо отказывает; лимит вопросов в час</span></li><li><b>Включает администратор</b><span>YandexGPT, GigaChat, OpenAI-совместимые и локальные модели</span></li></ul></div><div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_assistant.png" style="width:100%"></div>
</div></div>
<div class="s">
<h2>История машины и обращения извне</h2>
<div class="sub">Всё, что происходило с оборудованием, — в одной ленте</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_history.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Хронология по дням</b><span>Отказы, замены узлов, наработка, наряды и задания</span></li><li><b>Фильтры</b><span>По типу события, периоду и тексту</span></li><li><b>Публичная форма /report</b><span>Подрядчик сообщает об аварии без входа: место → машина → описание и контакты</span></li><li><b>API для внешних систем</b><span>Диспетчерская или SCADA заводят отказы по ключу доступа</span></li></ul></div>
</div></div>
<div class="s">
<h2>Быстрый старт и сохранность данных</h2>
<div class="sub">Мастер первичной настройки и резервные копии</div>
<div class="row">
<div class="shot" style="flex:1.25;height:500px"><img src="../guide/img/d_setup.png" style="width:100%"></div><div class="col" style="flex:.8"><ul><li><b>Мастер по шагам</b><span>Графики, должности, отделы, места, оборудование, сотрудники, карта, склады</span></li><li><b>Пакетный ввод</b><span>Одна строка — одна запись; можно вставить столбцы из Excel</span></li><li><b>Логины и пароли — таблицей</b><span>Для печати и раздачи сотрудникам</span></li><li><b>Резервные копии</b><span>База и конфигурация, автокопии по расписанию, восстановление</span></li></ul></div>
</div></div>
<div class="s dark">
<h2 style="font-size:44px;margin-top:70px">Готово к запуску</h2>
<div class="sub" style="font-size:22px">Развёртывание на вашем сервере, данные остаются у вас</div>
<div class="cards">
<div class="card"><b>Свой сервер</b><span>Docker + PostgreSQL, запуск через Coolify за обратным прокси; резервное копирование стандартными средствами</span></div>
<div class="card"><b>Демонстрационный режим</b><span>Личный: включается только у вас, остальные работают с рабочими данными</span></div>
<div class="card"><b>Открытый API</b><span>REST /api/v1, ключи доступа для внешних систем, резервные копии и мастер настройки</span></div>
</div>
<div style="margin-top:60px;font-size:24px">Руководство пользователя (PDF) и описание API входят в поставку.</div>
</div>
</html>
Binary file not shown.