From 3a8d88e1aa1f0e7d699696192a86f78d2c0048d5 Mon Sep 17 00:00:00 2001 From: "alex.kadyrov" Date: Fri, 27 Sep 2019 03:15:07 +0300 Subject: [PATCH] search complete --- configuration/config.go | 126 ++ configuration/defaults/formats.yaml | 4 + configuration/defaults/http.yaml | 4 + configuration/defaults/log.yaml | 11 + configuration/defaults/notifications.yaml | 10 + configuration/defaults/queue.yaml | 11 + configuration/defaults/settings.yaml | 13 + service/10_search.go | 9 +- service/service.go | 2 +- service/service_test.go | 59 +- structs/airline.go | 15 + structs/baggage.go | 7 + structs/clientInfo.go | 32 + structs/errorStructs.go | 8 + .../masterPricerTravelBoardSearch/request.go | 70 +- .../masterPricerTravelBoardSearch/response.go | 228 +-- .../masterPricerTravelBoardSearch/search.go | 473 ++++++ .../v14.3/request/makeQuery.go | 82 +- .../v14.3/response/filterBySegments.go | 81 ++ .../v14.3/response/parseReply.go | 1279 ++++++++++++++++- .../v16.3/request/makeQuery.go | 81 +- .../v16.3/response/parseReply.go | 2 +- structs/fareFamily.go | 11 + structs/flight.go | 105 ++ structs/flightDate.go | 177 +++ structs/itinerary.go | 39 + structs/price.go | 266 ++++ structs/travellers.go | 17 + utils/convert/convertTime.go | 268 ++++ utils/slice.go | 130 ++ 30 files changed, 3416 insertions(+), 204 deletions(-) create mode 100644 configuration/config.go create mode 100644 configuration/defaults/formats.yaml create mode 100644 configuration/defaults/http.yaml create mode 100644 configuration/defaults/log.yaml create mode 100644 configuration/defaults/notifications.yaml create mode 100644 configuration/defaults/queue.yaml create mode 100644 configuration/defaults/settings.yaml create mode 100644 structs/airline.go create mode 100644 structs/baggage.go create mode 100644 structs/clientInfo.go create mode 100644 structs/errorStructs.go create mode 100644 structs/fare/masterPricerTravelBoardSearch/search.go create mode 100644 structs/fare/masterPricerTravelBoardSearch/v14.3/response/filterBySegments.go create mode 100644 structs/fareFamily.go create mode 100644 structs/flight.go create mode 100644 structs/flightDate.go create mode 100644 structs/itinerary.go create mode 100644 structs/price.go create mode 100644 structs/travellers.go create mode 100644 utils/convert/convertTime.go create mode 100644 utils/slice.go diff --git a/configuration/config.go b/configuration/config.go new file mode 100644 index 0000000..e21f064 --- /dev/null +++ b/configuration/config.go @@ -0,0 +1,126 @@ +package configuration + +import ( + "encoding/base64" + "log" + "os" + + "github.com/microparts/configuration-golang" + "github.com/microparts/errors-go" + "github.com/microparts/logs-go" + "gopkg.in/yaml.v2" +) + +type connectionConfig struct { + URL string `yaml:"url"` + WSAP string `yaml:"wsap"` + Originator string `yaml:"originator"` + Password string `yaml:"password"` + PasswordRaw string + PinCode string `yaml:"pin_code"` + VatURL string `yaml:"vat_url"` + VatSOAPURL string `yaml:"vat_soap_url"` + VatOfficeID string `yaml:"vat_office_id"` + VatSign string `yaml:"vat_sign"` +} + +type amadeusConfig struct { + Connection connectionConfig `yaml:"connection"` + MaxRecommendations int `yaml:"max_recommendations"` +} + +type httpConfig struct { + Host string `yaml:"host"` + Port string `yaml:"port"` +} + +type formatsConfig struct { + Time string `yaml:"time"` + Date string `yaml:"date"` + XMLDate string `yaml:"xml_date"` +} + +// CfgType service config structure +type CfgType struct { + Amadeus amadeusConfig `yaml:"amadeus"` + HTTP httpConfig `yaml:"http"` + Formats formatsConfig `yaml:"formats"` + Queue queueConfig `yaml:"queue"` + Log *logs.Config `yaml:"log"` + Settings SettingsConfig `yaml:"settings"` + Notifications NotificationsConfig `yaml:"notifications"` +} + +type queueConfig struct { + PubSub PubSubConfig `yaml:"pubsub"` +} + +// PubSubConfig PubSub Config +type PubSubConfig struct { + Host string `yaml:"host"` + ProjectID string `yaml:"project_id"` + Topic string `yaml:"topic"` + Subscription string `yaml:"subscription"` + Key string `yaml:"key"` +} + +// SettingsConfig Settings Config +type SettingsConfig struct { + FoidRequreAirlines []string `yaml:"foid_require_airlines"` + RemoveDuplicateAirlines bool `yaml:"remove_duplicate_airlines"` + MaxAttemptsCancelVoid int `yaml:"max_attempts_cancel_void"` + MaxAttemptsDocIssuance int `yaml:"max_attempts_doc_issuance"` + MaxAttemptsPNRRET int `yaml:"max_attempts_pnr_ret"` + MaxAttemptsPNRADD int `yaml:"max_attempts_pnr_add"` + IssueExpire int `yaml:"issue_expire"` + BookingRequestsDelay int `yaml:"booking_requests_delay"` + IssueRequestsDelay int `yaml:"issue_requests_delay"` + FareRulesParagraphsToShow []string `yaml:"fare_rules_paragraphs_to_show"` + FareQualifierList []string `yaml:"fare_qualifier_list"` +} + +type NotificationsConfig struct { + ErrorMessageEmail string `yaml:"error_message_email"` + MailFrom string `yaml:"mail_from"` + Queue PubSubConfig `yaml:"pubsub"` +} + +var ( + // Provider is current service + Provider = "amadeus" +) + +var Config *CfgType + +// InitConfig initialize config data +func InitConfig() error { + configPath := config.GetEnv("CONFIG_PATH", "") + configBytes, err := config.ReadConfigs(configPath) + if errors.HasErrors(err) { + log.Printf("[config] read error: %+v", err) + return err + } + + err = yaml.Unmarshal(configBytes, &Config) + if errors.HasErrors(err) { + log.Printf("[config] unmarshal error for bytes: %+v", configBytes) + return err + } + // + // structs.TimeFormat = func() string { + // return Config.Formats.Time + // } + // + // structs.DateFormat = func() string { + // return Config.Formats.Date + // } + + if pwdBytes, err := base64.StdEncoding.DecodeString(Config.Amadeus.Connection.Password); err == nil { + Config.Amadeus.Connection.PasswordRaw = string(pwdBytes) + } else { + log.Println("Error decoding password. Is it not encrypted in bass64?") + os.Exit(-1) + } + + return nil +} diff --git a/configuration/defaults/formats.yaml b/configuration/defaults/formats.yaml new file mode 100644 index 0000000..ca9a32d --- /dev/null +++ b/configuration/defaults/formats.yaml @@ -0,0 +1,4 @@ +defaults: + formats: + time: "2006-01-02T15:04:05" + date: "2006-01-02" \ No newline at end of file diff --git a/configuration/defaults/http.yaml b/configuration/defaults/http.yaml new file mode 100644 index 0000000..fb9a080 --- /dev/null +++ b/configuration/defaults/http.yaml @@ -0,0 +1,4 @@ +defaults: + http: + host: "localhost" + port: "8080" diff --git a/configuration/defaults/log.yaml b/configuration/defaults/log.yaml new file mode 100644 index 0000000..149a64b --- /dev/null +++ b/configuration/defaults/log.yaml @@ -0,0 +1,11 @@ +defaults: + log: + level: "error" + format: "json" + sentry: + enable: true + dsn: "https://924c371cd88d44759b1c7d282d1b1105:2ca8dab7a2104d26843ac9f52b382f7e@sentry.io/1364865" + stackTrace: + enable: true + context: 0 + skip: 0 \ No newline at end of file diff --git a/configuration/defaults/notifications.yaml b/configuration/defaults/notifications.yaml new file mode 100644 index 0000000..c1684b9 --- /dev/null +++ b/configuration/defaults/notifications.yaml @@ -0,0 +1,10 @@ +defaults: + notifications: + error_message_email: "call-center@tm-consulting.ru" + mail_from: "robot@tmc24.io" + pubsub: + host: "localhost:8085" # for local pub/sup "localhost:8085" + project_id: "tmconsulting24" + topic: "notification-dev" + subscription: "notification-receiver-dev" + key: "" \ No newline at end of file diff --git a/configuration/defaults/queue.yaml b/configuration/defaults/queue.yaml new file mode 100644 index 0000000..f1f908f --- /dev/null +++ b/configuration/defaults/queue.yaml @@ -0,0 +1,11 @@ +defaults: + queue: + kind: "pubsub" # pubsub, redis, ... + pubsub: + host: "localhost:8085" # for local pub/sup "localhost:8085" + project_id: "tmconsulting24" + topic: "provider-logs-dev" + subscription: "provider-logs-receiver-dev" + key: "" + redis: + host: "" \ No newline at end of file diff --git a/configuration/defaults/settings.yaml b/configuration/defaults/settings.yaml new file mode 100644 index 0000000..9faf2b9 --- /dev/null +++ b/configuration/defaults/settings.yaml @@ -0,0 +1,13 @@ +defaults: + settings: + foid_require_airlines: ["2B","5N","7J","7R","8Q","BT","EY","FZ","HR","HY","J2","KC","KK","KR","LY","NN","PC","PS","QN","R2","R3","S7","SZ","U6","UT","VV","Y7","YC","YK","YQ","ZG"] + remove_duplicate_airlines: false + max_attempts_cancel_void: 5 # количество раз, которые система пробует отменить бронирование. каждая попытка предваряется sleep(attempt*seconds) + max_attempts_doc_issuance: 5 # количество раз, которые система пробует выписать билет. каждая попытка предваряется sleep(attempt*seconds) + max_attempts_pnr_ret: 5 # количество раз, которые система пробует сделать PNRRET. каждая попытка предваряется sleep(attempt*seconds) + max_attempts_pnr_add: 5 # количество раз, которые система пробует сделать PNRADD. каждая попытка предваряется sleep(attempt*seconds) + issue_expire: 30 # time in minutes, set TK XL time in minutes before departure + booking_requests_delay: 2 # время задержки между запросами букинг + issue_requests_delay: 0 # время задержки между запросами выписки + fare_rules_paragraphs_to_show: ["PE", "CD"] # PE (16)-penalties, CD (19)-Children discounts + fare_qualifier_list: ["P", "U"] # список fareQualifier, с которыми попытаются забронироваться \ No newline at end of file diff --git a/service/10_search.go b/service/10_search.go index 3308152..52411e7 100644 --- a/service/10_search.go +++ b/service/10_search.go @@ -11,11 +11,11 @@ import ( //"github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch/v14.3/response" ) -func (s *service) FareMasterPricerTravelBoardSearch(query *search.Request) (*search.Response, *client.ResponseSOAPHeader, error) { +func (s *service) FareMasterPricerTravelBoardSearch(query *search.SearchRequest) (*search.SearchResponse, *client.ResponseSOAPHeader, error) { switch s.mm[FareMasterPricerTravelBoardSearch] { case FareMasterPricerTravelBoardSearchV143: - query := Fare_MasterPricerTravelBoardSearchRequest_v14_3.MakeRequest(query) - response, header, err := s.sdk.FareMasterPricerTravelBoardSearchV143(query) + request := Fare_MasterPricerTravelBoardSearchRequest_v14_3.MakeRequest(query) + response, header, err := s.sdk.FareMasterPricerTravelBoardSearchV143(request) if response == nil { return nil, header, err } @@ -25,7 +25,8 @@ func (s *service) FareMasterPricerTravelBoardSearch(query *search.Request) (*sea return nil, header, errResponse } - return response.ToCommon(), header, err + reply, err := response.ToCommon(query) + return reply, header, err } return nil, nil, nil } diff --git a/service/service.go b/service/service.go index 75a5121..71203bd 100644 --- a/service/service.go +++ b/service/service.go @@ -135,7 +135,7 @@ type Service interface { CloseSession() (reply *SecuritySignOut_v04_1.Response, err error) // Search - FareMasterPricerTravelBoardSearch(query *search.Request) (*search.Response, *client.ResponseSOAPHeader, error) + FareMasterPricerTravelBoardSearch(query *search.SearchRequest) (*search.SearchResponse, *client.ResponseSOAPHeader, error) FareInformativeBestPricingWithout(query *Fare_InformativeBestPricingWithoutPNRRequest_v12_4.Request) (*Fare_InformativeBestPricingWithoutPNRResponse_v12_4.Response, *client.ResponseSOAPHeader, error) FareInformativePricingWithoutPNR(query *Fare_InformativePricingWithoutPNR_v12_4.Request) (*Fare_InformativePricingWithoutPNRReply_v12_4.Response, *client.ResponseSOAPHeader, error) diff --git a/service/service_test.go b/service/service_test.go index 205fcfb..864599d 100644 --- a/service/service_test.go +++ b/service/service_test.go @@ -1,6 +1,10 @@ package service import ( + "encoding/json" + "fmt" + "github.com/tmconsulting/amadeus-golang-sdk/configuration" + "github.com/tmconsulting/amadeus-golang-sdk/structs" search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" "github.com/tmconsulting/amadeus-golang-sdk/structs/pnr/retrieve" "log" @@ -83,26 +87,71 @@ func TestNewSKD(t *testing.T) { t.Run("Search test", func(t *testing.T) { + tearUp() + + err := configuration.InitConfig() + if err != nil { + t.FailNow() + } + + //url = configuration.Config.Amadeus.Connection.URL + //originator = configuration.Config.Amadeus.Connection.Originator + //passwordRaw = configuration.Config.Amadeus.Connection.PasswordRaw + //officeId = "MOWR228FG" + cl := client.New(client.SetURL(url), client.SetUser(originator), client.SetPassword(passwordRaw), client.SetAgent(officeId), client.SetLogger(stdOutLog)) amadeusSDK := New(cl) //amadeusSDK := New(cl, SetMethodVersion(PNRAddMultiElements, MethodVersion(PNRRetrieveV113))) - request := search.Request{ + itinerary := structs.Itinerary{ + DepartureLocation: structs.Location{ + AirportCode: "SVO", + CityCode: "MOW", + CountryCode: "RU", + Type: "city", + }, + ArrivalLocation: structs.Location{ + AirportCode: "LED", + CityCode: "LED", + CountryCode: "RU", + Type: "city", + }, + } + request := search.SearchRequest{ + ClientData: structs.ClientInfo{ + OfficeID: "MOWR224PW", + }, + BaseClass: []string{ + "E", + }, + Changes: false, + WithBaggage: true, + TravelType: "OW", Itineraries: map[int]*search.Itinerary{ 1: { - DepartureLocation: "MOW", - ArrivalLocation: "LED", - DepartureDate: time.Now().Add(10 * 24 * time.Hour), // add 10 days + Itinerary: &itinerary, + DepartureDate: structs.FlightDateTime{ + Date: time.Now().Add(10 * 24 * time.Hour), // add 10 days + }, + DepartureDateTill: structs.FlightDateTimeUniversal{ + DateStr: time.Now().Add(11 * 24 * time.Hour).String(), // add 10 days + }, }, }, Currency: "RUB", - Passengers: search.Travellers{ADT: 2, CHD: 1, INF: 1}, + Passengers: structs.Travellers{ADT: 1, CHD: 0, INF: 0}, + Airlines: []string{ + "SU", + }, } response, _, err := amadeusSDK.FareMasterPricerTravelBoardSearch(&request) t.Logf("response: %+v\n", response) + aa, _ := json.Marshal(response) + fmt.Println("Search response: ", string(aa)) + if !assert.NoError(t, err) { t.FailNow() } diff --git a/structs/airline.go b/structs/airline.go new file mode 100644 index 0000000..b657c80 --- /dev/null +++ b/structs/airline.go @@ -0,0 +1,15 @@ +package structs + +// Airline structure +type Airline struct { + CodeEng string `json:"code_eng" bson:"codeEng"` + CodeRus string `json:"code_rus,omitempty" bson:"codeRus,omitempty"` + NameEng string `json:"name_eng,omitempty" bson:"nameEng,omitempty"` + NameRus string `json:"name_rus,omitempty" bson:"nameRus,omitempty"` +} + +// Aircraft structure +type Aircraft struct { + Code *string `json:"code" db:"code" bson:"code"` + Name map[string]string `json:"name,omitempty" db:"name" bson:"name,omitempty"` +} diff --git a/structs/baggage.go b/structs/baggage.go new file mode 100644 index 0000000..90bbde4 --- /dev/null +++ b/structs/baggage.go @@ -0,0 +1,7 @@ +package structs + +// BaggageType structure +type BaggageType struct { + Value int `json:"value"` + Unit string `json:"unit"` +} diff --git a/structs/clientInfo.go b/structs/clientInfo.go new file mode 100644 index 0000000..e566409 --- /dev/null +++ b/structs/clientInfo.go @@ -0,0 +1,32 @@ +package structs + +import ( + "encoding/json" +) + +// ClientInfo customer info structure +type ClientInfo struct { + OfficeID string `json:"office_id"` + Timezone string `json:"timezone"` +} + +// UnmarshalJSON overrides UnmarshalJSON +func (c *ClientInfo) UnmarshalJSON(data []byte) error { + type ClientInfo2 struct { + Var1 string `json:"amadeus_office_id"` + Var2 string `json:"office_id"` + } + var clientInfo ClientInfo2 + if err := json.Unmarshal(data, &clientInfo); err != nil { + //logs.Log.WithError(err).Error("Error unmarshal json") + return err + } + var OfficeID = "" + if clientInfo.Var1 != "" { + OfficeID = clientInfo.Var1 + } else { + OfficeID = clientInfo.Var2 + } + c.OfficeID = OfficeID + return nil +} diff --git a/structs/errorStructs.go b/structs/errorStructs.go new file mode 100644 index 0000000..c3c762e --- /dev/null +++ b/structs/errorStructs.go @@ -0,0 +1,8 @@ +package structs + +// ErrorReply Error Reply structure +type ErrorReply struct { + Status string `json:"status,omitempty"` + ErrorCode string `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/structs/fare/masterPricerTravelBoardSearch/request.go b/structs/fare/masterPricerTravelBoardSearch/request.go index 8e71e3e..72429e7 100644 --- a/structs/fare/masterPricerTravelBoardSearch/request.go +++ b/structs/fare/masterPricerTravelBoardSearch/request.go @@ -1,39 +1,35 @@ package search -import ( - "time" -) - -type Request struct { - RequestId string `json:"request_id"` - ClientData ClientInfo `json:"client_data"` - Passengers Travellers `json:"passengers_count"` - AllowMixing *bool `json:"allow_mixing,omitempty"` - BaseClass []string `json:"base_class,omitempty"` //array_enum("", "e", "b", "f") - Changes *bool `json:"changes,omitempty"` - Airlines []string `json:"airlines,omitempty"` // Массив IATA кодов авиакомпаний по которым нужно фильтровать результаты поиска - ExcludedAirlines []string `json:"excluded_airlines,omitempty"` - WithBaggage bool `json:"with_baggage,omitempty"` - IsDomestic bool `json:"is_domestic,omitempty"` - TravelType string `json:"travel_type"` //enum("OW", "RT", "CT") где "OW" - One Way, "RT" - Round Trip, "CT" - Complex Trip - Itineraries map[int]*Itinerary `json:"itineraries"` - Currency string `json:"currency"` -} - -type Itinerary struct { - DepartureLocation string - ArrivalLocation string - DepartureDate time.Time - DepartureDateTill *time.Time -} - -type ClientInfo struct { - OfficeId string `json:"office_id"` - Timezone string `json:"timezone"` -} - -type Travellers struct { - ADT int `json:"ADT,omitempty"` - CHD int `json:"CHD,omitempty"` - INF int `json:"INF,omitempty"` -} +//type Request struct { +// RequestId string `json:"request_id"` +// ClientData ClientInfo `json:"client_data"` +// Passengers Travellers `json:"passengers_count"` +// AllowMixing *bool `json:"allow_mixing,omitempty"` +// BaseClass []string `json:"base_class,omitempty"` //array_enum("", "e", "b", "f") +// Changes *bool `json:"changes,omitempty"` +// Airlines []string `json:"airlines,omitempty"` // Массив IATA кодов авиакомпаний по которым нужно фильтровать результаты поиска +// ExcludedAirlines []string `json:"excluded_airlines,omitempty"` +// WithBaggage bool `json:"with_baggage,omitempty"` +// IsDomestic bool `json:"is_domestic,omitempty"` +// TravelType string `json:"travel_type"` //enum("OW", "RT", "CT") где "OW" - One Way, "RT" - Round Trip, "CT" - Complex Trip +// Itineraries map[int]*Itinerary `json:"itineraries"` +// Currency string `json:"currency"` +//} +// +////type Itinerary struct { +//// DepartureLocation string +//// ArrivalLocation string +//// DepartureDate time.Time +//// DepartureDateTill *time.Time +////} +// +//type ClientInfo struct { +// OfficeId string `json:"office_id"` +// Timezone string `json:"timezone"` +//} +// +//type Travellers struct { +// ADT int `json:"ADT,omitempty"` +// CHD int `json:"CHD,omitempty"` +// INF int `json:"INF,omitempty"` +//} diff --git a/structs/fare/masterPricerTravelBoardSearch/response.go b/structs/fare/masterPricerTravelBoardSearch/response.go index 082c16d..beb84d3 100644 --- a/structs/fare/masterPricerTravelBoardSearch/response.go +++ b/structs/fare/masterPricerTravelBoardSearch/response.go @@ -1,114 +1,122 @@ package search -import ( - "time" -) - -type Response struct { - TravellesInfo []TravellerInfo - Segments []Segment - Recommendations []Recommendation - RoutesSegments FlightDetails -} - -type TravellerInfo struct { - FirstName string - LastName string - Type PaxType // ADT, CHD, INF - DateOfBirth time.Time - Quaifier string // PT, PI - Number int // reference number -} - -type PaxType string - -type Segment struct { - SegmentId string `json:"-"` - FlightNumber string `json:"flight_number"` - DepartureLocation Location `json:"departure"` - DepartureDate time.Time `json:"departure_date"` - ArrivalLocation Location `json:"arrival"` - ArrivalDate time.Time `json:"arrival_date"` - MarketingAirline *Airline `json:"marketing_airline"` - OperatingAirline *Airline `json:"operating_airline"` - ValidatingAirline *Airline `json:"validating_airline,omitempty"` - Aircraft *Aircraft `json:"aircraft,omitempty"` -} - -type Location struct { - AirportCode string `json:"airport_code"` - CountryCode string `json:"country_code"` - CityCode string `json:"city_code"` - Terminal string `json:"terminal"` - Type string `json:"-"` -} - -type Airline struct { - CodeEng string `json:"code_eng" bson:"codeEng"` - CodeRus string `json:"code_rus,omitempty" bson:"codeRus,omitempty"` - NameEng string `json:"name_eng,omitempty" bson:"nameEng,omitempty"` - NameRus string `json:"name_rus,omitempty" bson:"nameRus,omitempty"` -} - -type Aircraft struct { - Code *string `json:"code" db:"code" bson:"code"` - Name map[string]string `json:"name,omitempty" db:"name" bson:"name,omitempty"` -} +//type Response struct { +// TravellesInfo []TravellerInfo +// Segments []Segment +// Recommendations []Recommendation +// RoutesSegments FlightDetails +//} +// +//type TravellerInfo struct { +// FirstName string +// LastName string +// Type PaxType // ADT, CHD, INF +// DateOfBirth time.Time +// Quaifier string // PT, PI +// Number int // reference number +//} +// +//type PaxType string +// +//type Segment struct { +// SegmentId string `json:"-"` +// FlightNumber string `json:"flight_number"` +// DepartureLocation Location `json:"departure"` +// DepartureDate time.Time `json:"departure_date"` +// ArrivalLocation Location `json:"arrival"` +// ArrivalDate time.Time `json:"arrival_date"` +// MarketingAirline *Airline `json:"marketing_airline"` +// OperatingAirline *Airline `json:"operating_airline"` +// ValidatingAirline *Airline `json:"validating_airline,omitempty"` +// Aircraft *Aircraft `json:"aircraft,omitempty"` +//} +// +//type Location struct { +// AirportCode string `json:"airport_code"` +// CountryCode string `json:"country_code"` +// CityCode string `json:"city_code"` +// Terminal string `json:"terminal"` +// Type string `json:"-"` +//} +// +//type Airline struct { +// CodeEng string `json:"code_eng" bson:"codeEng"` +// CodeRus string `json:"code_rus,omitempty" bson:"codeRus,omitempty"` +// NameEng string `json:"name_eng,omitempty" bson:"nameEng,omitempty"` +// NameRus string `json:"name_rus,omitempty" bson:"nameRus,omitempty"` +//} +// +//type Aircraft struct { +// Code *string `json:"code" db:"code" bson:"code"` +// Name map[string]string `json:"name,omitempty" db:"name" bson:"name,omitempty"` +//} // Recommendation structure -type Recommendation struct { - ID string `json:"id,omitempty"` - Provider string `json:"provider"` - Class []string `json:"class"` - Price int `json:"price"` - ItinerarySegments FlightDetails `json:"itinerary_segments"` - ValidatingAirline Airline `json:"validating_airline"` - Interline bool `json:"interline"` - LastTicketDate string `json:"last_date,omitempty"` - Refundable string `json:"refundable,omitempty"` -} - -// ItineraryID itinerary type -// groupOfFlights.propFlightGrDetail.flightProposal.ref -type ItineraryID int - -// ItineraryID variant type -// groupOfFlights.flightDetails array key -type VariantID int - -// FlightDetails map of FlightDetails -type FlightDetails map[ItineraryID]map[VariantID]Variant - -type Variant struct { - FlightDetails []FlightDetail -} - -// RouteSegments Route Segments structure -type RouteSegments struct { - GroupOfSegmentsID int `json:"-"` - ItinerarySegments []*ItinerarySegment `json:"segments"` - TravelTime int `json:"travel_time"` -} - -// ItinerarySegment segments of Itinerary -type ItinerarySegment struct { - SegmentID string `json:"segment_id"` - CodeShare bool `json:"codeshare"` - *FlightDetail -} - -// FlightDetail structure -type FlightDetail struct { - Class string `json:"class"` // @TODO Deprecate it! - Cabin string `json:"cabin"` - FlightClass string `json:"flight_class"` // @TODO rename it to Subclass ! - BookingCode string `json:"booking_code"` - FareBasisCode string `json:"fare_basis_code"` - Availability int `json:"availability"` - Baggage *t.BaggageType `json:"baggage,omitempty"` - - DepartureTime time.Time - ArrivalTime time.Time - DepartureLocation Location - ArrivalLocation Location -} +//type Recommendation struct { +// ID string `json:"id,omitempty"` +// Provider string `json:"provider"` +// Class []string `json:"class"` +// Price int `json:"price"` +// ItinerarySegments FlightDetails `json:"itinerary_segments"` +// ValidatingAirline Airline `json:"validating_airline"` +// Interline bool `json:"interline"` +// LastTicketDate string `json:"last_date,omitempty"` +// Refundable string `json:"refundable,omitempty"` +//} +// +//// ItineraryID itinerary type +//// groupOfFlights.propFlightGrDetail.flightProposal.ref +//type ItineraryID int +// +//// ItineraryID variant type +//// groupOfFlights.flightDetails array key +//type VariantID int +// +//// FlightDetails map of FlightDetails +//type FlightDetails map[ItineraryID]map[VariantID]Variant +// +//type Variant struct { +// FlightDetails []FlightDetail +//} +// +//// RouteSegments Route Segments structure +//type RouteSegments struct { +// GroupOfSegmentsID int `json:"-"` +// ItinerarySegments []*ItinerarySegment `json:"segments"` +// TravelTime int `json:"travel_time"` +//} +// +//// ItinerarySegment segments of Itinerary +//type ItinerarySegment struct { +// SegmentID string `json:"segment_id"` +// CodeShare bool `json:"codeshare"` +// *FlightDetail +//} +// +//// FlightDetail structure +//type FlightDetail struct { +// Class string `json:"class"` // @TODO Deprecate it! +// Cabin string `json:"cabin"` +// FlightClass string `json:"flight_class"` // @TODO rename it to Subclass ! +// FareClass string `json:"fare_class"` +// BookingClass string `json:"rbd"` +// FareBasisCode string `json:"fare_basis_code"` +// Availability int `json:"availability"` +// //Baggage *t.BaggageType `json:"baggage,omitempty"` +// +// DepartureTime time.Time +// ArrivalTime time.Time +// DepartureLocation Location +// ArrivalLocation Location +//} +// +//// Equal compare flight details +//func (fd *FlightDetail) Equal(test *FlightDetail) bool { +// if fd.BookingClass == test.BookingClass && +// fd.FareClass == test.FareClass && +// fd.FareBasisCode == test.FareBasisCode && +// fd.Availability == test.Availability { +// return true +// } +// return false +//} diff --git a/structs/fare/masterPricerTravelBoardSearch/search.go b/structs/fare/masterPricerTravelBoardSearch/search.go new file mode 100644 index 0000000..bb49273 --- /dev/null +++ b/structs/fare/masterPricerTravelBoardSearch/search.go @@ -0,0 +1,473 @@ +package search + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/tmconsulting/amadeus-golang-sdk/structs" + "strings" + "time" +) + +/* +{ + "request_id": "hashcode", + "client_data": { + "amadeus_office_id": "" // officeId, для которого нужно запрашивать сессию amadeus + }, + "passengers_count": { + "ADT": 1, + "CHD": 0, + "INF": 0 + }, + "allow_mixing": true, //allow the mixing of classes in segments + "search_only_s7": false, + "base_сlass": [], // ["E", "B", "F"] где E - Economic, B - Business, F - First + "changes": true, // допустимы ли рейсы с пересадками + "airline": [], + "excluded_airlines": [], + "with_baggage": false, // eсли оно true - в выдаче должны быть только те рекомендации, где присутствует возможность провоза багажа + "is_domestic": false, // признак домашнего перелёта (Вылетаем ли за пределы страны, домашней для аэропорта вылета) + "travel_type": "OW", // ["OW", "RT", "CT"] где OW - One Way, RT - Round Trip, CT - Complex Trip + "itineraries": { + "{itinerary_id}": { + "departure_location": { + "airport_code": "MSQ", + "country_code": "BY", + "city_code": "MSQ" + }, + "arrival_location": { + "airport_code": "JFK", + "country_code": "US", + "city_code": "NYC" + }, + "departure_date": "YYYY-MM-DDThh:mm:ss", + "departure_date_till": "YYYY-MM-DDThh:mm:ss", + "arrival_date": "YYYY-MM-DDThh:mm:ss" + } + }, + "currency": "RUB" +} +*/ + +// SearchRequest structure of Search request +type SearchRequest struct { + RequestID string `json:"request_id"` + ClientData structs.ClientInfo `json:"client_data"` + Passengers structs.Travellers `json:"passengers_count"` + AllowMixing *bool `json:"allow_mixing,omitempty"` + ResultOnlyS7 bool `json:"search_only_s7,omitempty"` + BaseClass []string `json:"base_class,omitempty"` //array_enum("C", "Y", "F") + Changes bool `json:"changes,omitempty"` + Airlines []string `json:"airlines,omitempty"` // Массив IATA кодов авиакомпаний по которым нужно фильтровать результаты поиска + ExcludedAirlines []string `json:"excluded_airlines,omitempty"` + WithBaggage bool `json:"with_baggage,omitempty"` + IsDomestic bool `json:"is_domestic,omitempty"` + TravelType string `json:"travel_type"` //enum("OW", "RT", "CT") где "OW" - One Way, "RT" - Round Trip, "CT" - Complex Trip + Itineraries mapItineraries `json:"itineraries"` + Currency string `json:"currency"` +} + +type mapItineraries map[int]*Itinerary + +// Check validate search request +func (request *SearchRequest) Check() (err error) { + var officeID = request.ClientData.OfficeID + if officeID == "" { + err = errors.New("empty OfficeID") + fmt.Println(err) + return + } + + var routeCount = len(request.Itineraries) + if routeCount == 0 { + err = errors.New("empty Itineraries") + fmt.Println(err) + return + } + + var travelType = strings.ToUpper(request.TravelType) + if travelType == "" { + err = errors.New("empty TravelType") + fmt.Println(err) + return + } + + if travelType == "OW" && routeCount != 1 { + err = errors.New("number of Itineraries for 'OW' should be equal to one") + fmt.Println(err) + return + } + + if travelType == "RT" && routeCount != 2 { + err = errors.New("number of Itineraries for 'RT' should be equal to two") + fmt.Println(err) + return + } + + if travelType == "CT" && routeCount < 2 { + err = errors.New("use TravelType == 'OW'") + fmt.Println(err) + return + } + + request.TravelType = travelType + + if (request.Passengers.ADT + request.Passengers.CHD) <= 0 { + err = errors.New("invalid count passengers") + return + } + if request.Passengers.INF < 0 { + request.Passengers.INF = 0 + } + if request.Passengers.ADT < request.Passengers.INF { + err = errors.New("number of infants exceeds the number of adults") + return + } + + //for id, itinerary := range request.Itineraries { + for i := 1; i <= len(request.Itineraries); i++ { + itinerary, exists := request.Itineraries[i] + if !exists { + err = fmt.Errorf("not found 'itinerary_id' equal to '%d'", i) + return + } + errDep := itinerary.DepartureDate.ParseRequest() + errDepTill := itinerary.DepartureDateTill.ParseRequest() + errArrival := itinerary.ArrivalDate.ParseRequest() + if errDep != nil { + if errDepTill == nil { + if itinerary.DepartureDateTill.DateFlag { + departureDateStr := itinerary.DepartureDateTill.Date.Format("2006-01-02") + "T00:00" + if departureDate, err := time.Parse("2006-01-02T15:04", departureDateStr); err == nil { + itinerary.DepartureDate.DateStr = departureDateStr + itinerary.DepartureDate.Date = departureDate + itinerary.DepartureDate.TimeFlag = true + itinerary.DepartureDate.Error = nil + errDep = nil + } + } + } + if errDep != nil { + err = fmt.Errorf("no valid travel date for itinerary ('itinerary_id' equal to '%d')", i) + return + } + } + + if !itinerary.DepartureDate.TimeFlag { + itinerary.DepartureDate.DateStr = itinerary.DepartureDate.Date.Format("2006-01-02") + "T00:00" + itinerary.DepartureDate.TimeFlag = true + } + + if errDepTill == nil { + if !itinerary.DepartureDateTill.DateFlag { + departureDateTillStr := itinerary.DepartureDate.Date.Format("2006-01-02") + "T" + itinerary.DepartureDateTill.Date.Format("15:04") + if departureDateTill, err := time.Parse("2006-01-02T15:04", departureDateTillStr); err == nil { + itinerary.DepartureDateTill.DateStr = departureDateTillStr + itinerary.DepartureDateTill.Date = departureDateTill + itinerary.DepartureDateTill.DateFlag = true + } else { + //itinerary.DepartureDateTill.DateFlag = false + itinerary.DepartureDateTill.TimeFlag = false + itinerary.DepartureDateTill.Error = err + } + } else if !itinerary.DepartureDateTill.TimeFlag { + departureDateTillStr := itinerary.DepartureDateTill.Date.Format("2006-01-02") + "T23:59" + if departureDateTill, err := time.Parse("2006-01-02T15:04", departureDateTillStr); err == nil { + itinerary.DepartureDateTill.DateStr = departureDateTillStr + itinerary.DepartureDateTill.Date = departureDateTill + itinerary.DepartureDateTill.TimeFlag = true + } else { + itinerary.DepartureDateTill.DateFlag = false + //itinerary.DepartureDateTill.TimeFlag = false + itinerary.DepartureDateTill.Error = err + } + } + } // else { + // departureDateTillStr := itinerary.DepartureDate.Date.Format("2006-01-02") + "T23:59" + // if departureDateTill, err := time.Parse("2006-01-02T15:04", departureDateTillStr); err == nil { + // itinerary.DepartureDateTill.DateStr = departureDateTillStr + // itinerary.DepartureDateTill.Date = departureDateTill + // itinerary.DepartureDateTill.DateFlag = true + // } + //} + + if errArrival == nil { + if !itinerary.ArrivalDate.TimeFlag { + arrivalDateStr := itinerary.ArrivalDate.Date.Format("2006-01-02") + "T23:59" + if arrivalDate, err := time.Parse("2006-01-02T15:04", arrivalDateStr); err == nil { + itinerary.ArrivalDate.DateStr = arrivalDateStr + itinerary.ArrivalDate.Date = arrivalDate + itinerary.ArrivalDate.TimeFlag = true + } else { + itinerary.ArrivalDate.TimeFlag = false + itinerary.ArrivalDate.Error = err + } + } + } else { + arrivalDateStr := itinerary.DepartureDate.Date.Format("2006-01-02") + if arrivalDate, err := time.Parse("2006-01-02", arrivalDateStr); err == nil { + itinerary.ArrivalDate.DateStr = arrivalDateStr + itinerary.ArrivalDate.Date = arrivalDate + itinerary.ArrivalDate.TimeFlag = false + } else { + itinerary.ArrivalDate.TimeFlag = false + itinerary.ArrivalDate.Error = err + } + } + } + + return +} + +// Itinerary of search request +type Itinerary struct { + *structs.Itinerary + //ItineraryID string `json:"-"` + DepartureDate structs.FlightDateTime + DepartureDateTill structs.FlightDateTimeUniversal + ArrivalDate structs.FlightDateTime +} + +// UnmarshalJSON overrides UnmarshalJSON +func (i *Itinerary) UnmarshalJSON(data []byte) error { + type ItineraryRequest struct { + *structs.Itinerary + //ItineraryID string `json:"itinerary_id,omitempty"` + DepartureDate string `json:"departure_date"` + DepartureDateTill string `json:"departure_date_till,omitempty"` + ArrivalDate string `json:"arrival_date,omitempty"` + } + var request ItineraryRequest + if err := json.Unmarshal(data, &request); err != nil { + //logs.Log.WithError(err).Error("Error unmarshal json") //todo log + return err + } + + i.Itinerary = request.Itinerary + //i.ItineraryID = request.ItineraryID + i.DepartureDate.DateStr = request.DepartureDate + errDep := i.DepartureDate.ParseRequest() + i.DepartureDateTill.DateStr = request.DepartureDateTill + errDepTill := i.DepartureDateTill.ParseRequest() + i.ArrivalDate.DateStr = request.ArrivalDate + errArrival := i.ArrivalDate.ParseRequest() + if errDep != nil && (errArrival != nil || errDepTill == nil) { + return errors.New("empty 'departure_date'") + } + return nil +} + +/* +{ + "segments": { + "amadeus_1": { + "departure": { + "airport_code": "DME", + "terminal": "" + }, + "arrival": { + "airport_code": "AER", + "terminal": "" + }, + "departure_date": "2018-05-21T09:40", + "arrival_date": "2018-05-21T12:00", + "operating_airline_flight_number": "1045", + //"marketing_airline_flight_number": "1045", + "aircraft": "738", + //"aircraft_full_name": "Boeing 737-800 Passenger", + "marketing_airline": { + "code_eng": "S7" + }, + "operating_airline": { + "code_eng": "GH", + }, + "codeshare": true, + "travel_time": 140 + } + }, + "recommendations": [ + { + "provider": "amadeus", + "class": ["E"], + "price": { + "total_amount": { + "amount": 0, + "currency": "RUB" + }, + "fare_amount": { + "amount": 0, + "currency": "RUB" + }, + "tax_amount": { + "amount": 0, + "currency": "RUB" + }, + "fee_amount": { + "amount": 0, + "currency": "RUB" + }, + "pax_fare": { + "ADT": { + "total_amount": { + "amount": 0, + "currency": "RUB" + }, + "fare_amount": { + "amount": 0, + "currency": "RUB" + }, + "tax_amount": { + "amount": 0, + "currency": "RUB" + }, + "fee_amount": { + "amount": 0, + "currency": "RUB" + } + }, + "CHD": null, //same as ADT + "INF": null //same as ADT + }, + "taxes": [ + { + "costs": { + "currency": "RUB", + "amount": 0 + }, + "code": "" + } + ] + }, + "itineraries_segments": { //routes_segments по-русски. + "{itinerary_id}": { // ссылка на itinerary_id из request + "{index}": { + "segments": [ + { + "segment_id": "amadeus_1", // ссылка на id из segments + "codeshare": true, + + // flightDetails["ADT"] + "class": "E", + "flight_class": "M", // (Amadeus) + "booking_code": "Q", + "fare_basis_code": "QBSOW", + "availability": 9, + "baggage": { + "value": 0, + "unit": "" + }, + + "pax_details": { // только для случаев, если "fare_basis_code" отличается от "ADT" + "CHD": { + //"class": "E", + "flight_class": "M", // (Amadeus) + "booking_code": "Q", + "fare_basis_code": "QBSOW", + "availability": 9 + "baggage": { // только для случаев, если "baggage" отличается от "ADT" + "value": 0, + "unit": "" + } + }, + "INF": null //same as CHD + } + } + ], + "travel_time": 140, + } + } + }, + "validating_airline": { + "code_eng": "UT", + }, + "interline": true, + "last_date": "2018-07-15T00:00:00", + "refundable": "N" + } + ] +} +*/ + +// SearchResponse structure of Search response +type SearchResponse struct { + Segments MapFlights `json:"segments,omitempty"` + Recommendations []*Recommendation `json:"recommendations,omitempty"` + Error *structs.ErrorReply `json:"error,omitempty"` +} + +// MapFlights Map of Flights +type MapFlights map[string]*structs.Flight + +// Recommendation structure +type Recommendation struct { + ID string `json:"id,omitempty"` + Provider string `json:"provider"` + Class []string `json:"class"` + Price structs.Price `json:"price"` + ItinerarySegments RoutesSegments `json:"itinerary_segments"` + ValidatingAirline structs.Airline `json:"validating_airline"` + Interline bool `json:"interline"` + LastTicketDate string `json:"last_date,omitempty"` + Refundable string `json:"refundable,omitempty"` +} + +// Combine compare recomendations +func (recommendation *Recommendation) Combine(recommendation2 *Recommendation) bool { + if !recommendation.Price.Equal(&recommendation2.Price) { + return false + } + if recommendation.ValidatingAirline.CodeEng != recommendation2.ValidatingAirline.CodeEng { + return false + } + for itineraryID, variants := range recommendation.ItinerarySegments { + var variantNumber = len(variants) + 1 + for _, segments := range recommendation2.ItinerarySegments[itineraryID] { + variants[variantNumber] = segments + variantNumber++ + } + } + return true +} + +// RoutesSegments map of RoutesSegments +type RoutesSegments map[int]map[int]*RouteSegments + +// RouteSegments Route Segments structure +type RouteSegments struct { + GroupOfSegmentsID int `json:"-"` + ItinerarySegments []*ItinerarySegment `json:"segments"` + TravelTime int `json:"travel_time"` +} + +// ItinerarySegment segments of Itinerary +type ItinerarySegment struct { + SegmentID string `json:"segment_id"` + CodeShare bool `json:"codeshare"` + *FlightDetail + PaxDetails mapPaxDetails `json:"pax_details,omitempty"` +} + +type mapPaxDetails map[string]*FlightDetail + +// FlightDetail Flight Detail structure +type FlightDetail struct { + Class string `json:"class"` // @TODO Deprecate it! + Cabin string `json:"cabin"` + FareClass string `json:"fare_class"` + BookingClass string `json:"rbd"` + FareBasisCode string `json:"fare_basis_code"` + Availability int `json:"availability"` + Baggage *structs.BaggageType `json:"baggage,omitempty"` +} + +// Equal compare flight details +func (fd *FlightDetail) Equal(test *FlightDetail) bool { + if fd.BookingClass == test.BookingClass && + fd.FareClass == test.FareClass && + fd.FareBasisCode == test.FareBasisCode && + fd.Availability == test.Availability { + return true + } + return false +} diff --git a/structs/fare/masterPricerTravelBoardSearch/v14.3/request/makeQuery.go b/structs/fare/masterPricerTravelBoardSearch/v14.3/request/makeQuery.go index 3358fc0..bb24dce 100644 --- a/structs/fare/masterPricerTravelBoardSearch/v14.3/request/makeQuery.go +++ b/structs/fare/masterPricerTravelBoardSearch/v14.3/request/makeQuery.go @@ -3,10 +3,10 @@ package Fare_MasterPricerTravelBoardSearchRequest_v14_3 // fmptbq143 import ( search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" "github.com/tmconsulting/amadeus-golang-sdk/structs/formats" - "github.com/tmconsulting/amadeus-golang-sdk/utils" + "github.com/tmconsulting/amadeus-golang-sdk/utils/convert" ) -func MakeRequest(request *search.Request) *Request { +func MakeRequest(request *search.SearchRequest) *Request { var query = Request{ NumberOfUnit: &NumberOfUnitsType{ @@ -110,29 +110,75 @@ func MakeRequest(request *search.Request) *Request { }) } - for k, i := range request.Itineraries { + var routeCount = len(request.Itineraries) + var useMultiCity = false + //if routeCount > 3 && len(request.BaseClass) > 0 { + // useMultiCity = true + //} else + if routeCount < 4 { + useMultiCity = true + } + for routeID := 1; routeID <= routeCount; routeID++ { + var route = request.Itineraries[routeID] + var firstDateTimeDetail = DateAndTimeDetailsTypeI{} + if route.DepartureDate.Error == nil { + firstDateTimeDetail.Date = formats.Date_DDMMYY(convert.DateToAmadeusDate(route.DepartureDate.Date)) + if route.DepartureDate.TimeFlag { + firstDateTimeDetail.Time = formats.Time24_HHMM(convert.DateToAmadeusTime(route.DepartureDate.Date)) + firstDateTimeDetail.TimeQualifier = formats.AlphaNumericString_Length1To3("TD") + } + } else if route.ArrivalDate.Error == nil { + firstDateTimeDetail.Date = formats.Date_DDMMYY(convert.DateToAmadeusDate(route.ArrivalDate.Date)) + if route.ArrivalDate.TimeFlag { + firstDateTimeDetail.Time = formats.Time24_HHMM(convert.DateToAmadeusTime(route.ArrivalDate.Date)) + firstDateTimeDetail.TimeQualifier = formats.AlphaNumericString_Length1To3("TA") + } + } + + var departureLocation = route.DepartureLocation.AirportCode + if departureLocation == "" { + departureLocation = route.DepartureLocation.CityCode + } + var arrivalLocation = route.ArrivalLocation.AirportCode + if arrivalLocation == "" { + arrivalLocation = route.ArrivalLocation.CityCode + } + var itinerary = Itinerary{ RequestedSegmentRef: &OriginAndDestinationRequestType{ - SegRef: formats.NumericInteger_Length1To2(k), + SegRef: formats.NumericInteger_Length1To2(routeID), }, TimeDetails: &DateAndTimeInformationType_181295S{ - FirstDateTimeDetail: &DateAndTimeDetailsTypeI{ - Date: formats.Date_DDMMYY(utils.DateToAmadeusDate(i.DepartureDate)), - }, - }, - } - - itinerary.DepartureLocalization = &DepartureLocationType{ - DeparturePoint: &ArrivalLocationDetailsType_120834C{ - LocationId: formats.AlphaString_Length3To5(i.DepartureLocation), + FirstDateTimeDetail: &firstDateTimeDetail, }, } - itinerary.ArrivalLocalization = &ArrivalLocalizationType{ - ArrivalPointDetails: &ArrivalLocationDetailsType{ - LocationId: formats.AlphaString_Length3To5(i.ArrivalLocation), - }, + if useMultiCity { + itinerary.DepartureLocalization = &DepartureLocationType{ + DepMultiCity: []*MultiCityOptionType{ + { + LocationId: formats.AlphaString_Length3To5(departureLocation), + }, + }, + } + itinerary.ArrivalLocalization = &ArrivalLocalizationType{ + ArrivalMultiCity: []*MultiCityOptionType{ + { + LocationId: formats.AlphaString_Length3To5(arrivalLocation), + }, + }, + } + } else { + itinerary.DepartureLocalization = &DepartureLocationType{ + DeparturePoint: &ArrivalLocationDetailsType_120834C{ + LocationId: formats.AlphaString_Length3To5(departureLocation), + }, + } + itinerary.ArrivalLocalization = &ArrivalLocalizationType{ + ArrivalPointDetails: &ArrivalLocationDetailsType{ + LocationId: formats.AlphaString_Length3To5(arrivalLocation), + }, + } } - query.Itinerary = append(query.Itinerary, &itinerary) } diff --git a/structs/fare/masterPricerTravelBoardSearch/v14.3/response/filterBySegments.go b/structs/fare/masterPricerTravelBoardSearch/v14.3/response/filterBySegments.go new file mode 100644 index 0000000..b13cf95 --- /dev/null +++ b/structs/fare/masterPricerTravelBoardSearch/v14.3/response/filterBySegments.go @@ -0,0 +1,81 @@ +package Fare_MasterPricerTravelBoardSearchResponse_v14_3 + +import ( + search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" + + structsCommon "github.com/tmconsulting/amadeus-golang-sdk/structs" +) + +// GroupOfFlights Group Of Flights +type FilterGroupOfFlights struct { + ItineraryID int + GroupOfSegmentsID int + Flights []*structsCommon.Flight + MajorityCarrier string + //ElapseFlyingTime string + Duration int +} + +// FilterRules Filter Rules +type FilterRules struct { + Itineraries []*search.Itinerary + OnlyNonStopFlight bool + Airlines []string + //ExcludedAirlines []string +} + +// CheckRulesCompanies returns true if validating company's rules +func (filterRules *FilterRules) CheckRulesCompanies(flight *structsCommon.Flight) bool { + + //for _, excludedAirline := range filterRules.ExcludedAirlines { + // if excludedAirline == flight.OperatingAirline { + // return false + // } + // if excludedAirline == flight.MarketingAirline { + // return false + // } + //} + if len(filterRules.Airlines) > 0 { + var flag = false + for _, airline := range filterRules.Airlines { + if airline == flight.OperatingAirline.CodeEng { + flag = true + } + if airline == flight.MarketingAirline.CodeEng { + flag = true + } + } + return flag + } + + return true +} + +// CheckRulesTimes Check Rules Times +func (filterRules *FilterRules) CheckRulesTimes(itineraryIndex int, isDeparture, isArrival bool, segment *structsCommon.Flight) bool { + + //var itinerary = filterRules.Itineraries[itineraryIndex] + // + //if isDeparture { + // var departureDateStart = itinerary.DepartureDate.Date + // if !departureDateStart.Equal(segment.DepartureDate) && !departureDateStart.Before(segment.DepartureDate) { + // return false + // } + // + // if itinerary.DepartureDateTill.Error == nil { + // var departureDateEnd = itinerary.DepartureDateTill.Date + // if !departureDateEnd.Equal(segment.DepartureDate) && !departureDateEnd.After(segment.DepartureDate) { + // return false + // } + // } + //} + // + //if isArrival && itinerary.ArrivalDate.Error == nil { + // var arrivalDate = itinerary.ArrivalDate.Date + // if !arrivalDate.Equal(segment.ArrivalDate) && !arrivalDate.After(segment.ArrivalDate) { + // return false + // } + //} + + return true +} diff --git a/structs/fare/masterPricerTravelBoardSearch/v14.3/response/parseReply.go b/structs/fare/masterPricerTravelBoardSearch/v14.3/response/parseReply.go index 31f25ce..0652ec8 100644 --- a/structs/fare/masterPricerTravelBoardSearch/v14.3/response/parseReply.go +++ b/structs/fare/masterPricerTravelBoardSearch/v14.3/response/parseReply.go @@ -1,8 +1,16 @@ package Fare_MasterPricerTravelBoardSearchResponse_v14_3 import ( + "encoding/json" "fmt" + "github.com/tmconsulting/amadeus-golang-sdk/configuration" + structsCommon "github.com/tmconsulting/amadeus-golang-sdk/structs" search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" + "github.com/tmconsulting/amadeus-golang-sdk/utils" + "github.com/tmconsulting/amadeus-golang-sdk/utils/convert" + + "sort" + "strconv" "strings" ) @@ -14,27 +22,1276 @@ func (r *Response) CheckErrorReply() error { return nil } -func (r *Response) ToCommon() *search.Response { +func (reply *Response) ToCommon(request *search.SearchRequest) (*search.SearchResponse, error) { - var response search.Response + return ParseReply(request, reply) +} - for itineraryId, flightIndex := range r.FlightIndex { - for variantId, groupOfFLight := range flightIndex.GroupOfFlights { - var flightDetails []search.FlightDetail - for _, groupOfFLight := range groupOfFLight.FlightDetails { +// ParseReply Parse Reply from FareMasterPricerTravelBoardSearch +func ParseReply(request *search.SearchRequest, reply *Response) (*search.SearchResponse, error) { - flightDetails = append(flightDetails, search.FlightDetail{}) + var requestItineraries []*search.Itinerary + for i := 1; i <= len(request.Itineraries); i++ { + var itinerary = request.Itineraries[i] + requestItineraries = append(requestItineraries, itinerary) + } - response.RoutesSegments[search.ItineraryID(itineraryId)][search.VariantID(variantId)] = search.Variant{ - flightDetails, + //var recommendationIds []int + var groupsRef = make(map[string][][]int) + for _, recReply := range reply.Recommendation { + var recommendationID = recReply.ItemNumber.ItemNumberId.Number + var segmentsRef [][]int + for _, segmentFlightRef := range recReply.SegmentFlightRef { + var groupOfSegmentsIds []int + for _, referencingDetail := range segmentFlightRef.ReferencingDetail { + if referencingDetail.RefQualifier == "S" { + groupOfSegmentsIds = append(groupOfSegmentsIds, int(referencingDetail.RefNumber)) } } + segmentsRef = append(segmentsRef, groupOfSegmentsIds) } + //if id, err := strconv.Atoi(recommendationID); err == nil { + // recommendationIds = append(recommendationIds, id) + //} + groupsRef[recommendationID] = segmentsRef } + //sort.Ints(recommendationIds) - //for _, recommendation := range r.Recommendation { + //var excludedAirlineS7 = false + //var excludedAirlines []string + //var excludedAirlineS7 = true + //var excludedAirlines = []string{"BD", "EQ", "N4", "QH", "UT", "YK", "6X"} + //if len(request.AirlineList) == 1 { + // if request.AirlineList[0] == "S7" { + // excludedAirlineS7 = false + // } + // request.ResultOnlyS7 = true + // excludedAirlines = []string{} + //} else { + // for _, airline := range request.AirlineList { + // if airline == "S7" { + // excludedAirlineS7 = false + // } else { + // if exists, i := utils.InArray(airline, excludedAirlines); exists { + // excludedAirlines = append(excludedAirlines[:i], excludedAirlines[i+1:]...) + // } + // } + // } + //} + //if excludedAirlineS7 { + // excludedAirlines = append(excludedAirlines, "S7") + //} + //for _, airline := range request.ExcludedAirlines { + // if !utils.InArrayString(airline, excludedAirlines) { + // excludedAirlines = append(excludedAirlines, airline) + // } + //} + + var itineraries, err = ParseListOfFlights(reply.FlightIndex, groupsRef, &FilterRules{ + Itineraries: requestItineraries, + //RecommendationIds: recommendationIds, + //AllowMixing: request.AllowMixing, + OnlyNonStopFlight: !request.Changes, + Airlines: request.Airlines, + //ExcludedAirlines: excludedAirlines, + }, request.BaseClass[0]) + if err != nil { + return nil, err + } + if itineraries == nil || len(itineraries) == 0 { + return nil, nil + } + + var currencyDefault string + // Specifies the currency used for pricing + if reply.ConversionRate != nil { + if len(reply.ConversionRate.ConversionRateDetail) == 1 { + currencyDefault = reply.ConversionRate.ConversionRateDetail[0].Currency + } + } + + var majorClass string + if len(request.BaseClass) == 1 { + majorClass = request.BaseClass[0] + } + + // Service fee information per passenger + var _, freeBagAllowances = parseServiceFeesGrp(reply.ServiceFeesGrp) + + //// int = familyInformation[].RefNumber + //var fareFamily = make(map[int]*structsCommon.FareFamily) + //var fareFamilyServices = make(map[int][]*structsCommon.FareFamilyService) // + //// Details of the fare families processed + //for _, familyInformation := range reply.FamilyInformation { + // var number = int(familyInformation.RefNumber) + // fareFamily[number] = &structsCommon.FareFamily{ + // Name: familyInformation.FareFamilyname, + // Description: familyInformation.Description, + // } + // for _, familyInformationService := range familyInformation.Services { + // if service, exists := services[familyInformationService.Reference]; exists { + // // Codeset for Status, coded (Ref: 4405 1A 14.1.1) + // // CHA - At charge + // // INC - Included + // // NOF - Not offered + // switch familyInformationService.Status { + // case "INC": + // service.Status = "Included" + // case "CHA": + // service.Status = "Charge" + // case "NOF": + // service.Status = "NotOffered" + // } + // fareFamilyServices[number] = append(fareFamilyServices[number], service) + // } + // } //} - return &response + //var skipRecommendationIds []string + var skipSegmentIds, responseSegmentIds []string + + var addSkipSegments = func(segmentIds []string) { + for _, segmentID := range segmentIds { + if utils.InArrayString(segmentID, responseSegmentIds) { + continue + } + if !utils.InArrayString(segmentID, skipSegmentIds) { + skipSegmentIds = append(skipSegmentIds, segmentID) + } + } + } + + var recommendations []*search.Recommendation + var segments = make(map[string]*structsCommon.Flight) + + // Recommendation details + for _, recReply := range reply.Recommendation { + var recommendationID = recReply.ItemNumber.ItemNumberId.Number + var variantRef []map[int][]int + var flightsRef = make(map[int]map[int]*FilterGroupOfFlights) + var baggageRef = make(map[int]int) + for variantIndex, segmentFlightRef := range recReply.SegmentFlightRef { + var itineraryID, baggageID = 1, 0 + var groupOfSegmentsIds = make(map[int][]int) + groupOfSegmentsIds[itineraryID] = []int{} + var skip = false + for _, referencingDetail := range segmentFlightRef.ReferencingDetail { + var refNumber = int(referencingDetail.RefNumber) + switch referencingDetail.RefQualifier { + case "S": + //flightsRef[itineraryID] = append(flightsRef[itineraryID], refNumber) + groupOfSegmentsIds[itineraryID] = append(groupOfSegmentsIds[itineraryID], refNumber) + + var itinerary = itineraries[itineraryID] + if group, exists := itinerary[refNumber]; exists { + if flightsRef[itineraryID] == nil { + flightsRef[itineraryID] = make(map[int]*FilterGroupOfFlights) + } + flightsRef[itineraryID][refNumber] = group + } else { + skip = true + } + itineraryID++ + case "B": + baggageID = refNumber + } + if skip { + break + } + } + if skip { + continue + } + variantRef = append(variantRef, groupOfSegmentsIds) + if baggageID != 0 { + baggageRef[variantIndex] = baggageID + } + } + + if len(variantRef) == 0 { + //skipRecommendationIds = append(skipRecommendationIds, recommendationID) + continue + } + + var recClass []string + + var validatingAirlines []string + var validatingAirlineOverride string + var mapMajorityCarrier = make(map[int]string) + var matchValidatingAirline = true + + var refundableSlice []string + var lastTicketSlice []string + + var perPaxPricing = make(structsCommon.PerPaxType) + + var passengerTypes []string + var adtFareGroups = make(map[int]map[int]*search.FlightDetail) + var otherFareGroups = make(map[int]map[int]map[string]*search.FlightDetail) + + // Passenger fare product details + for _, paxFareProduct := range recReply.PaxFareProduct { + + var passengerType string + if len(paxFareProduct.PaxReference) == 1 { + for _, paxReference := range paxFareProduct.PaxReference { + if len(paxReference.Ptc) == 1 { + for _, ptc := range paxReference.Ptc { + switch ptc { + case structsCommon.TravellerTypeCH: + passengerType = structsCommon.TravellerTypeCHD + case structsCommon.TravellerTypeIN: + passengerType = structsCommon.TravellerTypeINF + default: + passengerType = ptc + } + break + } + } + } + } + if passengerType != "" { + if utils.InArrayString(passengerType, passengerTypes) { + continue + } + } + + for _, fareDetail := range paxFareProduct.FareDetails { + var itineraryID = int(fareDetail.SegmentRef.SegRef) + + adtFareDetails, exists := adtFareGroups[itineraryID] + if !exists { + adtFareDetails = make(map[int]*search.FlightDetail) + } + otherFareDetails, exists := otherFareGroups[itineraryID] + if !exists { + otherFareDetails = make(map[int]map[string]*search.FlightDetail) + } + + for flightIndex, fare := range fareDetail.GroupOfFares { + + var passengerType string + switch fare.ProductInformation.FareProductDetail.PassengerType { + case structsCommon.TravellerTypeCH: + passengerType = structsCommon.TravellerTypeCHD + case structsCommon.TravellerTypeIN: + passengerType = structsCommon.TravellerTypeINF + default: + passengerType = fare.ProductInformation.FareProductDetail.PassengerType + } + + availability, err := strconv.Atoi(fare.ProductInformation.CabinProduct.AvlStatus) + if err != nil { + //log.Println("[strconvError]", "Value is not a number (fare.ProductInformation.CabinProduct.AvlStatus):", err) + //continue + availability = 9 + } + + var flightDetail = &search.FlightDetail{ + // Reservation booking designator - BookingClass. + BookingClass: fare.ProductInformation.CabinProduct.Rbd, + Cabin: fare.ProductInformation.CabinProduct.Cabin, + FareBasisCode: fare.ProductInformation.FareProductDetail.FareBasis, + //FareType: fare.ProductInformation.FareProductDetail.FareType, + Availability: availability, + } + + // https://www.biletik.aero/handbook/pomoshch/perelet/klassy-obsluzhivaniya-v-samoletakh-klassy-aviabiletov/ + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + // * + ** ***** * **+**** + // ** ** * + // * * * + + switch flightDetail.Cabin { + case "A", "F", "P", "R": + flightDetail.FareClass = "F" // 3 = first + case "C", "D", "I", "J", "Z": + flightDetail.FareClass = "C" // 2 = business + default: // "B", "E", "G", "H", "K", "L", "M", "N", "O", "Q", "S", "T", "U", "V", "W", "X", "Y" + flightDetail.FareClass = "Y" // 1 = economic + } + + //if majorClass != "" { + // if !utils.InArrayString(majorClass, recClass) { + // recClass = append(recClass, majorClass) + // } + //} else { + if !utils.InArrayString(flightDetail.FareClass, recClass) { + recClass = append(recClass, flightDetail.FareClass) + } + //} + + if passengerType == structsCommon.TravellerTypeADT { + adtFareDetails[flightIndex] = flightDetail + } else { + perPaxFareDetails, exists := otherFareDetails[flightIndex] + if !exists { + perPaxFareDetails = make(map[string]*search.FlightDetail) + } + perPaxFareDetails[passengerType] = flightDetail + otherFareDetails[flightIndex] = perPaxFareDetails + } + } + adtFareGroups[itineraryID] = adtFareDetails + otherFareGroups[itineraryID] = otherFareDetails + } + + for _, paxReference := range paxFareProduct.PaxReference { + for _, travellerType := range paxReference.Ptc { + + // Company role identification + for _, codeShareDetails := range paxFareProduct.PaxFareDetail.CodeShareDetails { + if codeShareDetails.Company == "" { + continue + } + + // Codeset for Transport stage qualifier (Ref: 8051 1A 07.2.10) + // V - Possible validating carrier – used for price calculation + // X - Excluded Validating carrier + // default: Other Possible Validating carrier + switch codeShareDetails.TransportStageQualifier { + case "V": + if matchValidatingAirline { + if validatingAirlineOverride == "" { + validatingAirlineOverride = codeShareDetails.Company + } else if validatingAirlineOverride != codeShareDetails.Company { + validatingAirlineOverride = "" + matchValidatingAirline = false + + if exists, _ := utils.InArray(codeShareDetails.Company, validatingAirlines); !exists { + validatingAirlines = append(validatingAirlines, codeShareDetails.Company) + } + } + } else { + if exists, _ := utils.InArray(codeShareDetails.Company, validatingAirlines); !exists { + validatingAirlines = append(validatingAirlines, codeShareDetails.Company) + } + } + case "X": + default: + if exists, _ := utils.InArray(codeShareDetails.Company, validatingAirlines); !exists { + validatingAirlines = append(validatingAirlines, codeShareDetails.Company) + } + } + } + + var tax = 0.0 + if paxFareProduct.PaxFareDetail.TotalTaxAmount != nil { + tax = *paxFareProduct.PaxFareDetail.TotalTaxAmount + } + var pricing = &structsCommon.Pricing{ + Currency: currencyDefault, + Total: paxFareProduct.PaxFareDetail.TotalFareAmount, + Base: paxFareProduct.PaxFareDetail.TotalFareAmount - tax, + Tax: tax, + } + for _, monetaryDetail := range paxFareProduct.PaxFareDetail.MonetaryDetails { + + currency := "" + if monetaryDetail.Currency != currencyDefault || currencyDefault == "" { + currency = monetaryDetail.Currency + } + + pricing.AddTax(&structsCommon.Tax{ + // Codeset for Monetary Amount Type Qualifier (Ref: 5025 1A 12.4.2) + // A - Total Additional Collection + // B - The Tax (or surcharge in WQ) total Difference in currency of reissue selling + // BFA - Base fare Amount + // BGT - Grand Total Balance per Requested Segment + // BTA - Tax Balance per Requested Segment + // C - MCO Residual Value + // CR - Converted Recommendation amount used for information and sorting. Conversion rate not guaranteed + // CT - Converted Tax amount used for information and sorting. Conversion rate not guaranteed + // D - Grand Total Difference amount (includes penalty. May be with or without tax and surcharges depending on the request) in currency of reissue selling + // F - Fuel surcharge amount ( New total YQ/YR amount ) + // G - Total amount to be paid in cash + // H - Total of taxes & surcharges to be paid in cash + // I - Total amount to be paid in miles + // J - Minimum amount to be paid in miles + // K - Base Fare Amount in Miles + // M - Grand Total amount (includes penalty. May be with or without tax and surcharges depending on the request) in currency of reissue selling + // MAC - Mileage accrual + // MIL - Miles + // N - New Tax (or surcharge in WQ) total in currency of reissue selling + // NF - No Show fee amount + // OB - Ticketing fees total + // OR - Recommendation amount in original fare currency + // OT - Recommendation Tax amount in original fare currency + // P - Penalty amount in currency of reissue selling + // PNB - Penalty Before Netting per Requested Segment + // PND - PTC non discounted amount + // PO - total amount of polled services + // R - Real Recommendation amount in published currency + // RES - Residual Value per Requested Segment + // S - Sum penalty + // T - Real tax amount in published currency + // TAC - Total Additional Collection per Requested Segment + // TP - Total Penalty amount + // U - US taxes amount + // X - Max penalty + // XOB - Total amount without OB fees + // YQ - YQ amounts included in taxes + // YR - YR amounts included in taxes + Code: monetaryDetail.AmountType, + Amount: monetaryDetail.Amount, + Currency: currency, + }) + } + + switch travellerType { + case structsCommon.TravellerTypeCH: + travellerType = structsCommon.TravellerTypeCHD + case structsCommon.TravellerTypeIN: + travellerType = structsCommon.TravellerTypeINF + } + + if pp, exists := perPaxPricing[travellerType]; exists { + pp.AddUp(pricing) + } else { + perPaxPricing[travellerType] = pricing + } + + for _, fare := range paxFareProduct.Fare { + // Codeset for Text subject qualifier + // 1 - Coded free text + // 3 - Literal text + // 4 - Coded and literal text + // APM - Appended message + // IFC - Internal fare calc + // LTD - Last Date to Ticket + // PEN - Penalties Message + // SUR - Surcharges + // SYS - SYSTEM CHECKS + // TFC - Ticketing fare calc + // WRN - Warning message + switch fare.PricingMessage.FreeTextQualification.TextSubjectQualifier { + case "PEN": + for _, description := range fare.PricingMessage.Description { + switch description { + case "TICKETS ARE NON-REFUNDABLE", + "TICKETS ARE NON REFUNDABLE", + "TICKETS ARE NON REFUNDABLE BEFORE DEPARTURE": + refundableSlice = append(refundableSlice, "No") + case "TICKETS ARE NON REFUNDABLE AFTER DEPARTURE": + refundableSlice = append(refundableSlice, "Yes") + case "PENALTY APPLIES", + "PENALTY APPLIES - CHECK RULES", + "PERCENT PENALTY APPLIES", + "SUBJ TO CANCELLATION/CHANGE PENALTY": + refundableSlice = append(refundableSlice, "Penalty") + default: + //logs.Log.Info("Error Refundable Value:", description) + // TODO log + } + } + case "LTD": + for _, description := range fare.PricingMessage.Description { + if len(description) == 7 { + if lastTicketDate, err := convert.AmadeusDateConvert(description); err == nil { + lastTicketStr := lastTicketDate.Format(configuration.Config.Formats.Time) + if exists, _ := utils.InArray(lastTicketStr, lastTicketSlice); !exists { + lastTicketSlice = append(lastTicketSlice, lastTicketStr) + } + } + } + } + } + } + } + } + } + + if len(adtFareGroups) == 0 { + for itineraryID, otherFareDetails := range otherFareGroups { + if len(otherFareDetails) > 0 { + var adtFareDetails = make(map[int]*search.FlightDetail) + for flightIndex, perPaxFareDetails := range otherFareDetails { + if chdFareDetails, exists := perPaxFareDetails[structsCommon.TravellerTypeCHD]; exists { + adtFareDetails[flightIndex] = chdFareDetails + } + } + if len(adtFareDetails) > 0 { + adtFareGroups[itineraryID] = adtFareDetails + } + } + } + } + + //test + if len(adtFareGroups) == 0 { + //skipRecommendationIds = append(skipRecommendationIds, recommendationID) + continue + } + + for itineraryID, adtFareDetails := range adtFareGroups { + var otherFareDetails = otherFareGroups[itineraryID] + if len(otherFareDetails) == 0 { + continue + } + for flightIndex, flightDetail := range adtFareDetails { + var perPaxFareDetails = otherFareDetails[flightIndex] + var removeKeys []string + for passengerType, testFareDetails := range perPaxFareDetails { + if flightDetail.Equal(testFareDetails) { + removeKeys = append(removeKeys, passengerType) + } + } + if len(removeKeys) > 0 { + for _, removeKey := range removeKeys { + delete(perPaxFareDetails, removeKey) + } + if len(perPaxFareDetails) == 0 { + delete(otherFareDetails, flightIndex) + } + } + } + } + + var interline = false // Рекомендация носит признак interline тогда, когда на одном или более сегментах OA отличается от VA. + + var validatingAirlinesCount = len(validatingAirlines) + if validatingAirlineOverride == "" && (validatingAirlinesCount == 0 || validatingAirlinesCount > 1) { + matchValidatingAirline = true + var airlines []string + + for _, majorityCarrier := range mapMajorityCarrier { + if exists, _ := utils.InArray(majorityCarrier, airlines); !exists { + airlines = append(airlines, majorityCarrier) + } + } + if len(airlines) == 1 { + validatingAirlineOverride = airlines[0] + } + for _, validatingAirline := range airlines { + if exists, _ := utils.InArray(validatingAirline, validatingAirlines); !exists { + validatingAirlines = append(validatingAirlines, validatingAirline) + } + } + } + if len(validatingAirlines) == 1 { + if validatingAirlineOverride == validatingAirlines[0] { + validatingAirlines = []string{} + } else if validatingAirlineOverride == "" { + validatingAirlineOverride = validatingAirlines[0] + validatingAirlines = []string{} + } + } // else { + // interline = true // !!! + //} + + //if validatingAirlineOverride == "S7" { + // if excludedAirlineS7 { + // log.Println("skip recommendation, validatingAirline:", validatingAirlineOverride) + // addSkipRec(recommendationID) + // continue + // } + //} else if request.ResultOnlyS7 { + // addSkipRec(recommendationID) + // continue + //} + + var recSegmentIds []string + + var itineraryRef = make(map[int]map[int][]int) + for variantIndex, groupOfSegmentsIds := range variantRef { + var variantID = variantIndex + 1 + for itineraryID, groupOfSegmentsID := range groupOfSegmentsIds { + for _, n := range groupOfSegmentsID { + var variants, exists = itineraryRef[itineraryID] + if !exists { + variants = make(map[int][]int) + } + variants[variantID] = append(variants[variantID], n) + if !exists { + itineraryRef[itineraryID] = variants + } + } + } + } + + var routesSegments = make(search.RoutesSegments) + var skipVariantIds []int + for itineraryID, itineraryVariants := range itineraryRef { + var itinerary, existsVariants = routesSegments[itineraryID] + if !existsVariants { + itinerary = make(map[int]*search.RouteSegments) + } + var adtFareDetails = adtFareGroups[itineraryID] + var otherFareDetails = otherFareGroups[itineraryID] + for variantID, groupOfSegmentsIds := range itineraryVariants { + if utils.InArrayInt(variantID, skipVariantIds) { + continue + } + var skipVariant = false + for _, groupOfSegmentsID := range groupOfSegmentsIds { + var group = flightsRef[itineraryID][groupOfSegmentsID] + var variantSegmentID []string + var itinerarySegments []*search.ItinerarySegment + for flightIndex, flight := range group.Flights { + var segmentID = flight.SegmentID + variantSegmentID = append(variantSegmentID, segmentID) + + var flightDetail = adtFareDetails[flightIndex] + if flightDetail == nil { + continue + } + var baggage *structsCommon.BaggageType + if flightDetail.Baggage == nil { + if num, exists := baggageRef[variantID-1]; exists { + if freeBagGroups, exists := freeBagAllowances[num]; exists { + if freeBagLegs, exists := freeBagGroups[itineraryID]; exists { + baggage = freeBagLegs[flightIndex+1] + } + } + } + if baggage == nil { + baggage = &structsCommon.BaggageType{ + Unit: "PC", + } + } + } + + // исключение вариантов, у которых багажа нет. + // Нужно было проверить на то, что в запросе не приходит nil (из-за omitempty — herb ,s jnjhdfnm pf nfre. djkmyjcnm!), + // а если не nil, то смотреть, что указано. + // Если стоит, что с багажом, а количество багажа == 0, то скипать предложение. + if request.WithBaggage == true && baggage.Value == 0 { + skipVariantIds = append(skipVariantIds, variantID) + skipVariant = true + break + } + + // исключение по validating airlines + // не смотря на MandatoryCompanies() гдс выдаёт в том числе другие АК, если они попадаются на стыковочных перелетах + // Чтобы они вообще не попадались, отсеим их + // Если надо отсеять по оперирующему или маркетирующему перевозчику, воспользуйтесь CheckRulesCompanies() + for _, airline := range request.Airlines { + if validatingAirlineOverride != airline { + skipVariant = true + break + } + } + + if _, exists := segments[segmentID]; !exists { + segments[segmentID] = flight + } + + if !utils.InArrayString(segmentID, recSegmentIds) { + recSegmentIds = append(recSegmentIds, segmentID) + } + + if !interline { + if flight.OperatingAirline.CodeEng != validatingAirlineOverride { + interline = true + } + } + + var paxDetails = otherFareDetails[flightIndex] + itinerarySegments = append(itinerarySegments, &search.ItinerarySegment{ + SegmentID: segmentID, + FlightDetail: &search.FlightDetail{ + Class: flightDetail.Class, // @TODO Deprecate it! + Cabin: flightDetail.Cabin, + FareClass: flightDetail.FareClass, // @TODO rename it to Subclass ! + BookingClass: flightDetail.BookingClass, + FareBasisCode: flightDetail.FareBasisCode, + Availability: flightDetail.Availability, + Baggage: baggage, + //FareType: flightDetail.FareType, + }, + PaxDetails: paxDetails, + }) + } + if skipVariant { + addSkipSegments(variantSegmentID) + break + } + itinerary[variantID] = &search.RouteSegments{ + GroupOfSegmentsID: groupOfSegmentsID, + ItinerarySegments: itinerarySegments, + TravelTime: group.Duration, + } + } + } + if !existsVariants && len(itinerary) > 0 { + routesSegments[itineraryID] = itinerary + } + } + + //test + if len(perPaxPricing) == 0 || len(routesSegments) == 0 { + //skipRecommendationIds = append(skipRecommendationIds, recommendationID) + addSkipSegments(recSegmentIds) + continue + } + + for _, segmentID := range recSegmentIds { + if exists, i := utils.InArray(segmentID, skipSegmentIds); exists { + skipSegmentIds = append(skipSegmentIds[:i], skipSegmentIds[i+1:]...) + } + if !utils.InArrayString(segmentID, responseSegmentIds) { + responseSegmentIds = append(responseSegmentIds, segmentID) + } + } + + if interline { + for _, variants := range routesSegments { + for _, variant := range variants { + for _, itinerarySegment := range variant.ItinerarySegments { + var flight = segments[itinerarySegment.SegmentID] + itinerarySegment.CodeShare = flight.CodeShare() + } + } + } + } + + var lastTicketDate string + if l := len(lastTicketSlice); l > 0 { + sort.Strings(lastTicketSlice) + lastTicketDate = lastTicketSlice[l-1] + } + + var refundable = "N" + if exists, _ := utils.InArray("No", refundableSlice); !exists { + exists, _ := utils.InArray("Penalty", refundableSlice) + if exists { + refundable = "P" + } else { + exists, _ := utils.InArray("Yes", refundableSlice) + if exists { + refundable = "Y" + } + } + } + + if len(recClass) == 0 && majorClass != "" { + recClass = []string{majorClass} + } + + var recommendation = &search.Recommendation{ + ID: recommendationID, + Provider: configuration.Provider, + Class: recClass, + ItinerarySegments: routesSegments, + ValidatingAirline: structsCommon.Airline{}, + //ValidatingAirlines: validatingAirlines, + Interline: interline, + LastTicketDate: lastTicketDate, + Refundable: refundable, //TODO bool? + } + recommendation.ValidatingAirline.CodeEng = validatingAirlineOverride + + // To describe type of recReply + //for _, warningMessage := range recReply.warningMessage { + // warning := &errs.warningMessage{} + // warning.Parse(warningMessage) + // recommendation.WarningMessages = append(recommendation.WarningMessages, warning) + // log.Println("Recommendation.warningMessage:", warning.String()) + //} + + // Recommendation Pricing and Taxes + for num, monetaryDetail := range recReply.RecPriceInfo.MonetaryDetail { + switch num { + case 0: + recommendation.Price.Total = monetaryDetail.Amount + if recommendation.Price.Total < 2000 { + if _, err := json.Marshal(&request.Itineraries); err == nil { + //logs.Log.Warnf("Price too low. Price: %v, Request: %s", recommendation.Price.Total, string(a)) //TODO log + } + } + case 1: + recommendation.Price.Tax = monetaryDetail.Amount + default: + currency := "" + if monetaryDetail.Currency != currencyDefault || currencyDefault == "" { + currency = monetaryDetail.Currency + } + recommendation.Price.AddTax(&structsCommon.Tax{ + // Codeset for Monetary Amount Type Qualifier (Ref: 5025 1A 14.2.1) + // A - Total Additional Collection + // B - The Tax (or surcharge in WQ) total Difference in currency of reissue selling + // BFA - Base fare Amount + // BGT - Grand Total Balance per Requested Segment + // BTA - Tax Balance per Requested Segment + // C - MCO Residual Value + // CR - Converted Recommendation amount used for information and sorting. Conversion rate not guaranteed + // CT - Converted Tax amount used for information and sorting. Conversion rate not guaranteed + // D - Grand Total Difference amount (includes penalty. May be with or without tax and surcharges depending on the request) in currency of reissue selling + // F - Fuel surcharge amount ( New total YQ/YR amount ) + // FCN - Minimum fee amount for credit card + // FCX - Maximum fee amount for credit card + // FDN - Minimum fee amount for debit card + // FDX - Maximum fee amount for debit card + // FON - Minimum fee amount for any card + // FOX - Maximum fee amount for any card + // G - Total amount to be paid in cash + // H - Total of taxes & surcharges to be paid in cash + // I - Total amount to be paid in miles + // J - Minimum amount to be paid in miles + // K - Base Fare Amount in Miles + // M - Grand Total amount (includes penalty. May be with or without tax and surcharges depending on the request) in currency of reissue selling + // MAC - Mileage accrual + // MIL - Miles + // N - New Tax (or surcharge in WQ) total in currency of reissue selling + // NF - No Show fee amount + // OB - Ticketing fees total + // OR - Recommendation amount in original fare currency + // OT - Recommendation Tax amount in original fare currency + // P - Penalty amount in currency of reissue selling + // PNB - Penalty Before Netting per Requested Segment + // PND - PTC non discounted amount + // PO - total amount of polled services + // R - Real Recommendation amount in published currency + // RES - Residual Value per Requested Segment + // S - Sum penalty + // T - Real tax amount in published currency + // TAC - Total Additional Collection per Requested Segment + // TP - Total Penalty amount + // U - US taxes amount + // X - Max penalty + // XOB - Total amount without OB fees + // YQ - YQ amounts included in taxes + // YR - YR amounts included in taxes + Code: monetaryDetail.AmountType, + Amount: monetaryDetail.Amount, + Currency: currency, + }) + } + } + recommendation.Price.Currency = currencyDefault + recommendation.Price.PerPax = &perPaxPricing + recommendation.Price.RoundUp() + recommendations = append(recommendations, recommendation) + } + + if len(recommendations) == 0 || len(segments) == 0 { + return nil, nil + } + + var result = &search.SearchResponse{ + Segments: make(map[string]*structsCommon.Flight), + } + + for segmentID, segment := range segments { + if utils.InArrayString(segmentID, responseSegmentIds) { + result.Segments[segmentID] = segment + } + } + + var skipRecommendationIds []string + for _, recommendation := range recommendations { + var flag = true + for _, testRecommendation := range result.Recommendations { + if testRecommendation == recommendation || recommendation.ID == testRecommendation.ID { + continue + } + if utils.InArrayString(recommendation.ID, skipRecommendationIds) { + continue + } + if testRecommendation.Combine(recommendation) { + skipRecommendationIds = append(skipRecommendationIds, recommendation.ID) + flag = false + break + } + } + if flag { + result.Recommendations = append(result.Recommendations, recommendation) + } + } + + recommendations = cleanRecomendationsFromDuplicatedItinerarySegments(recommendations) + + return result, nil +} + +// ParseListOfFlights Parse List Of Flights +func ParseListOfFlights(listOfFlights []*FlightIndex, recommendationGroupsRef map[string][][]int, filterRules *FilterRules, baseclass string) (map[int]map[int]*FilterGroupOfFlights, error) { + + // (itineraryId) int = flightIndex[].RequestedSegmentRef.SegRef + // (groupOfSegmentsId) int = flightIndex[].GroupOfFlights[].PropFlightGrDetail.FlightProposal[].Ref + var itineraries = make(map[int]map[int]*FilterGroupOfFlights) + + var itineraryEnd = len(listOfFlights) - 1 + for itineraryIndex, flightIndex := range listOfFlights { + var flightNum = 1 + var itineraryID = int(flightIndex.RequestedSegmentRef.SegRef) + var isDeparture = itineraryID == 1 + var isArrival = itineraryIndex == itineraryEnd + + // List of proposed segments per requested segment + for _, groupOfFlights := range flightIndex.GroupOfFlights { + if filterRules.OnlyNonStopFlight && len(groupOfFlights.FlightDetails) > 1 { + //skipFlightCount++ + continue + } + + var groupOfSegmentsID = -1 + var variant = &FilterGroupOfFlights{ + ItineraryID: itineraryID, + } + + // Parameters for proposed flight group + for _, flightProposal := range groupOfFlights.PropFlightGrDetail.FlightProposal { + // Codeset for Type of Units Qualifier (Ref: 6353 1A 09.1.7) + // CDC - Cross-over date combi + // DUR - Duration for Rail journey + // EFT - Elapse Flying Time + // MCX - Majority carrier + // NAV - No Availability + // NFA - No fare + // NIT - No Itinerary + // NJO - No Journey + // RJS - Rank in Journey Server + switch flightProposal.UnitQualifier { + case "EFT": + variant.Duration = convert.AmadeusTimeToMinutes(flightProposal.Ref) + case "MCX": + variant.MajorityCarrier = flightProposal.Ref + case "": + if ref, err := strconv.Atoi(flightProposal.Ref); err == nil { + groupOfSegmentsID = ref + } else { + //logs.Log.WithError(err).Error("Value is not a number (flightProposal.Ref)") + // TODO log + } + } + } + if groupOfSegmentsID < 0 { + //errorFlightCount++ + continue + } + variant.GroupOfSegmentsID = groupOfSegmentsID + + var end = len(groupOfFlights.FlightDetails) - 1 + // List of flight per proposed segment + for num, flightDetail := range groupOfFlights.FlightDetails { + var flight = &structsCommon.Flight{ + SegmentID: fmt.Sprintf("amadeus_%d_%d_%s", itineraryID, flightNum, baseclass), + } + + // Location of departure and arrival + for locationID, location := range flightDetail.FlightInformation.Location { + if locationID == 0 { + flight.DepartureLocation.AirportCode = location.LocationId + flight.DepartureLocation.Terminal = location.Terminal + } else { + flight.ArrivalLocation.AirportCode = location.LocationId + flight.ArrivalLocation.Terminal = location.Terminal + } + } + + // Date and time of departure and arrival + var dateOfDeparture = flightDetail.FlightInformation.ProductDateTime.DateOfDeparture + var timeOfDeparture = flightDetail.FlightInformation.ProductDateTime.TimeOfDeparture + flight.DepartureDate = convert.AmadeusDateTimeConvert(dateOfDeparture, timeOfDeparture) + var dateOfArrival = flightDetail.FlightInformation.ProductDateTime.DateOfArrival + var timeOfArrival = flightDetail.FlightInformation.ProductDateTime.TimeOfArrival + flight.ArrivalDate = convert.AmadeusDateTimeConvert(dateOfArrival, timeOfArrival) + //flight.TravelTime = int(flight.ArrivalDate.Sub(flight.DepartureDate) / time.Minute) + + // Company identification + flight.OperatingAirline = &structsCommon.Airline{} + flight.OperatingAirline.CodeEng = flightDetail.FlightInformation.CompanyId.OperatingCarrier + + flight.MarketingAirline = &structsCommon.Airline{} + flight.MarketingAirline.CodeEng = flightDetail.FlightInformation.CompanyId.MarketingCarrier + + flight.FlightNumber = flightDetail.FlightInformation.FlightOrtrainNumber + flight.Aircraft = &structsCommon.Aircraft{} // Type of aircraft + flight.Aircraft.Code = &flightDetail.FlightInformation.ProductDetail.EquipmentType + + //if flightDetail.FlightInformation.AddProductDetail.ElectronicTicketing == "Y" { + // flight.ETicket = true + //} + + // + // + // + + //if !filterRules.CheckRulesCompanies(flight) { + // skipGroup = true + // break + //} + + if isDeparture && num == 0 { + if !filterRules.CheckRulesTimes(itineraryIndex, true, false, flight) { + break + } + } + + if isArrival && num == end { + if !filterRules.CheckRulesTimes(itineraryIndex, false, true, flight) { + break + } + } + + // + // + // + + variant.Flights = append(variant.Flights, flight) + flightNum++ + } + + if len(variant.Flights) == 0 { + continue + } + + if dp, ok := itineraries[itineraryID]; ok { + dp[groupOfSegmentsID] = variant + } else { + dp = make(map[int]*FilterGroupOfFlights) + dp[groupOfSegmentsID] = variant + itineraries[itineraryID] = dp + } + } + if _, ok := itineraries[itineraryID]; !ok { + return nil, nil + } + } + + var itineraryMax = len(itineraries) + if itineraryMax != 0 && itineraryMax != len(filterRules.Itineraries) { + return nil, fmt.Errorf("number of itineraries in request (%d) differs from number of itineraries in response (%d)", len(filterRules.Itineraries), itineraryMax) + } + + return itineraries, nil +} + +func parseServiceFeesGrp(serviceFeesGrp []*ServiceFeesGrp) (services map[string]*structsCommon.FareFamilyService, serviceCoverageInfoGrp map[int]map[int]map[int]*structsCommon.BaggageType) { + + services = make(map[string]*structsCommon.FareFamilyService) + //serviceCoverageInfoGrp := make(map[int]map[int]map[int]*structsCommon.BaggageType) + + for _, serviceFeesGroup := range serviceFeesGrp { + + // Codeset for Option (Ref: 9750 1A 13.1.2) + // FBA - Free baggage allowance + // OA - Booking fees + // OB - Ticketing fees + // OC - Service fees + // SSR - Service request + switch serviceFeesGroup.ServiceTypeInfo.CarrierFeeDetails.Type { + case "OC": + + // Description of applicable services + for _, serviceDetail := range serviceFeesGroup.ServiceDetailsGrp { + number := serviceDetail.FeeDescriptionGrp.ItemNumberInfo.ItemNumberDetails.Number + + name, classification, group := "", "", "" + if serviceDetail.FeeDescriptionGrp != nil { + + // Other service information (service description, ...) + if serviceDetail.FeeDescriptionGrp.ServiceDescriptionInfo != nil { + + // To specify the Service Classification of the Service Requirement. + // Codeset for Special requirement type (Ref: 9962 IA 08.3.21) + // F - flight related. Must be associated to a flight + // M - merchandise + // R - Rule busted. Must be associated to a fare component + // T - Ticket. Must be associated to a ticket + // UNK - Unknown + classification = serviceDetail.FeeDescriptionGrp.ServiceDescriptionInfo.ServiceRequirementsInfo.ServiceClassification + + // Specify the Service group. + group = serviceDetail.FeeDescriptionGrp.ServiceDescriptionInfo.ServiceRequirementsInfo.ServiceGroup + } + + if serviceDetail.FeeDescriptionGrp.CommercialName != nil { + name = serviceDetail.FeeDescriptionGrp.CommercialName.FreeText + } + } + + services[number] = &structsCommon.FareFamilyService{ + Name: name, + Type: serviceDetail.ServiceOptionInfo.DataTypeInformation.SubType, + Classification: classification, + Group: group, + } + } + case "FBA": + serviceCoverageInfoGrp = parseFreeBagAllowance(serviceFeesGroup) + } + } + return services, serviceCoverageInfoGrp +} + +func parseFreeBagAllowance(serviceFeesGroup *ServiceFeesGrp) map[int]map[int]map[int]*structsCommon.BaggageType { + + // int = freeBagAllowanceGrp[].ItemNumberInfo.ItemNumberDetails[].Number + // string = freeBaggageAllowance + freeBagAllowanceGroup := make(map[int]*structsCommon.BaggageType) + + // Free baggage allowance information group + for _, bagAllowance := range serviceFeesGroup.FreeBagAllowanceGrp { + var freeBagAllowance structsCommon.BaggageType + freeBagAllowance.Value = int(*bagAllowance.FreeBagAllownceInfo.BaggageDetails.FreeAllowance) + + // Codeset for Allowance or charge qualifier (Ref: 5463 1A 11.1.44) + // N - Number of pieces + // W - Weight + switch bagAllowance.FreeBagAllownceInfo.BaggageDetails.QuantityCode { + case "N": + //if freeBagAllowance.Value != 0 { + freeBagAllowance.Unit = "PC" + //} + case "W": + // Codeset for Measure unit qualifier (Ref: 6411 1A 11.1.327) + // K - Kilograms + // L - Pounds + switch bagAllowance.FreeBagAllownceInfo.BaggageDetails.UnitQualifier { + case "K": + freeBagAllowance.Unit = "Kilograms" + case "L": + freeBagAllowance.Unit = "Pounds" + } + } + + for _, itemNumberDetails := range bagAllowance.ItemNumberInfo.ItemNumberDetails { + freeBagAllowanceGroup[int(*itemNumberDetails.Number)] = &freeBagAllowance + } + + // bagAllowance.FreeBagAllownceInfo.BagTagDetails - ignore... + } + + // int = serviceCoverageInfoGrp[].ItemNumberInfo.ItemNumber.Number + // int = serviceCoverageInfoGrp[].serviceCovInfoGrp[].coveragePerFlightsInfo[].numberOfItemsDetails.refNum + // int = serviceCoverageInfoGrp[].serviceCovInfoGrp[].coveragePerFlightsInfo[].lastItemsDetails[].refOfLeg + // string = freeBaggageAllowance + freeBagAllowances := make(map[int]map[int]map[int]*structsCommon.BaggageType) + + // Service coverage information per passenger + for _, coverageInfoGroup := range serviceFeesGroup.ServiceCoverageInfoGrp { + + // Item reference number for service coverage details + number, err := strconv.Atoi(coverageInfoGroup.ItemNumberInfo.ItemNumber.Number) + if err != nil { + //logs.Log.WithError(err).Error("Value is not a number (coverageInfoGroup.ItemNumberInfo.ItemNumber.Number)") + // todo log + continue + } + + freeBagGroups, existsGroups := freeBagAllowances[number] + if !existsGroups || freeBagGroups == nil { + freeBagGroups = make(map[int]map[int]*structsCommon.BaggageType) + } + + // Service coverage information group + for _, serviceCovInfoGrp := range coverageInfoGroup.ServiceCovInfoGrp { + + // Service reference number + for _, referencingDetail := range serviceCovInfoGrp.RefInfo.ReferencingDetail { + + // Codeset list: 1153 1A 13.1.5 + // F - Free baggage allowance reference + // S - Service reference number + if referencingDetail.RefQualifier == "F" { + freeBagAllowance, exists := freeBagAllowanceGroup[int(referencingDetail.RefNumber)] + if !exists { + continue + } + + // Service coverage information at flight level Matched seat characteristics + for _, coveragePerFlightsInfo := range serviceCovInfoGrp.CoveragePerFlightsInfo { + if coveragePerFlightsInfo.NumberOfItemsDetails.ReferenceQualifier == "RS" { + + itineraryID, err := strconv.Atoi(coveragePerFlightsInfo.NumberOfItemsDetails.RefNum) + if err != nil { + continue + } + + var firstItem = 0 + var lastItems = 0 + var refOfLegs []int + + for _, lastItemsDetails := range coveragePerFlightsInfo.LastItemsDetails { + if lastItemsDetails.FirstItemIdentifier != nil { + firstItem = int(*lastItemsDetails.FirstItemIdentifier) + } + if lastItemsDetails.LastItemIdentifier != nil { + lastItems = int(*lastItemsDetails.LastItemIdentifier) + } + if lastItemsDetails.RefOfLeg != "" { + if refOfLeg, err := strconv.Atoi(lastItemsDetails.RefOfLeg); err == nil { + refOfLegs = append(refOfLegs, refOfLeg) + } + } + } + + if firstItem != 0 && lastItems != 0 { + for i := firstItem; i <= lastItems; i++ { + refOfLegs = append(refOfLegs, i) + } + } + + if len(refOfLegs) == 0 { + refOfLegs = []int{1} + } else { + refOfLegs = utils.UniqueIntSlice(refOfLegs) + } + + freeBagLegs, exists := freeBagGroups[itineraryID] + if !exists || freeBagLegs == nil { + freeBagLegs = make(map[int]*structsCommon.BaggageType) + } + + for _, refOfLeg := range refOfLegs { + freeBagLegs[refOfLeg] = freeBagAllowance + } + + freeBagGroups[itineraryID] = freeBagLegs + } + } + } + } + } + + if !existsGroups { + freeBagAllowances[number] = freeBagGroups + } + } + return freeBagAllowances +} + +func cleanRecomendationsFromDuplicatedItinerarySegments(recommendations []*search.Recommendation) []*search.Recommendation { + for _, recommendation := range recommendations { + for i, routesSegment := range recommendation.ItinerarySegments { + var keys []int + for k := range recommendation.ItinerarySegments[i] { + keys = append(keys, k) + } + sort.Ints(keys) + + for _, k := range keys { + if routesSegment[k] == nil { + continue + } + + for m := k + 1; m < len(keys)+1; m++ { + if routesSegment[m] == nil { + continue + } + + if len(routesSegment[k].ItinerarySegments) != len(routesSegment[m].ItinerarySegments) { + continue + } + + equal := true + + for j, s1 := range routesSegment[k].ItinerarySegments { + if s1.SegmentID != routesSegment[m].ItinerarySegments[j].SegmentID { // тут можно добавить условий для сравнения + equal = false + } + } + + if equal { + delete(recommendation.ItinerarySegments[i], m) + } + } + } + } + } + + return recommendations } diff --git a/structs/fare/masterPricerTravelBoardSearch/v16.3/request/makeQuery.go b/structs/fare/masterPricerTravelBoardSearch/v16.3/request/makeQuery.go index 6a0195b..b778ff0 100644 --- a/structs/fare/masterPricerTravelBoardSearch/v16.3/request/makeQuery.go +++ b/structs/fare/masterPricerTravelBoardSearch/v16.3/request/makeQuery.go @@ -3,10 +3,10 @@ package Fare_MasterPricerTravelBoardSearchRequest_v16_3 // fmptbq143 import ( search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" "github.com/tmconsulting/amadeus-golang-sdk/structs/formats" - "gitlab.teamc.io/tm-consulting/tmc24/avia/layer3/amadeus-agent-go/utils/convert" + "github.com/tmconsulting/amadeus-golang-sdk/utils/convert" ) -func MakeRequest(request *search.Request) *Request { +func MakeRequest(request *search.SearchRequest) *Request { var query = Request{ NumberOfUnit: &NumberOfUnitsType{ @@ -110,27 +110,74 @@ func MakeRequest(request *search.Request) *Request { }) } - for k, i := range request.Itineraries { + var routeCount = len(request.Itineraries) + var useMultiCity = false + //if routeCount > 3 && len(request.BaseClass) > 0 { + // useMultiCity = true + //} else + if routeCount < 4 { + useMultiCity = true + } + for routeID := 1; routeID <= routeCount; routeID++ { + var route = request.Itineraries[routeID] + var firstDateTimeDetail = DateAndTimeDetailsTypeI{} + if route.DepartureDate.Error == nil { + firstDateTimeDetail.Date = formats.Date_DDMMYY(convert.DateToAmadeusDate(route.DepartureDate.Date)) + if route.DepartureDate.TimeFlag { + firstDateTimeDetail.Time = formats.Time24_HHMM(convert.DateToAmadeusTime(route.DepartureDate.Date)) + firstDateTimeDetail.TimeQualifier = formats.AlphaNumericString_Length1To3("TD") + } + } else if route.ArrivalDate.Error == nil { + firstDateTimeDetail.Date = formats.Date_DDMMYY(convert.DateToAmadeusDate(route.ArrivalDate.Date)) + if route.ArrivalDate.TimeFlag { + firstDateTimeDetail.Time = formats.Time24_HHMM(convert.DateToAmadeusTime(route.ArrivalDate.Date)) + firstDateTimeDetail.TimeQualifier = formats.AlphaNumericString_Length1To3("TA") + } + } + + var departureLocation = route.DepartureLocation.AirportCode + if departureLocation == "" { + departureLocation = route.DepartureLocation.CityCode + } + var arrivalLocation = route.ArrivalLocation.AirportCode + if arrivalLocation == "" { + arrivalLocation = route.ArrivalLocation.CityCode + } + var itinerary = Itinerary{ RequestedSegmentRef: &OriginAndDestinationRequestType{ - SegRef: formats.NumericInteger_Length1To2(k), + SegRef: formats.NumericInteger_Length1To2(routeID), }, TimeDetails: &DateAndTimeInformationType_181295S{ - FirstDateTimeDetail: &DateAndTimeDetailsTypeI{ - Date: formats.Date_DDMMYY(convert.DateToAmadeusDate(i.DepartureDate)), - }, - }, - } - - itinerary.DepartureLocalization = &DepartureLocationType{ - DeparturePoint: &ArrivalLocationDetailsType_120834C{ - LocationId: formats.AlphaString_Length3To5(i.DepartureLocation), + FirstDateTimeDetail: &firstDateTimeDetail, }, } - itinerary.ArrivalLocalization = &ArrivalLocalizationType{ - ArrivalPointDetails: &ArrivalLocationDetailsType{ - LocationId: formats.AlphaString_Length3To5(i.ArrivalLocation), - }, + if useMultiCity { + itinerary.DepartureLocalization = &DepartureLocationType{ + DepMultiCity: []*MultiCityOptionType{ + { + LocationId: formats.AlphaString_Length3To5(departureLocation), + }, + }, + } + itinerary.ArrivalLocalization = &ArrivalLocalizationType{ + ArrivalMultiCity: []*MultiCityOptionType{ + { + LocationId: formats.AlphaString_Length3To5(arrivalLocation), + }, + }, + } + } else { + itinerary.DepartureLocalization = &DepartureLocationType{ + DeparturePoint: &ArrivalLocationDetailsType_120834C{ + LocationId: formats.AlphaString_Length3To5(departureLocation), + }, + } + itinerary.ArrivalLocalization = &ArrivalLocalizationType{ + ArrivalPointDetails: &ArrivalLocationDetailsType{ + LocationId: formats.AlphaString_Length3To5(arrivalLocation), + }, + } } query.Itinerary = append(query.Itinerary, &itinerary) diff --git a/structs/fare/masterPricerTravelBoardSearch/v16.3/response/parseReply.go b/structs/fare/masterPricerTravelBoardSearch/v16.3/response/parseReply.go index 620a1ad..7c068f0 100644 --- a/structs/fare/masterPricerTravelBoardSearch/v16.3/response/parseReply.go +++ b/structs/fare/masterPricerTravelBoardSearch/v16.3/response/parseReply.go @@ -2,6 +2,6 @@ package Fare_MasterPricerTravelBoardSearchResponse_v16_3 import search "github.com/tmconsulting/amadeus-golang-sdk/structs/fare/masterPricerTravelBoardSearch" -func (r *Response) ToCommon() *search.Response { +func (r *Response) ToCommon() *search.SearchResponse { return nil } diff --git a/structs/fareFamily.go b/structs/fareFamily.go new file mode 100644 index 0000000..4abb429 --- /dev/null +++ b/structs/fareFamily.go @@ -0,0 +1,11 @@ +package structs + +// FareFamilyService FareFamily Service structure +type FareFamilyService struct { + Name string + //Description string + Type string + Classification string + Group string + Status string //enum("Included","Charge","NotOffered") +} diff --git a/structs/flight.go b/structs/flight.go new file mode 100644 index 0000000..d20d038 --- /dev/null +++ b/structs/flight.go @@ -0,0 +1,105 @@ +package structs + +import ( + "encoding/json" + "time" + + "github.com/tmconsulting/amadeus-golang-sdk/configuration" +) + +// Flight structure +type Flight struct { + SegmentID string `json:"-"` + Aircraft *Aircraft `json:"aircraft,omitempty"` + FlightNumber string `json:"flight_number"` + DepartureLocation Location `json:"departure"` + DepartureDate time.Time `json:"departure_date"` + ArrivalLocation Location `json:"arrival"` + ArrivalDate time.Time `json:"arrival_date"` + MarketingAirline *Airline `json:"marketing_airline"` + OperatingAirline *Airline `json:"operating_airline"` + ValidatingAirline *Airline `json:"validating_airline,omitempty"` + TechStops *int32 `json:"tech_stops,omitempty"` + + //TravelTime int `json:"travel_time,omitempty"` + //ETicket bool `json:"eTicket"` +} + +// MarshalJSON overrides MarshalJSON +func (flight *Flight) MarshalJSON() ([]byte, error) { + //@TODO refactor: перевести на общую структуру + return json.Marshal(struct { + Aircraft *Aircraft `json:"aircraft"` + //AircraftFullName string `json:"aircraft_full_name,omitempty"` + FlightNumber string `json:"marketing_airline_flight_number"` //@TODO Найти и передать номер рейса оперирующей компании + DepartureLocation *Location `json:"departure"` + DepartureDate string `json:"departure_date"` + ArrivalLocation *Location `json:"arrival"` + ArrivalDate string `json:"arrival_date"` + MarketingAirline *Airline `json:"marketing_airline"` + OperatingAirline *Airline `json:"operating_airline"` + //ValidatingAirline string `json:"validating_airline"` + //Codeshare bool `json:"codeshare"` + //TravelTime int `json:"travel_time"` + }{ + Aircraft: flight.Aircraft, + //AircraftFullName: flight.AircraftFullName, + FlightNumber: flight.FlightNumber, + DepartureLocation: &flight.DepartureLocation, + DepartureDate: flight.DepartureDate.Format(configuration.Config.Formats.Time), + ArrivalLocation: &flight.ArrivalLocation, + ArrivalDate: flight.ArrivalDate.Format(configuration.Config.Formats.Time), + MarketingAirline: flight.MarketingAirline, + OperatingAirline: flight.OperatingAirline, + //Codeshare: flight.MarketingAirline != flight.OperatingAirline, + //ValidatingAirline: flight.ValidatingAirline, + //TravelTime: flight.TravelTime, + }) +} + +// UnmarshalJSON overrides UnmarshalJSON +func (flight *Flight) UnmarshalJSON(data []byte) error { + type Flight2 struct { + Aircraft *Aircraft `json:"aircraft,omitempty"` + FlightNumber string `json:"marketing_airline_flight_number"` // marketing_airline_flight_number должно быть такое же как MarshalJSON.FlightNumber + DepartureLocation Location `json:"departure"` + DepartureDate string `json:"departure_date"` + ArrivalLocation Location `json:"arrival"` + ArrivalDate string `json:"arrival_date"` + MarketingAirline Airline `json:"marketing_airline"` + OperatingAirline Airline `json:"operating_airline"` + ValidatingAirline Airline `json:"validating_airline,omitempty"` + //TravelTime int `json:"travel_time,omitempty"` + } + var flight2 Flight2 + if err := json.Unmarshal(data, &flight2); err != nil { + //logs.Log.WithError(err).Error("Error unmarshal json") //todo log + return err + } + flight.Aircraft = flight2.Aircraft + flight.FlightNumber = flight2.FlightNumber + flight.DepartureLocation = flight2.DepartureLocation + departureDate, err := time.Parse(configuration.Config.Formats.Time, flight2.DepartureDate) + if err != nil { + return err + } + + flight.DepartureDate = departureDate + flight.ArrivalLocation = flight2.ArrivalLocation + arrivalDate, err := time.Parse(configuration.Config.Formats.Time, flight2.ArrivalDate) + if err != nil { + return err + } + + flight.ArrivalDate = arrivalDate + flight.MarketingAirline = &flight2.MarketingAirline + flight.OperatingAirline = &flight2.OperatingAirline + flight.ValidatingAirline = &flight2.ValidatingAirline + //flight.TravelTime = flight2.TravelTime + return nil +} + +// CodeShare returns sign of CodeShare flight +func (flight *Flight) CodeShare() bool { + return flight.MarketingAirline != flight.OperatingAirline +} diff --git a/structs/flightDate.go b/structs/flightDate.go new file mode 100644 index 0000000..97799f5 --- /dev/null +++ b/structs/flightDate.go @@ -0,0 +1,177 @@ +package structs + +import ( + "errors" + "time" +) + +// FlightDateTime structure +type FlightDateTime struct { + DateStr string + Date time.Time + TimeFlag bool + Error error +} + +const ( + formatDateTimeHHMMSS = "2006-01-02T15:04:05" + formatDateTimeHHMM = "2006-01-02T15:04" + formatDate = "2006-01-02" + formatTimeHHMMSS = "15:04:05" + formatTimeHHMM = "15:04" + + errMsgEmptyValue = "empty value" + errMsgInvalidFormat = "invalid format date" +) + +// ParseRequest parse FlightDateTime request +func (datetime *FlightDateTime) ParseRequest() error { + datetime.TimeFlag = false + if datetime.DateStr == "" { + datetime.Error = errors.New(errMsgEmptyValue) + return datetime.Error + } + switch len(datetime.DateStr) { + case 25: + result, err := time.Parse(time.RFC3339, datetime.DateStr) + if err != nil { + datetime.Error = err + return datetime.Error + } + + datetime.Date = result + datetime.TimeFlag = true + return nil + + case 19: + result, err := time.Parse(formatDateTimeHHMMSS, datetime.DateStr) + if err != nil { + datetime.Error = err + return datetime.Error + } + + datetime.Date = result + datetime.TimeFlag = true + return nil + + case 16: + result, err := time.Parse(formatDateTimeHHMM, datetime.DateStr) + if err != nil { + datetime.Error = err + return datetime.Error + } + + datetime.Date = result + datetime.TimeFlag = true + return nil + + case 10: + result, err := time.Parse(formatDate, datetime.DateStr) + if err != nil { + datetime.Error = err + return datetime.Error + } + + datetime.Date = result + //datetime.TimeFlag = false + return nil + + } + datetime.Error = errors.New(errMsgInvalidFormat) + return datetime.Error +} + +// FlightDateTimeUniversal structure +type FlightDateTimeUniversal struct { + DateStr string + Date time.Time + DateFlag bool + TimeFlag bool + Error error +} + +// ParseRequest parse FlightDateTimeUniversal +func (date *FlightDateTimeUniversal) ParseRequest() error { + date.DateFlag = false + date.TimeFlag = false + if date.DateStr == "" { + date.Error = errors.New(errMsgEmptyValue) + return date.Error + } + switch len(date.DateStr) { + case 25: + result, err := time.Parse(time.RFC3339, date.DateStr) + if err != nil { + date.Error = err + return date.Error + } + + date.Date = result + date.DateFlag = true + date.TimeFlag = true + return nil + + case 19: + result, err := time.Parse(formatDateTimeHHMMSS, date.DateStr) + if err != nil { + date.Error = err + return date.Error + } + + date.Date = result + date.DateFlag = true + date.TimeFlag = true + return nil + + case 16: + result, err := time.Parse(formatDateTimeHHMM, date.DateStr) + if err != nil { + date.Error = err + return date.Error + } + + date.Date = result + date.DateFlag = true + date.TimeFlag = true + return nil + + case 10: + result, err := time.Parse(formatDate, date.DateStr) + if err == nil { + date.Error = err + return date.Error + } + + date.Date = result + date.DateFlag = true + //date.TimeFlag = false + return nil + + case 8: + result, err := time.Parse(formatTimeHHMMSS, date.DateStr) + if err != nil { + date.Error = err + return date.Error + } + + date.Date = result + //date.DateFlag = false + date.TimeFlag = true + return nil + + case 5: + result, err := time.Parse(formatTimeHHMM, date.DateStr) + if err != nil { + date.Error = err + return date.Error + } + + date.Date = result + //date.DateFlag = false + date.TimeFlag = true + return nil + + } + date.Error = errors.New(errMsgInvalidFormat) + return date.Error +} diff --git a/structs/itinerary.go b/structs/itinerary.go new file mode 100644 index 0000000..d643a37 --- /dev/null +++ b/structs/itinerary.go @@ -0,0 +1,39 @@ +package structs + +import "encoding/json" + +type Itinerary struct { + DepartureLocation Location `json:"departure_location"` + ArrivalLocation Location `json:"arrival_location"` +} + +type Location struct { + AirportCode string `json:"airport_code"` + CountryCode string `json:"country_code"` + CityCode string `json:"city_code"` + Terminal string `json:"terminal"` + Type string `json:"-"` +} + +func (loc *Location) MarshalJSON() ([]byte, error) { + if loc.Type == "P" { + return json.Marshal(struct { + AirportCode string `json:"airport_code"` + Terminal string `json:"terminal"` + }{ + AirportCode: loc.AirportCode, + Terminal: loc.Terminal, + }) + } + return json.Marshal(struct { + AirportCode string `json:"airport_code"` + CountryCode string `json:"country_code"` + CityCode string `json:"city_code"` + Terminal string `json:"terminal"` + }{ + AirportCode: loc.AirportCode, + CountryCode: loc.CountryCode, + CityCode: loc.CityCode, + Terminal: loc.Terminal, + }) +} diff --git a/structs/price.go b/structs/price.go new file mode 100644 index 0000000..e119715 --- /dev/null +++ b/structs/price.go @@ -0,0 +1,266 @@ +package structs + +import ( + "encoding/json" + "math" +) + +// Price structure +type Price struct { + Currency string `json:"currency,omitempty"` + Total float64 `json:"total_amount"` + Base float64 `json:"fare_amount"` + Tax float64 `json:"tax_amount"` + Taxes []*Tax `json:"taxes,omitempty"` + //Fee float64 `json:"fee_amount"` + //Vats []*Vat `json:"vats"` + PerPax *PerPaxType `json:"pax_fare,omitempty"` +} + +// Amount structure +type Amount struct { + Value int64 `json:"amount"` + Currency string `json:"currency"` +} + +// MarshalJSON overrides MarshalJSON +func (price *Price) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Total Amount `json:"total_amount"` + Base Amount `json:"fare_amount"` + Tax Amount `json:"tax_amount"` + Taxes []*Tax `json:"taxes,omitempty"` + //Fee Amount `json:"fee_amount"` + //Vats []*Vat `json:"vats"` + PerPax *PerPaxType `json:"pax_fare,omitempty"` + }{ + Total: Amount{ + Currency: price.Currency, + Value: int64(price.Total * 100), + }, + Base: Amount{ + Currency: price.Currency, + Value: int64(price.Base * 100), + }, + Tax: Amount{ + Currency: price.Currency, + Value: int64(price.Tax * 100), + }, + Taxes: price.Taxes, + PerPax: price.PerPax, + }) +} + +// UnmarshalJSON overrides UnmarshalJSON +func (price *Price) UnmarshalJSON(data []byte) error { + return nil +} + +// RoundUp rounds price to greater value +func (price *Price) RoundUp() { + price.Base = math.Ceil(price.Total - price.Tax) + price.Tax = math.Ceil(price.Tax) + + if totalDiff := math.Abs((price.Base + price.Tax) - price.Total); totalDiff != 0.0 { // totalDiff < 2.0 + price.Total = price.Base + price.Tax + } +} + +// AddTax Add Tax to Price.Taxes +func (price *Price) AddTax(tax *Tax) { + price.Taxes = append(price.Taxes, tax) +} + +// Equal compare price.Total +func (price *Price) Equal(price2 *Price) bool { + if price.Total == price2.Total { + return true + } + return false +} + +// PerPaxType map [Ptc] of Pricing +type PerPaxType map[string]*Pricing + +// Pricing structure +type Pricing struct { + Currency string + Total float64 + Base float64 + Tax float64 + Taxes []*Tax + NumberOfPassengers int + //Type string +} + +// MarshalJSON overrides MarshalJSON +func (pricing *Pricing) MarshalJSON() ([]byte, error) { + //if pricing.Type == "P" { + // return json.Marshal(struct { + // Pricing *Pricing `json:"pricing"` + // }{ + // Pricing: &Pricing{ + // Currency: pricing.Currency, + // Total: pricing.Total, + // Base: pricing.Base, + // Tax: pricing.Tax, + // Taxes: pricing.Taxes, + // }, + // }) + //} + //return json.Marshal(struct { + // Currency string `json:"currency,omitempty"` + // Total float64 `json:"total_amount"` + // Base float64 `json:"fare_amount"` + // Tax float64 `json:"tax_amount"` + // Taxes []*Tax `json:"taxes,omitempty"` + //}{ + // Currency: pricing.Currency, + // Total: pricing.Total, + // Base: pricing.Base, + // Tax: pricing.Tax, + // Taxes: pricing.Taxes, + //}) + return json.Marshal(struct { + Total Amount `json:"total_amount"` + Base Amount `json:"fare_amount"` + Tax Amount `json:"tax_amount"` + Taxes []*Tax `json:"taxes,omitempty"` + }{ + Total: Amount{ + Currency: pricing.Currency, + Value: int64(pricing.Total * 100), + }, + Base: Amount{ + Currency: pricing.Currency, + Value: int64(pricing.Base * 100), + }, + Tax: Amount{ + Currency: pricing.Currency, + Value: int64(pricing.Tax * 100), + }, + Taxes: pricing.Taxes, + }) +} + +// UnmarshalJSON overrides UnmarshalJSON +func (pricing *Pricing) UnmarshalJSON(data []byte) error { + return nil +} + +// AddUp add pricing.Taxes values rounded to greater +func (pricing *Pricing) AddUp(plus *Pricing) { + pricing.Base = math.Ceil(pricing.Base + plus.Base) + pricing.Tax = math.Ceil(pricing.Tax + plus.Tax) + + pricing.Total = pricing.Base + pricing.Tax + + for _, tax := range plus.Taxes { + pricing.Taxes = append(pricing.Taxes, tax) + } +} + +// AddTax add pricing.Taxes +func (pricing *Pricing) AddTax(tax *Tax) { + pricing.Taxes = append(pricing.Taxes, tax) +} + +// Tax structure +type Tax struct { + Amount float64 `json:"amount"` + Code string `json:"code"` + Country string `json:"country,omitempty"` + Currency string `json:"currency,omitempty"` +} + +// MarshalJSON overrides MarshalJSON +func (tax *Tax) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Amount `json:"costs"` + Code string `json:"code"` + }{ + Amount: Amount{ + Currency: tax.Currency, + Value: int64(tax.Amount * 100), + }, + Code: tax.Code, + }) +} + +// UnmarshalJSON overrides UnmarshalJSON +func (tax *Tax) UnmarshalJSON(data []byte) error { + return nil +} + +//type Vat struct { +// Currency string `json:"currency"` +// Type int `json:"type"` +// Amount float64 `json:"amount"` +//} + +/* + "price": { + "total_amount": { + "amount": 250000, + "currency": "RUB" + }, + "fare_amount": { + "amount": 100000, + "currency": "RUB" + }, + "tax_amount": { + "amount": 150000, + "currency": "RUB" + }, + "taxes": [ + { + "costs": { + "amount": 150000, + "currency": "RUB" + }, + "code": "ZZ" + } + ] + } +*/ + +// PriceV2 ver 2 +type PriceV2 struct { + Total Amount `json:"total_amount"` + Base Amount `json:"fare_amount"` + Tax Amount `json:"tax_amount"` + Taxes []*TaxV2 `json:"taxes,omitempty"` + //Fee Amount `json:"fee_amount"` + //Vats []*VatV2 `json:"vats"` + PerPax *PerPaxTypeV2 `json:"pax_fare,omitempty"` +} + +// TaxV2 Tax ver 2 +type TaxV2 struct { + Amount `json:"costs"` + Code string `json:"code"` // taxType.isoCountry + Nature string `json:"nature"` // taxNature +} + +// PerPaxTypeV2 map of [Ptc] PricingV2 +type PerPaxTypeV2 map[string]*pricingV2 + +type pricingV2 struct { + Total Amount `json:"total_amount"` + Base Amount `json:"fare_amount"` + Tax Amount `json:"tax_amount"` + Taxes []*TaxV2 `json:"taxes,omitempty"` + Vats []*VatV2 `json:"vats,omitempty"` +} + +//VatV2 Новая структура НДС +type VatV2 struct { + Rate uint `json:"rate"` + Amount int64 `json:"amount"` + Base string `json:"base"` +} + +//type VatV2 struct { +// Amount `json:"costs"` +// Type int `json:"type"` +//} diff --git a/structs/travellers.go b/structs/travellers.go new file mode 100644 index 0000000..859b82d --- /dev/null +++ b/structs/travellers.go @@ -0,0 +1,17 @@ +package structs + +// TravellerTypes Ptc constants +const ( + TravellerTypeADT = "ADT" + TravellerTypeCH = "CH" + TravellerTypeCHD = "CHD" + TravellerTypeINF = "INF" + TravellerTypeIN = "IN" +) + +// Travellers types structure +type Travellers struct { + ADT int `json:"ADT,omitempty"` + CHD int `json:"CHD,omitempty"` + INF int `json:"INF,omitempty"` +} diff --git a/utils/convert/convertTime.go b/utils/convert/convertTime.go new file mode 100644 index 0000000..eba202a --- /dev/null +++ b/utils/convert/convertTime.go @@ -0,0 +1,268 @@ +package convert + +import ( + "errors" + "fmt" + "strconv" + "time" + + "github.com/metakeule/fmtdate" +) + +// AmadeusTimeToMinutes converts amadeus time to minutes +// 1504 to minutes int +func AmadeusTimeToMinutes(timeStr string) int { + if timeStr == "" { + return 0 + } + hours, err := strconv.Atoi(timeStr[0:2]) + if err != nil { + hours = 0 + } + minutes, err := strconv.Atoi(timeStr[2:]) + if err != nil { + minutes = 0 + } + return hours*60 + minutes +} + +// AmadeusDateConvert convert 01JAN95 to time +func AmadeusDateConvert(timeStr string) (time.Time, error) { + result := time.Unix(0, 0) + if timeStr == "" { + return result, errors.New("string empty") + } + day, err := strconv.Atoi(timeStr[0:2]) + if err != nil { + return result, err + } + if day < 1 || day > 31 { + return result, errors.New("invalid day format") + } + monthStr := timeStr[2:5] + month := time.Month(0) + switch monthStr { + case "JAN": + month = time.January + case "FEB": + month = time.February + case "MAR": + month = time.March + case "APR": + month = time.April + case "MAY": + month = time.May + case "JUN": + month = time.June + case "JUL": + month = time.July + case "AUG": + month = time.August + case "SEP": + month = time.September + case "OCT": + month = time.October + case "NOV": + month = time.November + case "DEC": + month = time.December + default: + return result, errors.New("invalid month format") + } + year, err := strconv.Atoi(timeStr[5:7]) + if err != nil { + return result, err + } + if year < 25 { + year += 2000 + } else { + year += 1900 + } + return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil +} + +// AmadeusDateTimeConvert converts 010219 to time +func AmadeusDateTimeConvert(dateStr string, timeStr string) time.Time { + if dateStr == "" { + return time.Unix(0, 0) + } + day, _ := strconv.Atoi(dateStr[0:2]) + month, _ := strconv.Atoi(dateStr[2:4]) + year, _ := strconv.Atoi(dateStr[4:6]) + if year < 25 { + year += 2000 + } else { + year += 1900 + } + hour, minute := 0, 0 + if timeStr != "" { + hour, _ = strconv.Atoi(timeStr[0:2]) + minute, _ = strconv.Atoi(timeStr[2:4]) + } + return time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.UTC) +} + +// DateTimeToInt convert time to int +func DateTimeToInt(date time.Time) (d, t int) { + var hours = date.Hour() * 100 + var minutes = date.Minute() + var year = strconv.Itoa(date.Year()) + var str = fmt.Sprintf("%02d%02d%s", date.Day(), date.Month(), year[2:4]) + if i, err := strconv.Atoi(str); err == nil { + d = i + } + t = hours + minutes + return +} + +// DateToAmadeusDate converts time to string ddmmyy +func DateToAmadeusDate(date time.Time) string { + year := strconv.Itoa(date.Year()) + return fmt.Sprintf("%02d%02d%s", date.Day(), date.Month(), year[2:4]) +} + +// DateToFormatDDMMMYY convert time to 01JAN96 +func DateToFormatDDMMMYY(date time.Time) string { + month, year := "", strconv.Itoa(date.Year()) + switch date.Month() { + case time.January: + month = "JAN" + case time.February: + month = "FEB" + case time.March: + month = "MAR" + case time.April: + month = "APR" + case time.May: + month = "MAY" + case time.June: + month = "JUN" + case time.July: + month = "JUL" + case time.August: + month = "AUG" + case time.September: + month = "SEP" + case time.October: + month = "OCT" + case time.November: + month = "NOV" + case time.December: + month = "DEC" + } + return fmt.Sprintf("%02d%s%s", date.Day(), month, year[2:4]) +} + +// ParseFormatDDMMYY converts time to string +func ParseFormatDDMMYY(str string) (time.Time, error) { + if str == "" { + return time.Time{}, errors.New("empty string") + } + return fmtdate.Parse("DDMMYY", str) +} + +// UpdateTimeFormatHHMM set time with str +func UpdateTimeFormatHHMM(date time.Time, str string) (time.Time, error) { + if str == "" { + return date, errors.New("empty string") + } + result, err := fmtdate.Parse("hhmm", str) + if err != nil { + return date, err + } + return time.Date(date.Year(), date.Month(), date.Day(), result.Hour(), result.Minute(), 0, 0, time.UTC), nil +} + +// DateToAmadeusTime returns time in 1504 format +func DateToAmadeusTime(date time.Time) string { + return fmt.Sprintf("%02d%02d", date.Hour(), date.Minute()) +} + +// DateToAmadeusTimeInt32 converts time to 1504 int32 +func DateToAmadeusTimeInt32(date time.Time) int32 { + var hours = int32(date.Hour()) * 100 + var minutes = int32(date.Minute()) + return hours + minutes +} + +// TimeToToAmadeusTimeFloat64 converts time to 1504 float64 +func TimeToToAmadeusTimeFloat64(date time.Time) float64 { + var hours = int32(date.Hour()) * 100 + var minutes = int32(date.Minute()) + return float64(hours + minutes) +} + +// ParseDateFormatISO8601 returns time from ISO8601 string +func ParseDateFormatISO8601(str string) (time.Time, error) { + switch len(str) { + case 16: + return time.Parse("2006-01-02T15:04", str) + case 19: + return time.Parse("2006-01-02T15:04:05", str) + } + var date time.Time + return date, errors.New("invalid format date") +} + +// ParseDateFormatDDMMYYYY returns time from "DD.MM.YYYY" format +func ParseDateFormatDDMMYYYY(str string) (time.Time, error) { + return fmtdate.Parse("DD.MM.YYYY", str) +} + +// ParseDateFormatDDMMYYYY2 returns time from "DDMMYYYY" format +func ParseDateFormatDDMMYYYY2(str string) (time.Time, error) { + return fmtdate.Parse("DDMMYYYY", str) +} + +// ParseDateFormatDDMMYY returns time from "DDMMYY" format +func ParseDateFormatDDMMYY(str string) (time.Time, error) { + return fmtdate.Parse("DDMMYY", str) +} + +// DateStrToTime attempts convert string to time in different formates +func DateStrToTime(dateStr string) (time.Time, error) { + var result time.Time + switch len(dateStr) { + case 10: + date, err := fmtdate.Parse("YYYY-MM-DD", dateStr) + if err != nil { + date, err = fmtdate.Parse("YYYY.MM.DD", dateStr) + if err != nil { + date, err = time.Parse("02.01.2006", dateStr) + if err != nil { + return result, err + } + } + } + return date, nil + case 15: + date, err := fmtdate.Parse("YYYY-MM-DDhh:mm", dateStr) + if err != nil { + date, err = fmtdate.Parse("YYYY.MM.DDhh:mm", dateStr) + if err != nil { + return result, err + } + } + return date, nil + case 16: + date, err := fmtdate.Parse("YYYY-MM-DDThh:mm", dateStr) + if err != nil { + date, err = fmtdate.Parse("YYYY.MM.DDThh:mm", dateStr) + if err != nil { + return result, err + } + } + return date, nil + case 19: + date, err := fmtdate.Parse("YYYY-MM-DDThh:mm:ss", dateStr) + if err != nil { + date, err = fmtdate.Parse("YYYY.MM.DDThh:mm:ss", dateStr) + if err != nil { + return result, err + } + } + return date, nil + default: + return result, errors.New("unknown date format") + } +} diff --git a/utils/slice.go b/utils/slice.go new file mode 100644 index 0000000..29e7b4d --- /dev/null +++ b/utils/slice.go @@ -0,0 +1,130 @@ +package utils + +import ( + "reflect" + "unicode" +) + +// InArray это реализация in_array на go: https://codereview.stackexchange.com/questions/60074/in-array-in-go +func InArray(val interface{}, array interface{}) (exists bool, index int) { + exists = false + index = -1 + + if reflect.TypeOf(array).Kind() == reflect.Slice { + s := reflect.ValueOf(array) + + for i := 0; i < s.Len(); i++ { + if reflect.DeepEqual(val, s.Index(i).Interface()) { + index = i + exists = true + return + } + } + } + + return +} + +// InArrayInt returns true if val exists in array +func InArrayInt(val int, array []int) bool { + + for _, test := range array { + if test == val { + return true + } + } + + return false +} + +// InArrayString returns true if val exists in array +func InArrayString(val string, array []string) bool { + + for _, test := range array { + if test == val { + return true + } + } + + return false +} + +// UniqueIntSlice returns unique values in slice +func UniqueIntSlice(intSlice []int) []int { + keys := make(map[int]bool) + var list []int + for _, entry := range intSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + +// RemoveItemInt remove item from array +func RemoveItemInt(val int, array []int) (list []int) { + + for _, v := range array { + if v == val { + continue + } else { + list = append(list, v) + } + } + + return +} + +// RemoveItemString remove item from array +func RemoveItemString(val string, array []string) (list []string) { + + for _, v := range array { + if v == val { + continue + } else { + list = append(list, v) + } + } + + return +} + +// IsLetter vwhether s letter +func IsLetter(s string) bool { + for _, r := range s { + //if !unicode.IsLetter(r) { + // return false + //} + if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') { + return false + } + } + return true +} + +// IsLatterAndDigit returns bool if s is Letter or digit +func IsLatterAndDigit(s string) bool { + for _, r := range s { + if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && !unicode.IsDigit(r) { + return false + } + } + return true +} + +// IsEnglishName returns true if all symbols is latin +func IsEnglishName(s string) bool { + if s == "" { + return false + } + for _, r := range s { + if r == ' ' || r == '-' { + continue + } + if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') { + return false + } + } + return true +}