Итак, в системе появилось требование: считать сроки не в календарных, а в рабочих днях. Что делать и на что обратить внимание при тестировании и разработке? Какие решения принимать с точки зрения UX?

На первый взгляд - мелочь. Добавили производственный календарь, исключили выходные, готово. На практике же это превратилось в клубок вопросов:

  • Когда начинается отсчёт - в момент создания заявления или с начала рабочего дня?

  • Что делать, если заявление пришла в 23:00?

  • Когда именно наступает просрочка по заявлению?

  • Почему пользователь видит 0/5, хотя “срок уже идёт”?

И самое неприятное - у этих вопросов нет “очевидно правильного” ответа. Любое решение - компромисс между логикой системы и ожиданиями пользователя. В этой статье я расскажу о принятых решениях и немного об их альтернативах.

P.S. Здесь не будет кода, только аналитика, примеры, и заметки по реализации и тестированию.

Суть доработки

  1. Все сроки оказаний услуг будут производиться в рабочих днях.

  2. Календарь один на всех. Подразумевается одинаковое рабочее расписание у каждого сотрудника. Хотя, конечно, можно подумать над усложненной вариацией календаря с личным расписанием, но в этой статье не об этом.

  3. Актуальный календарь с рабочими днями (в нашем случае) настраивается в стороннем сервисе и подтягивается автоматически через API запросы при запуске/перезапуске системы + раз в сутки по мере работы системы проверяется, есть ли календарь на текущий год (если его все еще нет) и на следующий (аналогично, если он все еще не заполнен в системе).

  4. Если календарь не настроен, все сроки будут рассчитываться в календарных днях.

  5. Подразумевается, что календарь настраивается техподдержкой/админом в стороннем сервисе. Т.е. уже существует способ хранения календаря и базовые методы работы с ним.

Примеры сроков регистрации (например, заявлений)

У разных организаций словосочетание "регистрация заявления" может означать разное. Пока у одних это означает "заявление проходит авто/ручную модерацию и только потом публикуется", у других это "так, нам пришло заявление, надо ему вручную/автоматически присвоить номер", а у третьих это "что пришло - то и публикуем, причем как можно скорее".

Сроки могут быть разные. Имеет смысл это обсуждать напрямую с заказчиками. Например, у вот этих ребят (Роснедр) https://www.consultant.ru/document/cons_doc_LAW_362470/66e65fcb6db80a6ea07545b5a8770a24e22131f9/ заявление регистрируется (в том числе в электронной форме) до 12 часов рабочего дня, следующего за днем поступления заявления.

У ФНС, судя по всему, отдается на всё про всё 30 минут. Предполагаю, что также имеется ввиду электронная форма. https://www.consultant.ru/document/cons_doc_LAW_124006/577a2a95103c95741893ced43758ba0bfce44904/

У Минприроды срок 1 рабочий день с момента поступления (в том числе в электронной форме) https://www.consultant.ru/document/cons_doc_LAW_388075/3e4ca88c284c82b6f1c231dbdaa2b17a8f35343a/

И так далее. Что-то гуглится, что-то узнается только из общения. И помним: не факт, что тот закон, который мы нашли, означает именно то, что там написано, и что мы вообще все правильно п��няли, и вообще надо было все реализовывать как бабка нагадала, а не вот это вот всё.

Итоговые принятые требования по рабочим дням с учетом нюансов по рабочим часам

Будем рассуждать про некое "заявление" - на его месте может быть любая ваша сущность.

  • Заявление, опубликованное в системе, автоматически регистрируется в день его публикации. Это значит, что оно будет видно для работы с ним сразу и не будет “ждать” наступления ближайшего рабочего дня, чтобы отобразиться пользователям.

Как работает отсчет начала срока с момента публикации? Допустим, нам на всё про всё дано 5 дней - как их считать?

Если заявление опубликовали:

•    В рабочее время (рабочий день + рабочие часы согласно расписанию), начало срока - день публикации заявления.
(даже если опубликовали в 17:59, а рабочий день до 18:00)

•    В нерабочее время (нерабочий день, или рабочий день, но не рабочие часы согласно расписанию), начало срока - ближайший рабочий день (первый рабочий день, следующий за днем его получения)
Если публикация произошла в 06:00 рабочего дня, но рабочее время с 9:00, значит, начало срока = день публикации.
Если публикация произошла в 23:00 рабочего дня, а рабочий день до 18:00, то начало срока - следующий рабочий день.
Если публикация произошла в нерабочий день, то начало срока - следующий рабочий день.


Некоторые вопросы, которые мы обсуждали

Были некоторые дебаты, просто приведу ответы, к которым мы пришли.

1.  В день, когда происходит публикация, какое значение счетчика? Сколько дней прошло из 5?

Ответ: 0/5 (Независимо от того, в какое время и в какой день опубликовано заявление, будет цифра 0 вплоть до 24:00 ближайшего рабочего дня)

Другие возможные варианты ответа
  • 1 (Независимо от того, в какое время и в какой день опубликовано заявление, будет цифра 1 вплоть до 24:00 ближайшего рабочего дня).

  • В зависимости от того, совпало ли начало срока согласования и день публикации

    • Если публикация произошла в 06:00 рабочего дня, но рабочее время с 9:00, то все это время (начиная с 6:00 и до 24:00) будет цифра 1

    • Если публикация произошла в 23:00 рабочего дня, а рабочий день до 18:00, то с 23:00 до 24:00 - цифра 0, а с 00:00 и весь следующий день - цифра 1

    • Если публикация произошла в нерабочий день, то пока не настанет ближайший рабочий день, будет цифра 0. Когда настанет рабочий день - станет цифра 1

Эти варианты ответа отмели из соображения "Мы хотим показывать сколько ПРОШЛО полных рабочих дней, а не какой по счету сейчас день". Хотя, пожалуй, и то и другое видение могут сбивать с толку - когда наступает просрочка? (об этом ниже)

2.  Когда именно должен увеличиваться счетчик дней?

Ответ: Сразу после того, как прошло 00:00 рабочего дня, который уже шёл в учёт срока.

Другие возможные варианты ответа
  • Сразу после того, как время перешагнуло через конец рабочего дня (отказались, т.к. не увидели проблемы в том, что человек задержится на работе и раскидается с задами, зачем создавать ему просрочку и какая разница, 17:59 или 18:01?)

  • Сразу после того, как время перешагнуло через начало рабочего дня (отказались, т.к. не слишком интуитивно выглядит для пользователя)

3.  Если поздно вечером заявление (за рабочим временем) было опубликовано, то в рабочее время следующего рабочего дня счетчик должен показывать значение 1?

Ответ: Исходя из ответа на 1 вопрос, тут будет также 0. При других вариантах ответа на 1 вопрос могли бы быть другие цифры и здесь.

4.  Какое время в день дедлайна должно выступать сигналом для просрочки?

Ответ: 00:00 дня следующего за дедлайном. Или 24:00 дня дедлайна, одно и то же, кому как легче читается.

Другие варианты ответа
  • Время окончания крайнего рабочего дня (все тот же вопрос, великая ли разница - 17:59 или 18:01?)

  • То же самое время, в которое заявление опубликовалось (посчитали, что не интуитивно)

  • Время начала следующего рабочего дня за дедлайном (посчитали, что не интуитивно)

5.  При каком значении счётчика начинаем считать заявление просроченным?

Ответ: Когда прошло дней ≥ срока (Пример: “Прошло дней 5 из 5”)

Другой вариант ответа

Когда прошло дней > срока (Пример: “Прошло дней 6 из 5”. Не вписалось в остальные пункты).

6.  Давайте разберем примеры. Все ли нас устраивает?

