Общая информация

Используемые понятия

  • Остановка - автовокзал/город/населенный пункт/произвольная точка на карте (поворот на трассе). Пример - Воронеж, АВ Центральный Воронеж, поворот на Лосево и т.д;
  • Маршрут - последовательность остановок, по которому едет автобус.

Обычно обозначается как "<Начальная остановка> - <Конечная остановка> <номер>", иногда используется комментарий по промежуточным остановкам.

Пример - "Старый Оскол АВ — Обоянь АС 2859", "Рязань ЦАВ - Скопин(ч/з Захарово)"

  • Регулярный рейс - расписание, по которому выполняет рейс конкретный перевозчик по конкретному маршруту.

Задается как совокупность маршрута, времени отправления, перевозчика и расписания.

Пример - "Рязань ЦАВ - Кораблино, перевозчbки АО Кораблинское АТП, отправление в 11-30, регулярность - ежедневно"

  • Рейс - рейс на конкретную дату регулярного рейса.

Пример - "Рязань ЦАВ - Кораблино, перевозчки АО Кораблинское АТП, отправление в 12.01.2023 в 11-30".

Имеет все параметры регулярного рейса, плюс - цена взрослого/детского/багажного, кол-во свободных мест, схема мест и т.д

  • Заказ - оформленный заказ клиента. Может включать в себя один или несколько услуг. Оплачивается всегда комплексно;
  • Элемент заказа - позиция заказа. Например, поездка Воронеж - Москва. Одна позиция заказа (поездка) может включать в себе несколько билетов (пассажиров). Заказ разбит на позиции, т.к. может включать в себя несколько поездок ( например, обратные билеты), которые могут быть совершенно разные по составу.
  • Билет - конкретный билет с номером места для одного пассажира.
  • Корзина - по аналогии - заказ, только не оформленный;
  • Позиция корзины - по аналогии с позицией заказа, только не оформленный.

Технические вводные

Приложение будет взаимодействовать с беком по REST API протоколу. Авторизация через токен в заголовках

Пример:

``` GET // Authorization: Device-Token <токен автозации> Content-Type: application/json Connection: keep-alive

{ "readonly" : false } ```

Общий формат

  • GET запрос на /app/<сущность>/ - получение списка;
  • POST запрос на /app/<сущность>/ - создание объекта;
  • GET запрос на /app/<сущность>// - получение объекта;
  • POST/GET запрос на /app/<сущность>/<действие>/ - выполнение общих действий с сущностью;
  • POST/GET запрос на /app/<сущность>//<действие>/ - выполнение действий над конкретным объектом;

Общий формат получения списков и пагинации:

http response HTTP 200 OK { "count": 10, "next": "https://api.example.org/accounts/?page=5", "previous": "https://api.example.org/accounts/?page=3", "results": [ … ] }

Для бека

Завести отдельное приложение app. Все взаимодействие - через DRF вьюхи.

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

Формат ошибок

Возможные коды ошибок:

  • 403 - неавторизованный запрос;
  • 404 - в случае получения несуществующего объекта (сообщение показывается пользователю);
  • 400 - в случае получения осмысленной ошибки от сервера, пользователю показывается;
  • 500 - если произойдет неожиданная ошибка сервера, пользователю не показывается (выводится общая ошибка, типа "Возникла ошибка на сервере").

Формат тела ответа - всегда присутствует message и exception_type.

Примеры:

403:

json { "message": "Учетные данные не были предоставлены.", "code": "not_authenticated", "exception_type": "NotAuthenticated" }

404:

json { "exception_type": "Http404", "message": "No Report matches the given query." }

404:

json { "message": "Выбранный отчет - комплексный. Получение данных отчета невозможно.", "code": 400, "exception_type": "APIException" }

500:

json { "exception_type": "ZeroDivisionError", "message": "division by zero" }

Для бека

Создать эксепшен для приложения от APIException. При возникновении осмысленных ошибок вызывать его.

Авторизация приложения

При первом запуске, приложение запрашивает токен доступа с сервера и отправляет информацию об устройстве.

Далее сохраняет этот токен в локальном хранилище.

Все дальнейшие запросы должно быть только авторизованные (с токеном в заголовках).

Если приложение отвечает кодом 403, необходимо заново запросить токен

