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