Все те же вопросы: как отображать вещи по типу “прошло N дней из M” ? Когда у нас действительно дедлайн?

Пример 1:
Заявление опубликовали 1 января, первый рабочий день 12 января, дедлайн 16 января.
Заявление обработали 13 января (неважно в какое время) - сколько дней прошло?

Ответ: 1 из 5 дней, потому что именно полных рабочих дней прошло всего 1 (12 января)
И да, технически уже шёл 2 день из 5.

Пример 2:
На обработку заявления отведено 2 дня. Заявление опубликовали в рабочий день в 06:00 или в рабочее время - 09:00, 15:00, 17:59 - неважно. Например, это понедельник.
В понедельник будет 0 из 2 дней.
Во вторник 1 из 2 дней.
В полночь со вторника на среду счетчик станет 2 из 2 и заявление будет просрочено.
То есть по факту может пройти не 2 рабочих дня, а чуть меньше. 1.5 дня. Или 1 день и 1 минута. В зависимости от времени, когда опубликовали.

Пример 3:
На обработку заявления отведено 2 дня. Заявление опубликовали в рабочий день в 20:00 (уже не рабочее время). Например, это понедельник.
В понедельник для всех будет 0 из 2 дней.
Во вторник 0 из 2 дней.
В среду 1 из 2 дней.
В полночь со среды на четверг счетчик станет 2 из 2 и заявление будет просрочено.
То есть по факту пройдёт ровно 2 рабочих дня.

Что если разные организации для обработки одной и той же штуки “живут в разном времени”? (разные часовые пояса)

Ответ: все даты вычисляются по часовому поясу сервера. Можно подумать о более изящном решении, когда в каждом браузере факт просрочки высчитывается по своему. И факт "когда стартовала обработка заявления" как будто бы тоже мог бы зависеть от локального времени. Но поскольку у нас разлёт был небольшой, остановились на времени сервера, которое совпадает с одним из охватываемых часовых поясов.

Нюансы реализации

  • Есть смысл кэшировать календарь на текущий год в памяти, а примерно в декабре - ещё и следующий год. Если сервис никогда не гаснет и работает годами - подумать о том, чтобы подчищать кэш старых календарей.

  • Есть смысл фиксировать в таблицах БД всякие вещи по типу "сколько дней прошло", чтобы не рассчитывать значения счетчиков каждый раз при обращении к серверу для снижения нагрузки и времени ответа.

  • Вероятно есть смысл сохранять в БД также значение "из скольких дней" - сроки работ могут иногда меняться для рассмотрения одного и того же типа заявления. Делать это на уровне таблицы, в которой отдельно хранятся конфигурации "с такого-то дня по такой-то день для такого-то типа сущности был актуален следующий срок обработки..." или сделать просто столбцом и заполнять для каждой сущности - тут уж сами решите, как быстрее, удобнее и лучше.

Как тестировать?

  • Проверить в обычный рабочий день

  • Проверить в выходной (или имитировать через БД/API)

  • Проверить вечером рабочего дня (или имитировать через БД/API)

  • Проверить поведение при всевозможных временно спящих промежуточных/нужных сервисах

  • Проверить поведение при отсутствующем календаре в нужном сервисе (т.е. он не сможет вовремя подгрузиться в систему). Затем добавить календарь в нужный сервис, подождать следующего дня и убедиться, что код автоматической подгрузки в 00:00 отработал.

  • Проверить и обычное количество рабочих дней, и просрочку.

  • Нагрузочное тестирование - что если начинает храниться много календарей? За предыдущий год, за текущий, за следующий?

  • Проверить Fallback на календарные дни

Заключение

Как мы убедились, расчёт сроков в рабочих днях — это не просто арифметика. Это баланс между правилами системы и ожиданиями пользователя. Правила нужно фиксировать и тестировать, иначе простая логика превращается в непредсказуемое поведение и вызывает вопросы у пользователей.