http request POST /app/device/auth/ { "device_id": "<Уникальный идентификатор устройства>", "device_os": "<1 - android, 2 - ios>", "firebase_id": "<При наличии>", "device_data": { // Все остальные данные по устройству по формате JSON } }

Ответ

json { "device_token": "" // токен для авторизации в остальных запросах }

Этот токен необходимо в дальнейшем использовать в заголовках Device-Token во всех запросах.

Для бека

Завести модель Device, сохранять данные по нему, авторизовывать и выдавать токен.

Получение настроек приложения

ЭП для получения основных настроек приложения. Запрашивается до показа первого экрана.

http request GET /app/config/

json { "background_image": "https://<картинка на главном экране>" // Визуальные и прочие настройки приложения }

Для бека

Завести синглтон модель AppSettings.

Загрузка справочников

Справочники загружать/обновлять при старте приложения в фоновом режиме

Типы документов

http request GET /app/type-of-document/

Ответ

json { "count": 22, "next": null, "previous": null, "results": [ { "id": 101, "available_range_age": { // Достпуный возвраст для типа документов "lower": 14, "upper": null, "bounds": "[)" }, "title": "Паспорт гражданина РФ", "is_active": true, "use_default": true, // Использовать по умолчанию "mask": "^\\d{10}$", // маска ввода "mask_description": "10 цифр без разделителей", // комментарий к маске "mask_example": "1234567890" // пример }, { "id": 110, "available_range_age": { "lower": null, "upper": null, "bounds": "()" }, "title": "Свидетельство о рождении иностранного гражданина", "is_active": true, "use_default": false, "mask": null, "mask_description": null, "mask_example": null } ] }

Страны (гражданства)

http request GET /app/country/

http response HTTP 200 OK { // ... "results": [ { "id": 2, "title": "Казахстанг", "use_default": false } ] }

Поиск рейсов

Получение списка отправлений:

http request GET /app/dispatch/q=Мих

http response HTTP 200 OK { // ... "results": [ { "id" : "mikhailov", "title" : "Михайлов", } ] }

В параметре q передается строка, вводимая пользователем.

Получение списка прибытий:

Список прибытий зависит от ID станции отправления. Без пункта отправления будет выдана ошибка.

http request GET /app/arrival/q=Вор&from_id=mikhailov

json { // ... "results": [ { "id": "voronezh", "title": "Воронеж" } ] }

В параметре q передается строка, вводимая пользователем.

Запрос календаря рейсов

http request GET /app/routes/calendar/?dispatch_id=mikhailov&arrival_id=voronezh

json { // ... "results": [ { "id": "11.10.2023", "price": 1267.3 }, { "id": "12.10.2023", "price": 1265.3 } ] }

Для бека - использовать алгоритм из виджета соседних дат.

Запрос расписаний

Расписания бывает 2 видов - только с пунктом отправления (общее расписание АВ) и с пунктом прибытия (расписание по конкретному маршруту)

Общее расписание:

http request GET /app/routes/?dispatch_id=mikhailov

json { // ... "results": [ { "route": "Старый Оскол АВ - Воронеж АВ 2871", "schedule": "Пн, Вт, Ср, Чт, Вс", "start_plan": { "15:15": { "id": "<ID регулярного рейса" // Информация по регулярному рейсу } } } ] }

Расписание по маршруту

http request GET /app/routes/?dispatch_id=mikhailov&arrival_id=voronezh

json { "table_trips": [ // Список рейсов { "start": "05:20:00", // Время отправления "point_a": 7470, // ID пункта отправления "trip_id": "3375", // ID рейсы "route": "Старый Оскол АВ — Воронеж АВ 1349", // Название маршрута "end": "07:20:00", // Время прибытия "point_b": 7317, // ID пункта прибытия "free_place": null, // Кол-во свободных мест "distance": 121, // Дистанция в км; "free_places": null, // Массив свободных мест "price_full": 371, // Цена полный "price_child": 0, // Цена детский "price_baggage": 0, // Цена багажный "price_insurance": 0, // Цена страховки "bus_info": null, // Информация по автобусу "number": null, // Номер маршрута "travel_time": 120, // Время в пути (в минутах) "travel_time_human": " 2 ч. ", // Время в пути (человеко-читаемое) "carrier": "Платонов Д.Б.", // Перевозчик "schedule": "Ежедневно", // Расписание "platform": null, // Номер платформы "without_pd": false, // Возможно продажа без персональных данных "without_select_places": false, // Не доступен выбор мест "status": null, // Статус рейса "sale_status": null, // Статус продажи "available_for_sale": false, // Рейс доступен для продажи "commission": 1 // ID Комиссии } ], "aggregate_table": { // Сводная информация по рейса (самый быстрый, самый дешевый, средняя продолжительность и цена и т.д "fastest_trip": { "start": "19:30:00", "point_a": 7470, "trip_id": "3390", "route": "Старый Оскол АВ-Москва АВ \"Саларьево\" 5488", "concreate": false, "end": "20:30:00", "point_b": 7317, "free_place": null, "distance": 150, "free_places": null, "price_full": 0, "price_child": 0, "price_baggage": 0, "price_insurance": 0, "bus_info": null, "number": null, "travel_time": 60, "travel_time_human": " 1 ч. ", "carrier": "Лаврусик В.И", "schedule": "Вс", "platform": null, "without_pd": false, "without_select_places": false, "status": null, "sale_status": null, "available_for_sale": false, "station_for_sale": null, "commission": 1 }, "cheapest_trip": { "start": "05:55:00", "point_a": 7470, "trip_id": "3362", "route": "Старый Оскол -Воронеж 3979", "concreate": false, "end": "07:55:00", "point_b": 7317, "free_place": null, "distance": 121, "free_places": null, "price_full": 250, "price_child": 0, "price_baggage": 0, "price_insurance": 0, "bus_info": null, "number": null, "travel_time": 120, "travel_time_human": " 2 ч. ", "carrier": "Сафонов Е.Н.", "schedule": "Пт", "platform": null, "without_pd": false, "without_select_places": false, "status": null, "sale_status": null, "available_for_sale": false, "station_for_sale": null, "commission": 1 }, "avg_travel_time": " 1 ч. 56 мин. ", "avg_distance": 122, "avg_price": 355.806451612903, "min_price": 275 } }

Запрос рейсов на дату

http request GET /app/trips/?dispatch_id=mikhailov&arrival_id=voronezh&trip_date=16.10.2023

json { "trips": [ { "point_a": 7470, "start": "2023-10-19T05:35:00+03:00", "trip_id": "1250357", "route": "Старый Оскол-Белгород 515", "concreate": true, "end": "2023-10-19T07:55:00+03:00", "point_b": 7292, "free_place": 8, "distance": 148, "free_places": [ 2, 3, 4, 6, 7, 8, 9, 10 ], "price_full": 345, "price_child": 172, "price_baggage": 34, "price_insurance": 0, "bus_info": "Mercedes 0303 (17+0багаж)", "number": "515", "travel_time": 140, "travel_time_human": " 2 ч. 20 мин. ", "carrier": "Гриднев Е.Е.", "schedule": "Пн, Вт, Ср, Чт, Пт", "platform": "2", "without_pd": true, "without_select_places": false, "status": null, "sale_status": null, "available_for_sale": false, "commission": 1 } ], "fastest_trip": { "point_a": 7470, "start": "2023-10-19T15:20:00+03:00", "trip_id": "1249945", "route": "Воронеж АВ — Белгород АВ 2199", "concreate": true, "end": "2023-10-19T17:15:00+03:00", "point_b": 7292, "free_place": 3, "distance": 280, "free_places": [ 15, 18, 19 ], "price_full": 350, "price_child": 175, "price_baggage": 35, "price_insurance": 0, "bus_info": "FOXBUS (31)", "number": "2199", "travel_time": 115, "travel_time_human": " 1 ч. 55 мин. ", "carrier": "Федотова Е.П.", "schedule": "Ежедневно", "platform": "0", "without_pd": true, "without_select_places": true, "status": null, "sale_status": null, "available_for_sale": false, "commission": 1 }, "cheapest_trip": { "point_a": 7470, "start": "2023-10-19T14:28:00+03:00", "trip_id": "1249998", "route": "Воронеж АВ — Белгород АВ 565", "concreate": true, "end": "2023-10-19T16:30:00+03:00", "point_b": 7292, "free_place": 7, "distance": 280, "free_places": [ 1, 2, 3, 4, 5, 6, 7 ], "price_full": 278.5, "price_child": 139, "price_baggage": 27, "price_insurance": 0, "bus_info": "Setra 315HD(2) (57)", "number": "565", "travel_time": 122, "travel_time_human": " 2 ч. 2 мин. ", "carrier": "Капустин Ю.В.", "schedule": "Ежедневно", "platform": "0", "without_pd": true, "without_select_places": true, "status": null, "sale_status": null, "available_for_sale": false, "commission": 1 }, "avg_travel_time": " 2 ч. 15 мин. ", "avg_distance": 171, "avg_price": 398.3333333333333, "min_price": 307 }

Добавление поездки в корзину

```http request POST /app/cart/add-to-cart/

{ // Полный объект рейса "point_a": 7470, "start": "2023-10-26T09:15:00+03:00", "trip_id": "1255954", "route": "Старый Оскол -Воронеж 2325", "concreate": true, "end": "2023-10-26T11:10:00+03:00", "point_b": 7317, "free_place": 19, "distance": 120, "free_places": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ], "price_full": 200, "price_child": 100, "price_baggage": 60, "price_insurance": 0, "bus_info": "Iveco (20)", "number": "2325", "travel_time": 115, "travel_time_human": " 1 ч. 55 мин. ", "carrier": "Исайкин Д. А.", "schedule": "Чт, Пт", "platform": "3", "without_pd": false, "without_select_places": false, "status": null, "sale_status": null, "available_for_sale": false, "commission": 1 } ```

В ответ - полный объект корзины

Удаление поездки из корзины

```http request DELETE /app/cart/remove-from-cart/

{ "cart_item_id" : 123213 } ```

Действия в корзине

Получение текущей корзины пользователя

http request GET /app/cart/current/

Ответ

json { "uuid": "08fa8bef-f9c2-426d-8726-b7c3a5f942ff", "cart_items": [ { "id": 47531, "trip": { "id": 47544, "tickets": [ { "id": 54002, "external_id": null, "created_at": "2023-10-17T19:01:04.374829+03:00", "updated_at": "2023-10-17T19:01:04.374829+03:00", "title": "Билет по маршруту Короча АС -Москва \" Саларьево\" АВ 5842 на дату 20.10.2023 09:00", "is_active": true, "date": null, "date_reserve": null, "ticket_number": null, "place_number": "14", "is_stand": false, "is_child": false, "barcode": null, "is_return": false, "commission_value": "0.00", "status": 1, "price": "440.00", "price_base": "0.00", "price_baggage": "0.00", "price_baggage_base": "0.00", "sum_baggage": "0.00", "sum_baggage_base": "0.00", "count_baggage": 0, "external_id_baggage": [], "sum_all": "0.00", "sum_insurance": "0.00", "uuid": "31dd8a00-9460-45a9-9530-5051fd8c943e", "company": 12, "passenger": null } ], "point_a": "Старый Оскол АВ", "point_b": "Воронеж Юго-Западная АС", "external_id": "1250916", "created_at": "2023-10-17T19:01:04.350885+03:00", "updated_at": "2023-10-17T19:01:04.350885+03:00", "title": "Короча АС -Москва \" Саларьево\" АВ 5842 на дату 20.10.2023 09:00", "is_active": true, "trip_id": "1250916", "price_full": "440.00", "price_full_base": "400.00", "price_child": "165.00", "price_child_base": "150.00", "price_baggage": "33.00", "price_baggage_base": "30.00", "price_insurance": "0.00", "start": "2023-10-20T09:00:00+03:00", "end": "2023-10-20T10:40:00+03:00", "route": "Короча АС -Москва \" Саларьево\" АВ 5842", "platform": null, "free_place": 10, "free_place_baggage": "0", "free_places": [ "14", "17", "21", "22", "23", "24", "27", "28", "31", "32" ], "travel_time": 100, "without_select_places": false, "carrier": "Аведян А. А.", "commission": 1 }, "count": 0, "price": "0.00", "sum_all": "0.00" } ] }

Пояснения по полям:

  • cart_items - позиции корзины;
  • trip - полная информация о поездке;
  • tickets - билеты заказа;

Вычисление суммовых показателей заказа (общая сумма, кол-во пассажирских, детских и багажных) реализовать на клиенте (из информации о поездке).

Добавление билета в заказ

Отправить только идентификатор позиции корзины

```http request POST /app/cart/add-ticket/

{ "cart_item_id" : 123123 } ```

В случае успеха, в ответ - полный объект корзины

Удаление билета из заказа

```http request POST /app/cart/remove_ticket/

{ "cart_id" : 123123, "ticket_id" : 123123 } ```

В случае успеха, в ответ - полный объект корзины.

Отправка заказа

```http request POST /app/order/

{ "buyer_emai" : 123123, "buyer_phone" : 123123, "items": { "" : { "" : { "citizenship" : "123", // Гражданство "type_document": "101", // Тип документа "seria_number": "101", // Серия номер документа "last_name": "Иванов", // ФИО "first_name": "Петр", "second_name": "Геннадьевич", "sex": "1", // Пол, 1 - мужской, 2 - женский "date_of_birthday": "12.12.2001", // Дата рождения "place_number": "1", // Дата рождения "сount_baggage": 1, // Кол-во багажа } } } } ```

При успешно оформлении - полный объект заказа и ссылка на оплату

Гид