<< назад CMS сайта продажи авиабилетов.
На этой страничке я расскажу об одном из самых сложных в моей жизни завершенных проектов, которые я поднял с нуля, от начала до конца. Который несколько лет успешно работал, а потом по непонятной причине был закрыт. Ну как бы под предлогом, что владельцу этого бизнеса тяжело платить копеечную сумму за хостинг этого проекта. Что было на самом деле - я не знаю, возможно опять возрос рейтинг Солнцеликого Хуепутина, а как известно каждый процент роста рейтинга приводит к 10% обнищанию населения, и обнищавшее население просто перестало покупать авиабилеты для отпуска в жарких странах. Может появился рядом какой-то конкурент и владелец вообще решил свернуть свой бизнес. Непонятно... Но я расскажу о своем - о проекте, над которым я долго ломал голову, и который работал очень успешно.
Во первых, у меня сохранился календарный план работы, который я составлял в марте 2012-го года, когда проект с одной стороны уже прекрасно работал, а с другой стороны было миллион желаний и у меня и у заказчика - развивать это проект дальше по множеству направлений.
Этот проект был примерно третий по сложности в моей жизни, поэтому я хотел бы просто для начала оценить объем кода только на формах. Это почти сто тысяч строк кода только непосредственно в проекте VS2010. Это 720 файлов, 5388 классов. Безусловно, тут есть какая-то часть автоматически сгенерированного кода, но видно что это не проект на три строчки. Количество форм этого проекта тоже немаленькое - вы можете увидеть их справа
Еще одна характеристика этого проекта - это DBML (именованные кеши в памяти, предназначенные для обмена данными с SQL-сервером. Вот взгляните на них.
Кроме того, для этого проекта очень много кода было написано не на Бейсике, а на FLEX-AIR. Ниже вы можете увидеть пример служебной программы - парсера, который я написал на AIR.
Теперь вкратце пробежимся по некоторым формам проекта, и я коротенько опишу смысл этого проекта. Все начинается со справочников партнеров. Авиакомпаний, поставщиков билетов, дилеров данной торговой компании, которые перепродают в розницу билеты, закупленные этой торговой фирмой оптом.
Далее существует справочник направлений, по которым данная фирма торгует билетами. То есть для незалогиненного юзера при заказе при выборе страны будут отображаются не сто стран, а обычно не более 5-10 стран, причем этот список меняется в зависимости от сезона, в стране отображаются тоже не все аэропорты, а только заведомо выбранные.
Далее существуют парсеры билетов авиакомпаний и крупных фирм, типа интуриста. У этих авиакомпаний можно выкупить билеты оптом. Один из парсеров, написанных на AIR, вы видели выше. Парсеров существует много, и они работают как вручную, так и сами по себе, в автоматическом режиме, запущенные по планировщику заданий.
В этом сайте многое работает автоматически, по планировщику заданий, например считывание курса валют.
Есть журнал работы сайта, по которому видно, что и когда было считано в автоматическом режиме. Есть журнал, какими билетами интересовались клиенты сайта, что позволяет оперативно реагировать на конъюнктуру.
Кроме того, билеты могут быть загружены вручную, ну например менеджер о чем-то с кем-то договорился, выкупил что-то, берет горячее направление и загружает свои 10 билетов в общую базу. Либо в пакете, экспортировав свой список из Excel, либо просто по одному билету.
Таким образом формируется общая база билетов.
В случае переноса рейсов, отмены и так далее, билеты могут быть скорректированы по любому параметру, вплоть до уже выкупленного билета.
Есть всякие контроли, например если запущенный парсер занес в базу билеты без цен (такое бывает у авиакомпаний). Такие билеты отбираются и менеджер должен определится с ценой.
Далее производится наценка на билеты. Она может быть разными способами сформирована, в долларах, в евро. Наценка, конечно зависит от поставщика, авиакомпании, направления.
Собственно с билетами все тоже не так просто. Есть билеты одиночные, есть двойные (туда-обратно). Цена по разному формируется всякий раз.
Кроме того, есть спецпредложения, эти билеты в списке отборов показываются всегда выше и красным.
Из общей базы билетов, можно всегда удалить билеты по разным критериям.
Есть база заказов билетов, сделанных клиентами. В этой базе менеджеры ведут все состояние заказа, отсюда же печатается электронный билет электронной регистрации, и также вносятся всякие корректировки типа переноса рейса.
Есть база туристов.
И вот здесь, стоп. Самый важный аспект этого сайта. Это не одиночный сайт, а центр управления дилерскими агентствами (кассами) и мелкими сайтами, которые продают билеты, выкупленные менеджерами этой фирмы. Подробнее о системе интеграции с дилерами можно посмотреть на страничке B2B-Сервисы с криптографическим залогиниванием (для клиентской и серверной интеграции).
В этом сайте есть еще много-много чего, например система управления рекламным контентом, которые Менеджеры выставляют на титульной странице сайта.
Неожиданно сложным для программирования оказался компонент сайта, который бы позволял предложить клиентам билеты со сдвижкой по дате (+/- три дня).
В этой системе сотни крученых алгоритмов, на отладку которых была убита куча сил, ну вот для примера алгоритм склеивания прямого и обратного билетов. 1: Imports Microsoft.VisualBasic 2:
3: Public Class ReturnTicket 4:
5: 'поиск обратного билета - на входе два рекордсета - прямые билеты и обратные 6: 'Dim FromTicket As System.Collections.Generic.List(Of DB.GetTicketResult) = db1.GetTicket(SessionTicketRequest.FromCountry, SessionTicketRequest.FromCity, SessionTicketRequest.ToCountry, SessionTicketRequest.ToCity, FromDate_).ToList 7: 'Dim ToTicket As System.Collections.Generic.List(Of DB.GetTicketResult) = db2.GetTicket(SessionTicketRequest.ToCountry, SessionTicketRequest.ToCity, SessionTicketRequest.FromCountry, SessionTicketRequest.FromCity, ToDate_).ToList 8: 'предполагается что по странам и датам все уже подобрано (еще на этапе отбора из SQL) 9: ' 10:
11: Public Function CheckReturnTicket(ByVal FromTicket As System.Collections.Generic.List(Of DB.GetTicketResult), ByVal ToTicket As System.Collections.Generic.List(Of DB.GetTicketResult)) As System.Collections.Generic.List(Of OneTicketWithReturn) 12: Dim ContinueSearch As Boolean = False 13: Dim _CheckReturnTicket = New System.Collections.Generic.List(Of OneTicketWithReturn) 14: 'до первого шага - вдруг обратный билет УЖЕ пришел в параметрах и требуется не искать, а только зацепить билеты в блок 15: If ToTicket IsNot Nothing And FromTicket IsNot Nothing Then 16: If ToTicket.Count > 0 And FromTicket.Count > 0 Then 17: If ToTicket.Count = 1 Then 18: For i As Integer = 0 To FromTicket.Count - 1 19: 'альтернатив нет - добавляем единственное что есть 20: _CheckReturnTicket.Add(New OneTicketWithReturn(FromTicket(i), ToTicket(0))) 21: Next 22: Return _CheckReturnTicket 23: End If 24: End If 25: End If 26: 'первый шаг - совпадает AviaCompanyCode, Supplier 27: For i As Integer = 0 To FromTicket.Count - 1 28: For j As Integer = 0 To ToTicket.Count - 1 29: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 30: FromTicket(i).Supplier = ToTicket(j).Supplier And 31: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 32: 'добавляем найденный обратный билет 33: _CheckReturnTicket.Add(New OneTicketWithReturn(FromTicket(i), ToTicket(j))) 34: GoTo Next1 35: End If 36: Next 37: 'добавили прямой без обратного 38: ContinueSearch = True 39: _CheckReturnTicket.Add(New OneTicketWithReturn(FromTicket(i))) 40: Next1:
41: Next 42: 'второй шаг - совпадает AviaCompanyCode 43: If ContinueSearch Then 44: For i As Integer = 0 To FromTicket.Count - 1 45: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).Toticket Is Nothing Then 46: For j As Integer = 0 To ToTicket.Count - 1 47: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 48: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 49: _CheckReturnTicket(i).ToTicket = ToTicket(j)
50: GoTo Next2 51: End If 52: Next 53: End If 54: Next2:
55: Next 56: End If 57: 'третий шаг - совпадает Supplier 58: If ContinueSearch Then 59: For i As Integer = 0 To FromTicket.Count - 1 60: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).Toticket Is Nothing Then 61: For j As Integer = 0 To ToTicket.Count - 1 62: If FromTicket(i).Supplier = ToTicket(j).Supplier And 63: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 64: _CheckReturnTicket(i).ToTicket = ToTicket(j)
65: GoTo Next3 66: End If 67: Next 68: End If 69: Next3:
70: Next 71: End If 72: 'третий шаг - любой билет 73: If ContinueSearch Then 74: For i As Integer = 0 To FromTicket.Count - 1 75: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).Toticket Is Nothing Then 76: For j As Integer = 0 To ToTicket.Count - 1 77: If FromTicket(i).FromDate <= ToTicket(j).FromDate Then 78: _CheckReturnTicket(i).ToTicket = ToTicket(j)
79: GoTo Next4 80: End If 81: Next 82: End If 83: Next4:
84: Next 85: End If 86: Return _CheckReturnTicket 87: End Function 88:
89:
90: Public Function CheckReturnTicketLESS(ByVal FromTicket As System.Collections.Generic.List(Of DB.GetTicketLESSResult), ByVal ToTicket As System.Collections.Generic.List(Of DB.GetTicketResult)) As System.Collections.Generic.List(Of OneLESSTicketWithReturn) 91: Dim ContinueSearch As Boolean = False 92: Dim _CheckReturnTicket = New System.Collections.Generic.List(Of OneLESSTicketWithReturn) 93: 'до первого шага - вдруг обратный билет УЖЕ пришел в параметрах и требуется не искать, а только зацепить билеты в блок 94: If ToTicket IsNot Nothing And FromTicket IsNot Nothing Then 95: If ToTicket.Count > 0 And FromTicket.Count > 0 Then 96: If ToTicket.Count = 1 Then 97: For i As Integer = 0 To FromTicket.Count - 1 98: 'альтернатив нет - добавляем единственное что есть 99: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i), ToTicket(0))) 100: Next 101: ContinueSearch = True 102: Return _CheckReturnTicket 103: End If 104: End If 105: End If 106: 'первый шаг - совпадает AviaCompanyCode, Supplier 107: For i As Integer = 0 To FromTicket.Count - 1 108: For j As Integer = 0 To ToTicket.Count - 1 109: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 110: FromTicket(i).Supplier = ToTicket(j).Supplier And 111: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 112: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i), ToTicket(j))) 113: GoTo Next1 114: End If 115: Next 116: 'добавили прямой без обратного 117: ContinueSearch = True 118: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i))) 119: Next1:
120: Next 121: 'второй шаг - совпадает AviaCompanyCode 122: If ContinueSearch Then 123: For i As Integer = 0 To FromTicket.Count - 1 124: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 125: For j As Integer = 0 To ToTicket.Count - 1 126: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 127: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 128: _CheckReturnTicket(i).ToTicket = ToTicket(j)
129: GoTo Next2 130: End If 131: Next 132: End If 133: Next2:
134: Next 135: End If 136: 'третий шаг - совпадает Supplier 137: If ContinueSearch Then 138: For i As Integer = 0 To FromTicket.Count - 1 139: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 140: For j As Integer = 0 To ToTicket.Count - 1 141: If FromTicket(i).Supplier = ToTicket(j).Supplier And 142: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 143: _CheckReturnTicket(i).ToTicket = ToTicket(j)
144: GoTo Next3 145: End If 146: Next 147: End If 148: Next3:
149: Next 150: End If 151: 'третий шаг - любой билет 152: If ContinueSearch Then 153: For i As Integer = 0 To FromTicket.Count - 1 154: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 155: For j As Integer = 0 To ToTicket.Count - 1 156: If FromTicket(i).FromDate <= ToTicket(j).FromDate Then 157: _CheckReturnTicket(i).ToTicket = ToTicket(j)
158: GoTo Next4 159: End If 160: Next 161: End If 162: Next4:
163: Next 164: End If 165: Return _CheckReturnTicket 166: End Function 167:
168:
169: ' 170: 'В отличии от предыдущей функции - проходит только по тем билетам, у которых нет обратно и подтягивает новые билеты из базы 171: ' 172: Public Function CheckReturnTicketLESS_1(ByVal TicketWithReturn As System.Collections.Generic.List(Of OneLESSTicketWithReturn)) As System.Collections.Generic.List(Of OneLESSTicketWithReturn) 173: 'первый шаг - совпадает AviaCompanyCode, Supplier 174: Dim db1 = New DB.FlySeasonDataContext, RetTicket 'As System.Collections.Generic.IList(Of DB.GetTicketLESSResult) 175: For i As Integer = 0 To TicketWithReturn.Count - 1 176: If TicketWithReturn(i).ToTicket Is Nothing Then 177: 'у этого прямого билета нет обратного билета 178: RetTicket = db1.GetTicketLESS(TicketWithReturn(i).FromTicket.ToCountry, TicketWithReturn(i).FromTicket.ToCity, TicketWithReturn(i).FromTicket.FromCountry, TicketWithReturn(i).FromTicket.FromCity, TicketWithReturn(i).FromTicket.FromDate).ToList()
179: If RetTicket IsNot Nothing Then 180: If RetTicket.Count > 0 Then 181: If RetTicket(0).FromDate > TicketWithReturn(i).FromTicket.FromDate Then 182: TicketWithReturn(i).SetToTicket(RetTicket(0))
183: End If 184: End If 185: End If 186: End If 187: Next 188: Return TicketWithReturn 189: End Function 190:
191:
192: ' 193: 'В отличии от предыдущей функции - проходит только по тем билетам, у которых нет обратно и подтягивает новые билеты из базы 194: ' 195: Public Function CheckReturnTicketLESS_2(ByVal FromTicket As System.Collections.Generic.List(Of DB.GetTicketLESSResult), ByVal ToDate As DateTime) As System.Collections.Generic.List(Of OneLESSTicketWithReturn) 196: Dim _CheckReturnTicket = New System.Collections.Generic.List(Of OneLESSTicketWithReturn) 197: Dim db1 = New DB.FlySeasonDataContext, RetTicket As System.Collections.Generic.List(Of DB.GetTicketLESSResult) 198: For i As Integer = 0 To FromTicket.Count - 1 199: 'у этого прямого билета нет обратного билета 200: RetTicket = db1.GetTicketLESS(FromTicket(i).ToCountry, FromTicket(i).ToCity, FromTicket(i).FromCountry, FromTicket(i).FromCity, ToDate).ToList()
201: If RetTicket IsNot Nothing Then 202: If RetTicket.Count > 0 Then 203: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i), RetTicket(0))) 204: Else 205: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i))) 206: End If 207: Else 208: _CheckReturnTicket.Add(New OneLESSTicketWithReturn(FromTicket(i))) 209: End If 210: Next 211: Return _CheckReturnTicket 212: End Function 213:
214:
215: Public Function CheckReturnTicketMORE(ByVal FromTicket As System.Collections.Generic.List(Of DB.GetTicketMOREResult), ByVal ToTicket As System.Collections.Generic.List(Of DB.GetTicketResult)) As System.Collections.Generic.List(Of OneMORETicketWithReturn) 216: Dim ContinueSearch As Boolean = False 217: Dim _CheckReturnTicket = New System.Collections.Generic.List(Of OneMORETicketWithReturn) 218: 'до первого шага - вдруг обратный билет УЖЕ пришел в параметрах и требуется не искать, а только зацепить билеты в блок 219: If ToTicket IsNot Nothing And FromTicket IsNot Nothing Then 220: If ToTicket.Count > 0 And FromTicket.Count > 0 Then 221: If ToTicket.Count = 1 Then 222: For i As Integer = 0 To FromTicket.Count - 1 223: 'альтернатив нет - добавляем единственное что есть 224: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i), ToTicket(0))) 225: Next 226: Return _CheckReturnTicket 227: End If 228: End If 229: End If 230: 'первый шаг - совпадает AviaCompanyCode, Supplier 231: For i As Integer = 0 To FromTicket.Count - 1 232: For j As Integer = 0 To ToTicket.Count - 1 233: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 234: FromTicket(i).Supplier = ToTicket(j).Supplier And 235: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 236: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i), ToTicket(j))) 237: GoTo Next1 238: End If 239: Next 240: 'добавили прямой без обратного 241: ContinueSearch = True 242: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i))) 243: Next1:
244: Next 245: 'второй шаг - совпадает AviaCompanyCode 246: If ContinueSearch Then 247: For i As Integer = 0 To FromTicket.Count - 1 248: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 249: For j As Integer = 0 To ToTicket.Count - 1 250: If FromTicket(i).AviaCompanyCode = ToTicket(j).AviaCompanyCode And 251: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 252: _CheckReturnTicket(i).ToTicket = ToTicket(j)
253: GoTo Next2 254: End If 255: Next 256: End If 257: Next2:
258: Next 259: End If 260: 'третий шаг - совпадает Supplier 261: If ContinueSearch Then 262: For i As Integer = 0 To FromTicket.Count - 1 263: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 264: For j As Integer = 0 To ToTicket.Count - 1 265:
266: If FromTicket(i).Supplier = ToTicket(j).Supplier And 267: FromTicket(i).FromDate <= ToTicket(j).FromDate Then 268: _CheckReturnTicket(i).ToTicket = ToTicket(j)
269: GoTo Next3 270: End If 271: Next 272: End If 273: Next3:
274: Next 275: End If 276: 'третий шаг - любой билет 277: If ContinueSearch Then 278: For i As Integer = 0 To FromTicket.Count - 1 279: If _CheckReturnTicket(i).FromTicket IsNot Nothing And _CheckReturnTicket(i).ToTicket Is Nothing Then 280: For j As Integer = 0 To ToTicket.Count - 1 281: If FromTicket(i).FromDate <= ToTicket(j).FromDate Then 282: _CheckReturnTicket(i).ToTicket = ToTicket(j)
283: GoTo Next4 284: End If 285: Next 286: End If 287: Next4:
288: Next 289: End If 290: Return _CheckReturnTicket 291: End Function 292:
293:
294: ' 295: 'В отличии от предыдущей функции - проходит только по тем билетам, у которых нет обратно и подтягивает новые билеты из базы 296: ' 297: Public Function CheckReturnTicketMORE_1(ByVal TicketWithReturn As System.Collections.Generic.List(Of OneMORETicketWithReturn)) As System.Collections.Generic.List(Of OneMORETicketWithReturn) 298: 'первый шаг - совпадает AviaCompanyCode, Supplier 299: Dim db1 = New DB.FlySeasonDataContext, RetTicket 300: For i As Integer = 0 To TicketWithReturn.Count - 1 301: If TicketWithReturn(i).ToTicket Is Nothing Then 302: 'у этого прямого билета нет обратного билета 303: RetTicket = db1.GetTicketMORE(TicketWithReturn(i).FromTicket.ToCountry, TicketWithReturn(i).FromTicket.ToCity, TicketWithReturn(i).FromTicket.FromCountry, TicketWithReturn(i).FromTicket.FromCity, TicketWithReturn(i).FromTicket.FromDate).ToList()
304: If RetTicket IsNot Nothing Then 305: If RetTicket.Count > 0 Then 306: If RetTicket(0).FromDate > TicketWithReturn(i).FromTicket.FromDate Then 307: TicketWithReturn(i).SetToTicket(RetTicket(0))
308: End If 309: End If 310: End If 311: End If 312: Next 313: Return TicketWithReturn 314: End Function 315:
316: ' 317: 'В отличии от предыдущей функции - проходит только по тем билетам, у которых нет обратно и подтягивает новые билеты из базы 318: ' 319: Public Function CheckReturnTicketMORE_2(ByVal FromTicket As System.Collections.Generic.List(Of DB.GetTicketMOREResult), ByVal ToDate As DateTime) As System.Collections.Generic.List(Of OneMORETicketWithReturn) 320: Dim _CheckReturnTicket = New System.Collections.Generic.List(Of OneMORETicketWithReturn) 321: Dim db1 = New DB.FlySeasonDataContext, RetTicket As System.Collections.Generic.List(Of DB.GetTicketMOREResult) 322: For i As Integer = 0 To FromTicket.Count - 1 323: 'у этого прямого билета нет обратного билета 324: RetTicket = db1.GetTicketMORE(FromTicket(i).ToCountry, FromTicket(i).ToCity, FromTicket(i).FromCountry, FromTicket(i).FromCity, ToDate).ToList()
325: If RetTicket IsNot Nothing Then 326: If RetTicket.Count > 0 Then 327: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i), RetTicket(0))) 328: Else 329: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i))) 330: End If 331: Else 332: _CheckReturnTicket.Add(New OneMORETicketWithReturn(FromTicket(i))) 333: End If 334: Next 335: Return _CheckReturnTicket 336: End Function 337:
338: End Class |
Неудачность этого проекта в том, что ожидания были намного больше полученного. Предполагалось, что этот проект будет тиражироваться по многим фирмам, торгующим авиабилетами, что к оптовой фирме подключатся сотни дилерских агентств (а подключилось всего 10), что проект будет жить долгие годы (а прожил всего года три), хотя все баги были выбраны, все было идеально вылизано для этого бизнеса. Соответственно, я рассчитывал получить возмещение своих затраченных усилий от адаптации проекта при его тиражировании, от хостинга проекта у дилеров. А получился полный фейл, владелец этого бизнеса покрутил этот проект всего несколько лет и закрыл его, хотя все было сделано в точности по его пожеланиям и он реально несколько лет вел торговлю в этом проекте.
Мне очень жалко, что я потратил столько сил и развивал такой сложный проект для какого-то барана, который в итоге просто отказался платить три копейки за хостинг и суппорт проекта. В итоге, такой большой мой труд прослужил всего лишь пару лет, в то время как другие мои проекты, например http://www.votpusk.ru/ служат уже 9 дет и, надеюсь, еще будут жить много-много лет.
|