Форма бронирования является тонким клиентом к SaaS web-сервису tourservice.net. Формат взаимодействия клиента с сервером приложений - Ajax, формат данных - JSON. В большинстве случаев нет разницы - использовать POST или GET. Имейте ввиду, что при передаче GET-параметров есть ограничения браузера и сервера приложений на длину URL (5000 символов для сервера приложений).
Теория туров
Тур, потребляемый туристом, состоит из разнородной информации:
Ресурсы
Каждый продукт, потребленный туристами во время потребления тура, имеет свой ресурс и этот ресурс конечен (ограниченное количество мест в трансфере или самолете, ограниченное количество номеров нужного типа в отеле, ограниченное количество гидов-экскурсоводов и тп). Обладатель ресурса (им может быть как сам туроператор, так и реальный владелец - отельер, авиа- или авто-перевозчик) стремится использовать ресурс максимально, без простоя. Из этого стремления проистекает требование к программному обеспечению в планировании отсутствия простоев ресурса - отсутствие незанятых дней в "сетке" номеров отеля в сезон, отсутствие незанятых одиночных мест в трансфере или запрет нескольких полупустых автобусов (уплотнение). Следует отметить, что, в свою очередь, других участников процесса продажи, не являющихся обладателями ресурса, а лишь продающих его как посредники, это стремление не касается и они при отсутствии контроля со стороны обладателя могут тратить эти ресурсы как заблагорассудится, допуская простои.
Таким образом, если процесс продаж грамотно построен и приносит максимальный доход из-за отсутствия простоев, можно разделить степень ответственности, возлагаемой на разных участников процесса в формировании объема потребления ресурсов и одновременно с этим степень управляемости этими ресурсами. Чем ниже степень ответственности, тем ниже степень управляемости. На деле это выливается в недоступность точной информации о наличии ресурсов для тех участников, которые не являются обладателями ресурсов. Это не является необходимым требованием, но проистекает из ситуации, когда все управление ресурсами сконцентрировано в одних руках (обладателя).
Часто возникает ситуация, когда непосредственный владелец не в силах по разным причинам управлять степенью заполняемости ресурсов и готов за определенную плату делегировать эту задачу на следующего в цепочке участника процесса продаж (чаще всего им будет туроператор). Это несет в себе прибыль и одновременно нагрузку для туроператора и меняет его отношение к делегированным ресурсам. В конкретных действиях это выглядит так: туроператор с владельцем ресурса договариваются об объеме делегируемых ресурсов, туроператор оплачивает этот объем владельцу по сниженной цене (разница между этой ценой и рыночной является оплатой владельца за делегирование нагрузки) и с этой минуты туроператор начинает себя вести как владелец этого ресурса - пытается исключить простои и концентрирует доступ к управлению ресурсом в своих руках.
Таким образом, для одного и того же участника ресурс может быть как чужим, так и своим, причем даже в рамках одного типа ресурсов - например, определенный набор номеров в одном отеле на определенный срок может быть ему делегирован, а все остальные - нет. Называться при этом делегированный набор ресурсов будет жестким блоком, остальные ресурсы - мягким блоком. Название проистекает из неопределенности доступности ресурса - в случае мягкого блока туроператор не знает - свободен ресурс или нет, пока не обратится к владельцу, в случае же жесткого блока - он сам является распределителем ресурса и может выдать мгновенный точный ответ о наличии или отсутствии ресурса. Полноценное онлайн-бронирование подразумевает продажу ресурсов с исключительно жестким блоком мест, так задержка подтверждения брони отсутствует и отложенный отказ не предполагается. Это условие не всегда возможно выполнить, так как обладание жесткими блоками означает связывание больших денежных ресурсов (заблаговременная оплата всего блока до начала продаж).
Билеты
Билеты в бронировании являются метками, связывающими конкретный тур с конкретным ресурсом. Билеты используются для любого продукта - будь то отель, трансфер, авиаперелет, питание или экскурсия. Билеты могут потреблять ресурс или нет. Но нужно, чтобы хотя бы один билет в туре потреблял этот ресурс. Пример: Вместе с молодой мамой в самолете летит годовалый младенец у нее на руках. Он не потребляет отдельное кресло, но для него нужен билет, иначе с ним не пропустят в салон. Другой пример: в случае продажи номера отеля тратится всего один ресурс - номер отеля. В то же время в нем живет несколько человек, и их имена вписаны в ваучер, можно сказать - они все потребляют один номер, но у каждого свой билет (дискретность привязки туриста к ресурсу). Привязка туриста к номеру обязательна еще и в других случаях - если проданы два номера в рамках одного тура, то для некоторых стран незамужние мужчина и женщина до определенного возраста не могут быть поселены в один из этих номеров - именная билетная привязка к ресурсу обязательна.
В случае мягкого блока нет точной привязки тура к физическому ресурсу, потому что мягкий ресурс абстрактен, его реальный номерной фонд неизвестен. Например, в случае мягкого блока отеля точный набор доступных номеров не имеет смысла заранее хранить в базе продавца, важно лишь знать о возможных типах номеров, чтобы выставлять точную цену. Конкретный же свободный ресурс (номер комнаты) сообщит владелец ресурса в момент подтверждения брони. Поэтому мягкий блок позволяет не хранить конкретные номера комнат отеля в базе продавца мягкого блока, поскольку нет смысла отслеживать степень их занятости. Все равно неизвестно, проданы ли эти ресурсы кому-то еще или не могут быть отданы вам, чтобы избежать незанятых "дырок" по времени, простоев без дохода.
Билеты могут иметь статусы, которые отражают состояние брони ресурса в туре. Если бронируется мягкий блок, саму попытку бронирования надо отметить созданием билетов, но задать им неподтвержденный статус (ожидание подтверждения брони). Они имеют минимальную ценность в этом состоянии. После этого они могут быть либо уничтожены (бронь не подтверждена, ресурс недоступен), либо переведены в статус подтвержденных - здесь они приобретают вес. В этом случае турист может отказаться от них без обязательной оплаты (если это допустимо правилами хозяина ресурса) или понести штрафы. Турагент в случае штрафов является поручителем туриста и если он не удосужился заранее взять деньги с туриста, будет расплачиваться своими средствами за штрафы при отмене брони. При реальной оплате ресурса билеты переводятся в статус оплаченных и имеют наивысший приоритет. Поясняющий пример: забронированные билеты в трансфере без оплаты могут быть отменены за несколько дней до отъезда, если их покупателем является турист, а не агент (который, напомним, является поручителем туриста).
API просчета тура
1. Сначала получаем список доступных турпакетов (и их базовых параметров), выставленных на просчет тура:
{ "variants":[{ "a_currency":"KZT", "a_locations":"[\"Киргизия\",\"база Каракол\",\"Бишкек\",\"Каракол\"]", "a_sale2agents":1, "a_start_town":"Алматы", "a_hide4agents":1, "pid":11 },{ "a_currency":"KZT", "a_locations":"[\"Казахстан\",\"Акши\",\"Коктума\"]", "a_sale2agents":0, "a_start_town":"Алматы", "pid":31 }], "packets":{ 11: { name="ГОРНОЛЫЖНЫЙ СЕЗОН В КИРГИЗИИ", desc="горнолыжные базы, выезды по выходным. Включен трансфер, подъем на гору, проживание."}, 31: { name="Алаколь 2014", desc=""}} }
variants | |
---|---|
a_currency | Валюта турпакета |
a_locations | STRING для повторного парсинга JSON с перечнем локаций турпакета. Первый элемент - страна, остальные элементы - курорты (города) в ней, в которых находятся ресурсы турпакета. |
a_sale2agents | 1 - можно отображать кнопку продажи тура, 0 - нет, только для ознакомления (в начале сезона есть период, когда продажи еще не стартовали и доступны лишь предварительные цены, либо в силу каких-то обстоятельств возможна продажа лишь при бронировании по телефону) |
a_hide4agents | 1 - игнорировать пакет (только для внутреннего пользования), 0 или отсутствует ключ - пакет доступен для работы |
a_start_town | Город выезда (начала тура) |
pid | ID пакета |
packets | |
name | Название пакета |
desc | Необязательное описание пакета |
{ "variants":[{ "293":{ "pid":11 "t_program":"[\"Транспорт туда\",1,0,0],[\"Транспорт обратно\",2,0,0],[\"Подъем до горнолыжной базы\",3,0,0],[\"Размещение\",4,1,1]", "t_max_days":12 "t_min_days":3 } },{ "352":{ "t_program":"[\"Размещение\",0,1,1,0,0],[\"Питание\",0,0,0,0,0]", "pid":31 } }], "packets":{ 11: { name="ГОРНОЛЫЖНЫЙ СЕЗОН В КИРГИЗИИ", desc="горнолыжные базы, выезды по выходным. Включен трансфер, подъем на гору, проживание."}, 31: { name="Алаколь 2014", desc=""}} }
variants | |
---|---|
t_program |
STRING для повторного парсинга JSON с перечнем продуктов в туре. Первый элемент каждого подмассива - название продукта, второй - ID типа продолжительности, третий - позволить выбирать цены (номера в отеле) авторизованным пользователям (не агенту), четвертый - позволить выбирать цены (номера в отеле) агентам или туристам, пятый - показывать себестоимость агентам (редко).
Типы продолжительности продукта в туре:
|
t_min_days | Число минимального количества дней в туре. Необязательное. Если есть, не позволять в календаре выбирать дату конца тура меньше, чем за указанное количество дней от начала тура. |
t_min_days | Число максимального количества дней в туре. Необязательное. Если есть, не позволять в календаре выбирать дату конца тура больше, чем за указанное количество дней от начала тура. Если указано и равно 0, подставлять автоматически дату конца тура тем же днем, что и дату начала тура (туры выходного дня). |
pid | ID пакета |
packets | |
name | Название пакета |
desc | Необязательное описание пакета |
{ "variants":[ ["Alateniz HV","Акши","Казахстан","5","genfrom:1;per_baseclient:1"], ["Алаколь","Акши","Казахстан","2",""], ["Аласу","Акши","Казахстан","2","genfrom:1;nosale"], ["Асуан","Коктума","Казахстан","2",""], ["Варадеро","Акши","Казахстан","2","genfrom:1"], ["Варадеро","Акши","Казахстан","2",""], ["Грин Хаус","Акши","Казахстан","2",""], ["Зодиак","Коктума","Казахстан","2","genfrom:1"], ["Коктума","Коктума","Казахстан","2",""], ["Пеликан","Акши","Казахстан","2","genfrom:1"], ["Черный Камень","Акши","Казахстан","2","genfrom:1"] ], "dictname":"hotels" }
{ "variants":[ { "92": { "pid":"31", "p_cost_mode":"night", "p_dictid":"9", "p_resourcedictid":"0", "p_name":"Размещение", "offertype":"location", "p_pricedictid":"13", "p_cancellable_right":"ManageTickets" }, "90": { "pid":"31", "p_cost_mode":"client", "p_dictid":"97", "p_resourcedictid":"50", "p_name":"Транспорт туда", "offertype":"transfer_to", "p_pricedictid":"121" }, "91": { "pid":"31", "p_cost_mode":"client", "p_dictid":"97", "p_resourcedictid":"50", "p_name":"Транспорт обратно", "offertype":"transfer_from", "p_pricedictid":"121" } } ],"packets":{ 31: { name="Алаколь 2014", desc=""}} }
variants | |
---|---|
p_cost_mode |
Тип цены. Именно на форме бронирования используется лишь при отображении сетки номеров в отеле. На стороне сервера приложений используется как определяющий параметр для подсчета цены тура.
Типы цен:
|
p_dictid | Номер информационного справочника для этого продукта. Содержит базовую информацию об услуге (продукте) в туре. На форме бронирования не используется. На стороне сервера приложений используется для заполнения полей заявки нужными данными. |
p_resourcedictid | Номер справочника ресурсов для этого продукта. Содержит перечень ресурсов для каждого продукта. Широко используется на форме бронирования - от построения перечня дат выезда-приезда до построения списка автобусов в трансфере и сетки номеров в отеле. Ресурсы являются определяющими для возможности совершения тура и набора характеристик тура. Не все продукты обладают ресурсными справочниками (в этом случае их номер будет равен 0 или ключ будет отсутствовать вообще), в этом случае продукты считаются не требующими учета ресурсов (например, питание в отеле или экскурсии). |
p_pricedictid | Номер справочника цен для этого продукта. На форме бронирования не употребляется. На стороне сервера приложений является главным источником ценообразования. |
p_name | Название продукта. Уникально для каждого турпакета и может не совпадать с предполагаемым названием услуги с тем же типом. Например, в турах выходного дня продукт трансфера может называться "Автобус в обе стороны", в другом туре - "Трансфер туда". |
offertype |
Определяющий политику работы с продуктом тип услуги. Перечень доступных типов услуг и их пользовательское название:
|
p_cancellable_right | Право отмены выбора продукта. Если оно есть, туристу/агенту должно быть запрещено действие отказа от продукта в составе этого тура. |
pid | ID пакета |
packets | |
name | Название пакета |
desc | Необязательное описание пакета |
{ "variants":[ ["Азимут Трэвел",null,"2014-06-05"], ["Азимут Трэвел",null,"2014-06-12"], ["Азимут Трэвел",null,"2014-06-15"], ["Азимут Трэвел",null,"2014-06-19"], ["Азимут Трэвел",null,"2014-06-22"], ["Азимут Трэвел",null,"2014-06-24"], ["Азимут Трэвел",null,"2014-06-26"], ["Азимут Трэвел",null,"2014-06-30"], ["Азимут Трэвел",null,"2014-07-02"], ... ],"dictname":"int_transfer_res" }
{ "variants":[ { "placescheme":"", "number":"1", "block_start":"1", "floatable":"0", "rowid":"1939", "saled":"2", "dictid":"50", "block_end":"4", "reserved":"0", "available":"48" },{ "placescheme":"mersedes", "number":"2", "block_start":"1", "floatable":"0", "rowid":"2131", "saled":"4", "dictid":"50", "block_end":"4", "reserved":"16", "available":"51" } ], "dictname":"int_transfer_res" }
variants | |
---|---|
available | Количество мест жесткого блока ресурса (в данном случае - количество мест в автобусе). |
floatable | Количество мест жесткого блока ресурса (в случае автобусов не используется и равно 0). |
placescheme | Класс отображения, содержащий CSS-параметры прорисовки ресурса, также используется в javascript-коде для выдачи нужной последовательности мест. Нужен для точности отображения мест в реальном автобусе (двухэтажный, короткий и тп). Это поле также позволяет иметь разные схемы рассадки для автобусов с одинаковым количеством мест. CSS-схема доступна в файле http://hostname.tourservice.net/styles/voucher.css по совпадающему имени селектора. |
number | Номер ресурса (автобуса) для различения между собой в этот день. Часто на автобусах крепят соответствующие номера. |
reserved | Количество ранее забронированных мест в этом автобусе/ресурсе. |
saled | Количество ранее забронированных и оплаченных мест в этом автобусе/ресурсе. |
block_start | Стартовый номер блока мест, запрещенного агентом к продаже (места гидов, запасных водителей и тп) |
block_end | Конечный номер блока мест, запрещенного агентом к продаже (места гидов, запасных водителей и тп) |
rowid | ID ресурса. Требуется для запоминания выбранного ресурса. |
{ "variants":[ {"ticket":"7"}, {"ticket":"8"}, {"ticket":"5"}, {"ticket":"6"}, {"ticket":"9"}, {"ticket":"12"}, {"ticket":"11"}, ... ] }
10. После рассадки группы по местам ресурса (в этом документе не освещена задача выбора отельных ресурсов, как неактуальная на данный момент и ресурсоемкая), остается две задачи - просчет цены и оформление заявки. Для подачи запроса на просчет нужно сформировать cookie, содержащий кодированную через функцию javascript escape() структуру данных JSON-формата и сделать Ajax-вызов функции сервера приложений. Это формат продиктован особенностями построения оригинальной клиентской части. Пример cookie:
{ "query":{ "country":"Казахстан", "town":"Акши", "hotel":"Аласу", "date_from":"2014-06-05", "date_to":"2014-06-19", "packetid":"31", "adults":"1", "pensioners":"1", "childs":"2", "childs_ages":"5,8", "infants":"0", "start_town":"Алматы" }, "90":{ "selected":1, "rowid":"1939", "tickets":[24,23,24], "provider":"Азимут Трэвел" }, "91":{ "selected":1, "rowid":"2008", "tickets":[22,21,20], "provider":"Азимут Трэвел" }, "92":{ "selected":0, "roomname":0, "roomid":0, "tickets":[], "bindrid":0 } }
query | |
---|---|
country | Выбранная страна |
town | Выбранный город (курорт). Должен выбираться интерфейсом бронирования автоматически при выборе точки доставки/отеля (см. запрос перечня точек доставки (отелей)) |
hotel | Отель (точка доставки), выбранный туристом. |
date_from | Дата в формате ISO (YYYY-MM-DD), выбранная в качестве начала тура. Если у продукта, определяющего начало тура, есть справочник ресурсов, обязан быть ресурс на эту дату. |
date_to | Дата в формате ISO (YYYY-MM-DD), выбранная в качестве конца тура. Если у продукта, определяющего конец тура, есть справочник ресурсов, обязан быть ресурс на эту дату. |
packetid | ID турпакета. |
adults | Количество взрослых (включая пенсионеров!) |
pensioners | Количество пенсионеров из числа взрослых. |
childs | Число детей |
childs_ages | Перечень возрастов детей через запятую. Число возрастов должно соответствовать числу детей, иначе созданная позже заявка не забронируется. Если с туристом взрослого возраста будут младенцы, не требующие места, они указываются в ключе infants. |
infants | Число младенцев, не требующих места (обычно дети до двух лет, но это может быть переопределено настройками хоста или конкретного продукта (механизм может быть описан в этом документе позже)). |
start_town | Стартовый город, выбранный туристом. |
90 (ID продукта) | |
selected | Булевый ключ, определяющий - выбрал турист этот продукт к использованию или нет. Не может быть равно нулю, если продукт обязателен. |
rowid | ID выбранного к употреблению ресурса в этом продукте |
tickets | Массив номеров мест, выбранный туристом. Количество элементов должно быть равно количеству взрослых плюс количество детей (но не младенцев) |
provider | Владелец ресурса, полученный со списком ресурсов - для этого ресурса. |
{ "price":14000 }
<input name="c_name_0" value=""> <input name="c_nmeng_0" value=""> <input type="hidden" name="c_borned_0" value="1970-01-01">Здесь c_name_0 - это фио туриста, желательно полностью, c_nmeng_0 - это ФИО туриста в транслитерации на латиннице, c_borned_0 - это дата рождения туриста в формате ISO. Дата обязательна, так как сочетание ФИО и даты рождения позволяет вычислить уникальность туриста в рамках БД и избежать ненужных коллизий. Но, принимая во внимание, что во внутреннем туризме категорически не принято узнавать дату рождения, дата может быть фиктивной, лучше всего 1970-01-01 (0 секунд по UNIX-времени) - это условная дата, ее постоянство будет указывать на фиктивность.
1. Для бронирования ресурсов в заявке нужно выполнить всего один авторизованный GET-запрос, при условии, что заявка уже была создана на стороне сервера в предыдущей главе: