# Спецификация рабочего сервиса ресурсного планирования Статус: **предложение к утверждению**, 13.10.2026, STT-232 / T-12345. Это спецификация поведения, а не разрешение начать реализацию. Сначала заказчик утверждает её и ответы на блокирующие вопросы, затем архитектуру и пакеты `tasks.md`. Код в этой задаче не создаётся. ## Цель и источники РП запрашивает ресурсы; ресурсный менеджер (РМ) назначает сотрудников и принимает решения; согласованные назначения образуют единый план. Рабочий контур добавляет вход, серверные права, справочники, факт и журнал без изменения смысла формул прототипа. База: ТЗ v1.7 от 09.10.2026, фактические заголовки §1–7, §13–30; `../../tz-v1.7/tz.md`; анализ `../../analysis/{tz-issues,gap,plan}.md`; прототип `../../prototype-v2/js/{calc,api,seed}.js`, commit `c2642df`. Отсутствующие §8–12 и AC 01–40 не считаются восстановленными: критерии ниже выведены из FR. Ошибочные ссылки ТЗ на §31–32 читаются по таблице в `tz-issues.md`. Документы не требуют доступа к исходным путям для чтения требований. Участники: РП (`pm`) — свои заявки и назначения разрешённых проектов; РМ (`rm`) — весь пул и решения. Технический оператор обслуживает подключения, но не получает автоматически роль РМ или расходные ставки. Должностная роль специалиста и право доступа пользователя — разные понятия. ## Требования и проверяемые критерии Каждая строка — отдельное требование; имя будущего теста содержит `[REQ-NNN]`. Уточнения рабочего контура помечены «предложение», окончательное решение — `open-questions.md`. | ID | Источник | Требование и критерий приёмки | | --- | --- | --- | | REQ-001 | §3–4, FR 01–02 | Заявка на один проект: 1–50 строк, 1–24 уникальных месяца на строку; форма предлагает 1–12. При пустом проекте, строках или повторе месяца сохранения нет. Создатель и РП определяются из разрешённого контекста, подмена имени не меняет владельца. | | REQ-002 | FR 01–04 | Роль из согласованного справочника; сотрудник необязателен, но соответствует роли; R > 0 и ≤ 1 000 000 ₽/ч; месячный FTE 0…1 и хотя бы один > 0. При смене роли сотрудник сброшен; совпавшие месяцы сохраняются, новые равны 0; комментарий ≤ 1000 символов. Новая заявка pending не резервирует часы. | | REQ-003 | §5, FR 05–06, 10 | Только pending допускает решение. РМ может изменить сотрудников, FTE и R, но не набор ролей; без сотрудника в любой строке согласование невозможно. approved/returned/rejected повторно не рассматриваются. | | REQ-004 | FR 07 | Согласование проверяет сумму FTE каждого рабочего дня по всем проектам, включая невидимые РП. Значение > 1 + 0,00001 блокируется; ровно 1 допускается. Две одновременные заявки на одного человека не могут обе превысить предел. | | REQ-005 | FR 08, 12–13 | При C > R нужен явный allowLoss; изменение сотрудника, FTE или ставки снимает подтверждение. Показываются текст, Δ и сумма превышения. C = R не требует подтверждения. Неизвестная C не подменяется нулём и блокирует решение до получения ставки. | | REQ-006 | FR 09–10 | Возврат и отказ требуют непустую причину ≤ 1000 символов. Возвращённая заявка исправляется созданием новой pending; исходная сохранена, в плане обе ничего не добавляют до согласования. | | REQ-007 | FR 11, 19; NFR 02 | Решение, назначение, версия и запись журнала сохраняются вместе. Устаревшая версия получает конфликт без частичной записи. При сбое введённые поля остаются в форме, повтор запроса с тем же ключом не дублирует решение. | | REQ-008 | §1, §6; FR 34, 37 | P = Σ дневной FTE × часы рабочего дня; месячный FTE = P/H. В эталонном календаре будней по 8 ч: 0,5 × 176 = 88 ч. Явный days[дата] = 0 имеет приоритет перед месячным FTE. Праздники/сокращённые дни — согласуемое расширение Q-06. | | REQ-009 | §1, §6; FR 35, 40–41 | I = P×R, расход = P×C, Δ = max(C−R,0), превышение = Δ×ΣP, разница сумм = I−расход. Для 88 ч, R=4000, C=4300: I=352000, расход=378400, превышение=26400 ₽. Неизвестная ставка даёт неизвестный итог, а не 0. | | REQ-010 | §1, §6, §18 | Rср=Σ(P×R)/ΣP; Cср аналогично. При 40 ч ×4000 и 48 ч ×5000 Rср=4545,45 на экране. Старые месячные R сводятся с сохранением дохода; округление только при отображении/денежном результате, не до суммирования. | | REQ-011 | FR 14–17, 33 | План: сотрудник → проекты, шесть месяцев с выбранного первого, FTE/ч/%, поиск и раскрытие. Только approved первичных заявок и применённые изменения; change не считается вторым назначением. Фильтр свободных выбирает FTE < 1 за первый месяц и включает полностью свободных. | | REQ-012 | FR 16, 32, 38; NFR 01 | РП получает лишь свои заявки в разрешённых проектах и соответствующие назначения; общий сотрудник или проект не раскрывает заявки другого РП. Фильтры, суммы, CSV, история и детализация подчиняются той же области. РМ видит весь пул. | | REQ-013 | FR 18, 35, 57 | Отсутствующий факт — прочерк. После подключения подтверждённого источника рабочий план показывает его данные с датой актуальности; ручной факт прототипа не притворяется автоматически полученным фактом. Семантика покрытия Time требует Q-05. | | REQ-014 | FR 20–23 | Изменение относится к одному действующему назначению: release/reduce/replace; новых месяцев/ролей нет. Дата ≥ сегодня по Москве, причина непустая ≤ 1000. Для reduce/replace 0 < FTE ≤ 1; замена — другой активный сотрудник той же роли. После даты есть ненулевая загрузка. | | REQ-015 | FR 24 | До даты изменения дневной план неизменен. release обнуляет остаток; reduce берёт min(старый FTE, новый); replace освобождает исходного и задаёт новый FTE замене до конца исходного периода. Нулевые месяцы остаются. В эталонном ноябре 2026 вывод 0,5 FTE с 16.11 даёт 40 ч и 40/168 FTE. | | REQ-016 | FR 21, 25–28 | На назначение не больше одного pending-изменения; создание не меняет план. У решения целевые параметры неизменяемы. Отказ/возврат освобождает возможность новой заявки, исходная история сохраняется. Завершённое назначение доступно только для просмотра. | | REQ-017 | FR 29–31 | При решении сверяется версия исходного назначения и снимок; изменение исходника блокирует устаревшее решение. Замена наследует R, C берётся с сервера для нового сотрудника; итоговый план повторно проверяется по дням, включая allowLoss. | | REQ-018 | §1, FR 36–38 | Отчёт утилизации: фильтры месяц/роль/проект/имя, план, N, свободные часы, число людей, 12 недель, сравнение и таблица. Свободные часы = Σmax(H−P,0), свободный ресурс = число людей−Σ(P/H). Для 88/176: 50% и 88 свободных часов. Пустая выборка этого отчёта даёт 0%. | | REQ-019 | FR 39–41, AC 44 | Финансы детализированы до назначений; средние взвешены часами; превышения считаются по строкам с C>R, а не по средней. 80ч×4000 и 40ч×6000 при C=4300 дают Rср=4666,67 и одну строку превышения. | | REQ-020 | FR 42–45 | Распределение объединяет назначения сотрудника на проекте; состояние имеет допуск 0,001 п.п.; дата освобождения — следующий календарный день за последним ненулевым рабочим днём всего плана проекта. Последний день 27.11.2026 → 28.11.2026. ЦФО/подразделения — Q-10. | | REQ-021 | FR 38, 45; AC 48 | CSV поддержанных отчётов соответствует отфильтрованной таблице, UTF-8 BOM, разделитель ;, десятичная запятая, даты YYYY-MM-DD; значения с разделителем экранируются. Выгрузка не расширяет права. | | REQ-022 | FR 46–49 | Некоммерческий отчёт: план/факт, 3/6/12 месяцев, подразделение и имя, синхронное раскрытие. Итоги FTE суммируются. Нет типов проектов → сообщение о настройке; есть проекты, нет плана → 0. Любой пропуск факта делает итог неполным, известная детализация остаётся. | | REQ-023 | FR 50–53 | Статистика считает уникальных людей, не FTE; человек на двух проектах входит в обе группы, но один раз в общий итог. Текущие подразделения/должности/навыки/ЦФО/состояние/дата выхода не объявляются историческим кадровым срезом. Пустые значения → «Не указано». | | REQ-024 | FR 54–56, 63–64 | РМ меняет разрешённые локальные сведения и ручные факт/отпуска с контролем версии, без изменения назначений. Пустое поле удаляет ручную запись, 0 сохраняется; факт 0…2 FTE, отпуск 0…H, ключи уникальны, не больше 10000 записей в одном сохранении. Импортированные поля не перезаписываются ручным вводом молча (Q-05/Q-07). | | REQ-025 | FR 58–60 | Категории: «Для внутреннего клиента», «Коммерческий», «НИОКР», «Служебные»; подробная категория выше общего типа. Без неё noncommercial → «Некоммерческий (без уточнения)», без типа → «Тип не указан». Одинаковая норма N используется для каждой категории и не суммируется между категориями. | | REQ-026 | §1, FR 60–62 | N = число выбранных людей×H, Nбез=N−V; утилизация плана P/N и P/Nбез, факта A/N и A/Nбез. При N=176,A=P=88,V=16 → 50% и 55%. Накопительно 80+132 ч при нормах 160+176 → 63,1%, не среднее процентов. | | REQ-027 | FR 61–64 | Полнота — по каждому человеку×проекту×месяцу, включая явные нули; при отсутствии проектов типа часы=0. Пропуск отпуска делает Nбез неизвестной, но не N. При Nбез=0 процент неизвестен. Отпуск не входит в фактические работы. | | REQ-028 | INT 01 | У проекта стабильный ID и явная связь с РП. Переименование не создаёт дубль. Смена сущности Сферы меняет сопоставление источника, а не идентификаторы заявок; выбор владельца и сущности блокирует живой импорт (Q-01). | | REQ-029 | INT 02 | Сотрудники/подразделения/роли обновляются без удаления согласованных назначений. Деактивация запрещает новый вход и новые назначения; закрытый проект запрещает новые заявки. Частичный ответ источника не считается массовым удалением. | | REQ-030 | INT 03 | Один мастер заявок/назначений; предложение — сервис. Повтор синка справочников не меняет решения. Запись в Сферу отсутствует до отдельного решения Q-02. | | REQ-031 | INT 04 | Секреты доступны только серверному подключению; их нет в странице, журнале ошибок и выгрузке. Повтор синка идемпотентен; недоступность источника сохраняет последний целый снимок и показывает устаревание. | | REQ-032 | NFR 01 | Неавторизованный пользователь не получает бизнес-данных. Роль РМ не выводится из должности. Недоступный объект не раскрывается; операции решений доступны только РМ. Расходные ставки и производные суммы скрыты до явного права (Q-03). | | REQ-033 | NFR 02 | Журнал каждого создания/решения/изменения содержит неизменяемые actorId, время, причину, before/after, версии и признак принятого превышения; первичный запрос не теряется. Нельзя сохранить назначение без записи журнала. Автор не принимается из тела запроса. | | REQ-034 | NFR 03 | Перед рабочим запуском утверждены объём данных, срок хранения, удаление, RPO/RTO и нагрузка (Q-09); восстановление проверено на отдельной БД. Проверка мониторинга обнаруживает искусственный сбой синка и показывает последнее успешное обновление. Числовой SLA пока не установлен. | | REQ-035 | §30, требование STT-232 | Повтор импорта одного набора часов не удваивает факт; корректировка/удаление исходной записи отражается после успешной сверки. Неопознанный сотрудник/проект не сопоставляется по имени автоматически и не попадает в итог как 0. | | REQ-036 | §1, §30, требование STT-232 | Календарь и ставки имеют источник и версию. Изменение текущей ставки не переписывает снимок согласованного назначения. Невалидный/неполный календарь не активируется. Пересчёт прошлого периода требует решения Q-06. | | REQ-037 | требование STT-232 | Расчёты, переходы и синк воспроизводимы с фиксированными временем, ID и входными данными без живых внешних систем; каждый модуль проверяется через публичный интерфейс. | | REQ-038 | требование STT-232 | Тестовый вход позволяет проверить обе роли без настоящего входа только в тестовом окружении; в рабочей сборке тестовый маршрут отсутствует даже при переданных флагах включения фейков. | ## Жизненные циклы ```mermaid stateDiagram-v2 [*] --> pending: create или change pending --> approved: РМ согласует pending --> returned: РМ возвращает с причиной pending --> rejected: РМ отклоняет с причиной returned --> [*]: новая исправленная заявка с другим ID rejected --> [*] approved --> [*]: решение неизменно ``` Назначение появляется только при approved первичной заявки. `active`/`ended` — вычисляемые состояния по последнему ненулевому дню и Clock; `changePending` — отдельный признак. Решение по change атомарно заменяет текущую версию назначения и сохраняет прошлую. Истечение периода не удаляет запись. ## Переходы назначения для L1 | Состояние | Событие и guard | Результат | | --- | --- | --- | | Не существует | approved первичной заявки, все проверки пройдены | Первая версия назначения; active/ended определяется датой и планом | | active, без pending-change | proposeChange, есть будущая ненулевая загрузка | Та же версия плана + changePending; часы не меняются | | active, changePending | approve change, снимок/версии совпали | Новая версия плана, changePending снят; состояние вычисляется заново | | active, changePending | return/reject с причиной | Версия плана прежняя, changePending снят | | active | Clock прошёл последний ненулевой день | ended, данные и история не меняются | | ended | proposeChange без будущей ненулевой загрузки | Отказ INVALID_TRANSITION, запись не меняется | При выводе с будущей даты статус остаётся active до последнего ненулевого дня; решение не означает немедленного завершения. Если согласование запоздало относительно effectiveDate, оно применяется к сохранённому запросу по правилам снимка FR 29; политика запрета ретроактивного решения, если потребуется, должна быть отдельно согласована — ТЗ ограничивает дату при создании запроса. ## Граница утверждения Q-01…Q-09 блокируют только перечисленные в `open-questions.md` рабочие подключения/запуск, а не подготовку документов. До ответов разрешены проектирование и будущие тесты на синтетических данных после утверждения спеки. Исторические дефекты публичного прототипа (имена вместо ID, отсутствие прав, локальное хранилище) не становятся требованиями к сервису.