Задача
Сервис доставки стирки устроен сложнее, чем кажется из клиентского приложения. Вещи обрабатывает сеть партнёрских прачечных по районам с разной загрузкой, разным набором услуг и разным временем обработки: собственного производства достаточной мощности у сервиса нет. Возят курьеры со сменами и маршрутами, постоянного парка машин на каждый район тоже нет. При этом клиенту даётся конкретное обязательство: заберём в такое-то окно, вернём в такое-то.
Сервис работает с чужими вещами, которые проходят физический цикл через несколько площадок и должны вернуться конкретному человеку в конкретное время. Поэтому нужно было связать в один управляемый цикл три физических процесса: приём заказа, маршрут курьера и производственный цикл прачечной. Решения принимает система, а не диспетчер по телефону.
Решение
Четыре приложения, одна система. Клиентское приложение — заказ, окна, оплата, отслеживание. Приложение курьера — смена, маршрут, забор и выдача, статусы. Приложение прачечной — приём партии, регистрация позиций, обработка, готовность. Операторская панель — заказы, расписания, очереди, курьеры, площадки и маршруты. Приложения связаны как роли одной системы: курьер не построит маршрут без операторского распределения, прачечная не получит партию без курьера, клиент не увидит статус без производственной части.
Расчёт доступных окон. Приложение показывает только те окна, которые сервис способен обслужить. На доступность окна влияют зона обслуживания адреса, наличие курьерской смены в этой зоне на нужный день, остаток ёмкости окна с учётом уже принятых заказов, минимальное время обработки выбранных услуг, график работы прачечных, способных эти услуги выполнить, их текущая и прогнозируемая загрузка и запас на сбои. Окно возврата всегда производно от окна забора и состава заказа: химчистка верхней одежды и стирка белья возвращаются в разные сроки.
Распределение партий по сроку. Самая содержательная часть системы. Очевидное решение «везти в ближайшую прачечную» работает плохо: ближняя площадка в загруженном районе может быть забита настолько, что заказ уйдёт в очередь и не успеет к обещанному окну, тогда как площадка подальше свободна и обработает партию сразу. Поэтому система сопоставляет текущую очередь и загрузку каждой площадки, её пропускную способность по конкретной услуге, фактическое среднее время обработки и оставшееся до закрытия время. Учитывается набор доступных услуг: не каждая площадка берёт химчистку кожи, обувь или ковры. Ещё система проверяет, попадает ли площадка в сегодняшний маршрут курьера, сколько даст объезд и какой запас остался до обещанного окна. Если дальняя площадка свободна и лежит по пути, партия уезжает туда. Если объезд ломает маршрут или не даёт выигрыша по времени, партия остаётся в ближней очереди. Тот же механизм защищает от перегрузки: диспетчер видит нарастающий затор в сводке очередей и перераспределяет поток заранее, до того как окна сорвутся.
Маршрут, который меняется на ходу. Маршрут здесь — последовательность визитов с временными ограничениями: обещанные окна, географическая близость, тип визита, вместимость сумки курьера, заезды в прачечные, длительность смены и перерывы. В течение дня появляются срочные заказы и отменяются старые, и маршрут пересчитывается. Отсюда отдельное требование к курьерскому приложению: принять новый порядок точек, не потеряв уже выполненные визиты и не заставляя курьера начинать смену заново.
Двухступенчатый биллинг. Точная стоимость в момент оформления неизвестна: стирка считается по фактическому весу, химчистка — по фактически принятым позициям. Поэтому сначала предварительная оценка и авторизация платёжного средства, затем пересчёт после регистрации позиций на площадке, согласование доплаты, если состав отличается от заявленного, и финальное списание после готовности. Платёжные операции идемпотентны: повторное подтверждение от провайдера не создаёт второе списание.
Проблемные вещи — отдельная ветка. Пятно, которое не выводится, повреждение, обнаруженное при приёме, вещь без ярлыка ухода. Такие позиции выводятся из основного потока в очередь согласования и либо возвращаются в цикл с другим режимом и ценой, либо возвращаются клиенту с объяснением.
Работа без связи. Курьер и площадка работают там, где сети может не быть. Критичные действия (забор, выдача, приём партии) фиксируются локально и досылаются, а сервер разрешает конфликты по времени события, а не по времени доставки данных.
Зафиксированные обязательства. Заказ хранит состав, обещанные окна и рассчитанную стоимость. Последующее изменение прайса, расписания или загрузки площадок не переписывает уже принятые обязательства. Каждый переход состояния фиксируется с отметкой времени, исполнителя и площадки, чтобы при разборе претензии точно знать, где находилась вещь.
Результат
Клиент видит простое приложение с несколькими экранами, за которыми скрыт весь логистический расчёт: ему предлагают только выполнимые окна и показывают укрупнённые этапы заказа. Курьер получает готовый маршрут, который переживает изменения в течение смены. Площадка отдаёт системе фактическое время обработки, и на нём держатся обещания окон и распределение новых партий.
Диспетчер видит сводку очередей и загрузок, отставание курьеров от графика и заказы, приближающиеся к обещанному сроку, и может вручную перераспределить точки. Пятнадцать сценариев, в которых физический процесс расходится с планом — от «клиента нет дома» до «площадка неожиданно встала», — переведены в явные состояния с ответственной ролью.