From 69853b026b20cf39959d36c9fac3d6fd29efc977 Mon Sep 17 00:00:00 2001 From: Senad Uka Date: Fri, 1 Jun 2018 10:39:46 +0200 Subject: [PATCH] Upstream sync --- .../applicationservice/applicationservice.go | 5 +- application/applicationservice/user.go | 230 ++++++++++++++- application/entitymapping/user.go | 2 + application/viewmodel/bxe.go | 13 +- application/viewmodel/error.go | 16 ++ application/viewmodel/user.go | 1 + config.dev.toml | 1 + config.prd.toml | 7 +- config.stg.toml | 1 + data/datamysql/user.go | 9 +- domain/entity/user.go | 1 + infra/config/config.go | 20 +- server/router/eligibilityroute/controller.go | 198 ++----------- server/router/externalroute/controller.go | 20 +- server/router/lyfthookroute/controller.go | 8 +- server/router/selfregisterroute/controller.go | 36 ++- server/router/tncroute/controller.go | 16 +- server/router/twilioroute/controller.go | 11 +- server/router/usersroute/controller.go | 162 +---------- server/router/visitroute/controller.go | 264 ++---------------- 20 files changed, 427 insertions(+), 594 deletions(-) create mode 100644 application/viewmodel/error.go diff --git a/application/applicationservice/applicationservice.go b/application/applicationservice/applicationservice.go index 6bbbaea..5cf4b7c 100644 --- a/application/applicationservice/applicationservice.go +++ b/application/applicationservice/applicationservice.go @@ -5,6 +5,7 @@ import ( "bitbucket.org/nemt/nemt-portal-api/application/entitymapping" "bitbucket.org/nemt/nemt-portal-api/application/notificationservice" + "bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi" "bitbucket.org/nemt/nemt-portal-api/domain/service" "bitbucket.org/nemt/nemt-portal-api/infra/config" ) @@ -30,8 +31,10 @@ type Service struct { // New returns a new domain Service instance func New(svc *service.Service, mapper *entitymapping.Mapper, notification *notificationservice.Service, cfg *config.Config) *Service { once.Do(func() { + bcbsi := bcbsi.New(cfg) + instance = &Service{ - Users: newUserService(svc, mapper), + Users: newUserService(svc, mapper, bcbsi, cfg), Rides: newRideService(svc, mapper), Visits: newVisitService(svc, mapper), Provider: newProviderService(svc, mapper), diff --git a/application/applicationservice/user.go b/application/applicationservice/user.go index 0b588c5..a0abcf4 100644 --- a/application/applicationservice/user.go +++ b/application/applicationservice/user.go @@ -1,24 +1,40 @@ package applicationservice import ( + "context" + "encoding/xml" + "fmt" + "html" + "math/rand" + "strings" + "time" + "bitbucket.org/nemt/nemt-portal-api/application/entitymapping" + "bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi" "bitbucket.org/nemt/nemt-portal-api/application/viewmodel" "bitbucket.org/nemt/nemt-portal-api/domain/entity" "bitbucket.org/nemt/nemt-portal-api/domain/service" + "bitbucket.org/nemt/nemt-portal-api/infra/config" "bitbucket.org/nemt/nemt-portal-api/infra/errors" + "bitbucket.org/nemt/nemt-portal-api/server/validation" + "googlemaps.github.io/maps" ) // userService holds methods to user application service type userService struct { svc *service.Service mapEntity *entitymapping.Mapper + bcbsi *bcbsi.Service + cfg *config.Config } // newUserService returns a userService instance -func newUserService(svc *service.Service, mapper *entitymapping.Mapper) *userService { +func newUserService(svc *service.Service, mapper *entitymapping.Mapper, bcbsi *bcbsi.Service, cfg *config.Config) *userService { return &userService{ svc: svc, mapEntity: mapper, + bcbsi: bcbsi, + cfg: cfg, } } @@ -184,3 +200,215 @@ func (s *userService) RemoveContact(contact viewmodel.Contact) (retVal viewmodel return s.mapEntity.User.ToContactModel(entity), err } + +func (s *userService) rangeIn(low, hi int) string { + result := low + rand.Intn(hi-low) + return fmt.Sprintf("%v", result) +} + +func (s *userService) generatePassword(n int) string { + const ( + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + ) + + return s.stringWithCharset(n, charset) +} + +func (s *userService) stringWithCharset(length int, charset string) string { + b := make([]byte, length) + var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +func (s *userService) CheckAndCreateMember(user viewmodel.User, provider viewmodel.ProviderResp, authorUser viewmodel.User) (viewmodel.User, error) { + if validationErrors := validation.ValidateEligibility(&user); len(validationErrors) > 0 { + return viewmodel.User{}, &viewmodel.ValidationError{Message: "Eligibility validation failed", Errors: validationErrors} + } + + entityUser := s.mapEntity.User.ToUserEntity(user) + entityAuthorUser := s.mapEntity.User.ToUserEntity(authorUser) + entityProvider := s.mapEntity.Provider.ToProviderRespEntity(provider) + + newEmail := entityUser.Email + newPhoneNumber := entityUser.PhoneNumber + + var err error + + if entityUser.UUID != "" { + entityUser, err = s.svc.Users.GetByUUID(entityUser.UUID, "US") + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding user by UUID: %s", err.Error())} + } + } else { + entityUser, err = s.svc.Users.GetByMemberID(entityUser.Member) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding user by Subscriber ID: %s", err.Error())} + } + } + + var eligibility viewmodel.Eligibility + loc, _ := time.LoadLocation("America/Chicago") + eligibility.TrackingID = s.rangeIn(1000000, 9999999) + eligibility.ServiceInfo.DateOfService = time.Now().In(loc) + eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"} + + if entityProvider.InternalID == "" && entityProvider.ProviderUUID != "" { + entityProvider, err = s.svc.Provider.GetByUUID(entityProvider.ProviderUUID, entityAuthorUser) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding provider by UUID: %s", err.Error())} + } + } else { + entityProvider, err = s.svc.Provider.GetByNPI(entityProvider.InternalID, entityAuthorUser) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding provider by NPI: %s", err.Error())} + } + } + + if entityProvider.InternalID == "" { + return viewmodel.User{}, &viewmodel.ValidationError{Message: "Provider not found"} + } else { + eligibility.Provider.ProviderNPI = entityProvider.InternalID + eligibility.Provider.ProviderName = entityProvider.OrganizatioName + } + + plan, err := s.svc.Plans.GetByAlphaPrefix(entityUser.Member[:3]) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error finding Plan: %s", err.Error())} + } + eligibility.Payer.PayerID = fmt.Sprintf("%v", plan.PayerID) + eligibility.Payer.PayerName = plan.PayerName + + eligibility.Subscriber.SubscriberID = entityUser.Member + eligibility.Subscriber.PatientType = entityUser.Type + eligibility.Subscriber.Name.First = strings.Split(entityUser.Name, " ")[0] + eligibility.Subscriber.Name.Last = strings.Split(entityUser.Name, " ")[1] + eligibility.Subscriber.DemographicInfo.DateOfBirth = entityUser.BirthDate + eligibility.Subscriber.DemographicInfo.Gender = entityUser.Gender + + ret, err := s.bcbsi.BXE.CheckEligibility(eligibility) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error checking eligibility: %s", err.Error())} + } + + if ret.QueryResult.QueryResultSummary.BenefitsFound { + xmlString := html.UnescapeString(ret.QueryResult.HIPPA271.T271) + xmlReader := strings.NewReader(xmlString) + + var f viewmodel.Interchange271 + err = xml.NewDecoder(xmlReader).Decode(&f) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error unmarshaling 271 response: %s", err.Error())} + } + + header := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 + body := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 + zipCode := strings.TrimSpace(body.N403) + + if len(strings.TrimSpace(zipCode)) > 5 { + zipCode = strings.TrimSpace(zipCode)[:5] + } + + if !s.cfg.App.DisableZipValidation { + _, err = s.svc.Zipcodes.GetByParticipatingZipcode(zipCode) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Member's Home zipcode, %s, is not currently eligible for participation in this program", zipCode)} + } + } + + if entityUser.ID == 0 { + profile, err := s.svc.Profile.GetByKey("US") + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error getting user profile: %s", err.Error())} + } + entityUser.Profiles = append(entityUser.Profiles, profile) + + if len(entityUser.PhoneNumber) == 10 && !strings.Contains(entityUser.PhoneNumber, "+1") { + phoneNumber := fmt.Sprintf("+1%s", entityUser.PhoneNumber) + entityUser.PhoneNumber = phoneNumber + } + + entityUser.Test = false + entityUser, err = s.svc.Users.Create(entityUser) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error creating user: %s", err.Error())} + } + } else { + if len(newEmail) > 0 && newEmail != entityUser.Email { + entityUser.Email = newEmail + } + + if len(newPhoneNumber) > 0 && len(newPhoneNumber) == 10 && !strings.Contains(newPhoneNumber, "+1") { + newPhoneNumber = fmt.Sprintf("+1%s", newPhoneNumber) + } + + if len(newPhoneNumber) > 0 && len(newPhoneNumber) == 12 && strings.Contains(newPhoneNumber, "+1") { + entityUser.PhoneNumber = newPhoneNumber + } + + err = s.svc.Users.UpdateLogin(entityUser) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error updating login data: %s", err.Error())} + } + } + + address := entity.Address{} + address.AddressType = entity.Params{ + Key: "home", + Name: "Home", + } + address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) + address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode) + address.CreatedUser = entityAuthorUser + address.User = entityUser + address.Origin = entity.Params{ + Key: "provider", + Name: "Provider", + } + + googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error instanciating google maps: %s", err.Error())} + } + + r := &maps.GeocodingRequest{ + Address: address.Address + " " + body.N402 + ", " + zipCode, + } + + result, err := googleMapsAPI.Geocode(context.Background(), r) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error getting geocode info: %s", err.Error())} + } + + if len(result) > 0 { + address.Latitude = result[0].Geometry.Location.Lat + address.Longitude = result[0].Geometry.Location.Lng + } + + if len(user.Addresses) > 0 { + for _, a := range user.Addresses { + if a.AddressType == "home" { + err := s.svc.Users.RemoveAddress(a.UUID) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error removing old address: %s", err.Error())} + } + } + } + } + + address, err = s.svc.Users.SaveAddress(address) + if err != nil { + return viewmodel.User{}, &viewmodel.ValidationError{Message: fmt.Sprintf("Error saving new address: %s", err.Error())} + } + entityUser.Addresses = append(entityUser.Addresses, address) + + return s.mapEntity.User.ToUserModel(entityUser), nil + } else { + return viewmodel.User{}, &viewmodel.ValidationError{Message: "No benefits found for the member"} + } + + return viewmodel.User{}, nil +} diff --git a/application/entitymapping/user.go b/application/entitymapping/user.go index 156c8ac..8a73497 100644 --- a/application/entitymapping/user.go +++ b/application/entitymapping/user.go @@ -51,6 +51,7 @@ func (mapping *userMapping) ToUserModel(item entity.User) viewmodel.User { Organizations: mapping.mapper.Organization.ToOrganizationModelSlice(item.Organizations), Types: mapping.mapper.Organization.ToOrganizationTypeModelSlice(item.Types), Type: &item.Type, + Test: item.Test, } } @@ -78,6 +79,7 @@ func (mapping *userMapping) ToUserEntity(item viewmodel.User) entity.User { Profiles: mapping.mapper.Profile.ToProfileEntitySlice(item.Profiles), Organizations: mapping.mapper.Organization.ToOrganizationEntitySlice(item.Organizations), Types: mapping.mapper.Organization.ToOrganizationTypeEntitySlice(item.Types), + Test: item.Test, } if item.Type == nil { diff --git a/application/viewmodel/bxe.go b/application/viewmodel/bxe.go index b68db8d..0196d58 100644 --- a/application/viewmodel/bxe.go +++ b/application/viewmodel/bxe.go @@ -6,12 +6,13 @@ import ( ) type Eligibility struct { - TrackingID string `json:"tracking_id,omitempty"` - Payer Payer `json:"payer,omitempty"` - Provider Provider `json:"provider,omitempty"` - Subscriber Subscriber `json:"subscriber,omitempty"` - ServiceInfo ServiceInfo `json:"service_info,omitempty"` - User User `json:"user,omitempty"` + TrackingID string `json:"tracking_id,omitempty"` + Payer Payer `json:"payer,omitempty"` + Provider Provider `json:"provider,omitempty"` + Subscriber Subscriber `json:"subscriber,omitempty"` + ServiceInfo ServiceInfo `json:"service_info,omitempty"` + User User `json:"user,omitempty"` + RawProvider ProviderResponse `json:"raw_provider,omitempty"` } type Payer struct { diff --git a/application/viewmodel/error.go b/application/viewmodel/error.go new file mode 100644 index 0000000..893b7b8 --- /dev/null +++ b/application/viewmodel/error.go @@ -0,0 +1,16 @@ +package viewmodel + +import ( + "fmt" + + "bitbucket.org/nemt/nemt-portal-api/infra/errors" +) + +type ValidationError struct { + Errors []errors.ValidationError + Message string +} + +func (ve *ValidationError) Error() string { + return fmt.Sprintf("Error: %s", ve.Message) +} diff --git a/application/viewmodel/user.go b/application/viewmodel/user.go index 4d4682d..9ec49bb 100644 --- a/application/viewmodel/user.go +++ b/application/viewmodel/user.go @@ -26,6 +26,7 @@ type User struct { Organizations []Organization `json:"organizations,omitempty"` Provider *ProviderResp `json:"provider,omitempty"` Consent bool `json:"consent,omitempty"` + Test bool `json:"test,omitempty"` } type Contact struct { diff --git a/config.dev.toml b/config.dev.toml index 77cbd91..3e88d0a 100644 --- a/config.dev.toml +++ b/config.dev.toml @@ -1,6 +1,7 @@ [app] name = "nemt" debug = true +disable-zip-validation = true [app.docs] swagger-path = "./static/swagger-ui" diff --git a/config.prd.toml b/config.prd.toml index dacdcc8..281d062 100644 --- a/config.prd.toml +++ b/config.prd.toml @@ -1,6 +1,7 @@ [app] name = "nemt" debug = true +disable-zip-validation = true [app.docs] swagger-path = "/opt/app/nemt-portal-api/static/swagger-ui" @@ -9,7 +10,7 @@ debug = true [db] host = "db01.cj5318jcaupw.us-east-2.rds.amazonaws.com" port = 3306 -name = "nemt_clean" +name = "nemt" user = "nemt-user" pass = "OL&!n#p6J8Lu" max-life-minutes = 5 @@ -74,8 +75,8 @@ user-uuid = "11a49fa4-fbc7-4fe9-87fe-52a5bc3b71f8" [bxe] url = "https://api-pve.bcbs.com/BXDirectConnect/V30" -key = "dacg7jtsmb6ajr3z553jbczg" -secret = "vFhNeWE8JdJpbDZQtcm6AHjX" +key = "c63z7gv9a5qz2zgx7zcm3253" +secret = "hK4Yc9jtBrxaZTr4UU2VsfX9" [blue365] url = "https://api-cloud.bcbs.com/blue365deals-stg/v1/validatePrefix/" diff --git a/config.stg.toml b/config.stg.toml index 4ca8fb6..eb1cf12 100644 --- a/config.stg.toml +++ b/config.stg.toml @@ -1,6 +1,7 @@ [app] name = "nemt" debug = true +disable-zip-validation = true [app.docs] swagger-path = "/opt/app/nemt-portal-api/static/swagger-ui" diff --git a/data/datamysql/user.go b/data/datamysql/user.go index b0eae23..4e39bc7 100644 --- a/data/datamysql/user.go +++ b/data/datamysql/user.go @@ -192,7 +192,7 @@ func (c *userRepo) parseEntity(row scanner) (retVal entity.User, err error) { err = row.Scan(&retVal.ID, &retVal.UUID, &retVal.Name, &retVal.Member, &birthDate, &retVal.LoginID, &retVal.LoginUUID, &retVal.Email, &retVal.PhoneNumber, &retVal.LoginKey, &retVal.Gender, &retVal.Active, &retVal.Created, &retVal.Updated, &profile.ID, &profile.Name, &profile.Key, &profile.Active, &profile.Blocked, &profile.Suspended, &profile.Created, &profile.Updated, &profile.Organization.ID, &profile.Organization.UUID, &profile.Organization.Type.ID, &profile.Organization.Type.Name, &profile.Organization.Type.Key, &profile.Organization.Type.Description, &profile.Organization.Name, &profile.Organization.Description, &profile.Organization.ReferenceID, &profile.Organization.ParentID, &profile.Organization.Main, &homeAddress.ID, &homeAddress.UUID, &homeAddress.AddressType.ID, &homeAddress.AddressType.Key, &homeAddress.AddressType.Name, &homeAddress.Name, &homeAddress.Address, &homeAddress.Latitude, &homeAddress.Longitude, - &workAddress.ID, &workAddress.UUID, &workAddress.AddressType.ID, &workAddress.AddressType.Key, &workAddress.AddressType.Name, &workAddress.Name, &workAddress.Address, &workAddress.Latitude, &workAddress.Longitude, &retVal.Type) + &workAddress.ID, &workAddress.UUID, &workAddress.AddressType.ID, &workAddress.AddressType.Key, &workAddress.AddressType.Name, &workAddress.Name, &workAddress.Address, &workAddress.Latitude, &workAddress.Longitude, &retVal.Type, &retVal.Test) if err != nil { if err != sql.ErrNoRows { return retVal, errors.Wrap(err) @@ -278,7 +278,8 @@ func (c *userRepo) getQuery() string { IFNULL(i.address, '') work_address, IFNULL(i.lat, 0) work_lat, IFNULL(i.long, 0) work_long, - IFNULL(a.member_type, 'S') member_type + IFNULL(a.member_type, 'S') member_type, + user_test FROM tab_user a INNER JOIN @@ -587,11 +588,11 @@ func (c *userRepo) createLogin(user entity.User) (int64, string, error) { func (c *userRepo) createUser(user entity.User) (int64, string, error) { const ( - query = "INSERT INTO tab_user(user_uuid, `name`, subscriber_id, birth_date, gender, member_type, user_hash) VALUES(?, ?, ?, ?, ?, ?, SHA2(CONCAT_WS('-',?,?,?,?,?), 512)) ON DUPLICATE KEY UPDATE user_uuid = ?, `name` = ?, subscriber_id = ?, birth_date = ?, gender = ?;" + query = "INSERT INTO tab_user(user_uuid, `name`, subscriber_id, birth_date, gender, member_type, user_hash, user_test) VALUES(?, ?, ?, ?, ?, ?, SHA2(CONCAT_WS('-',?,?,?,?,?), 512), ?) ON DUPLICATE KEY UPDATE user_uuid = ?, `name` = ?, subscriber_id = ?, birth_date = ?, gender = ?, user_test = ?;" ) guid, _ := uuid.NewV4() - result, err := c.conn.Exec(query, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender)) + result, err := c.conn.Exec(query, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Type, user.Test, guid.String(), user.Name, toNullString(user.Member), toNullTime(user.BirthDate), toNullString(user.Gender), user.Test) if err != nil { return 0, "", err } diff --git a/domain/entity/user.go b/domain/entity/user.go index ba2b261..b081e00 100644 --- a/domain/entity/user.go +++ b/domain/entity/user.go @@ -31,6 +31,7 @@ type User struct { Types []OrganizationType `json:"types,omitempty"` Organizations []Organization `json:"organizations,omitempty"` Type string `json:"type,omitempty"` + Test bool `json:"test,omitempty"` } type ContactInfo struct { diff --git a/infra/config/config.go b/infra/config/config.go index d17cdb8..83f6081 100644 --- a/infra/config/config.go +++ b/infra/config/config.go @@ -25,14 +25,15 @@ type Config struct { Blue365 Blue365Config Email EmailConfig GoogleShortener GoogleShortenerConfig - Eligibility EligibilityConfig + Eligibility EligibilityConfig } // AppConfig represents the configuration values about the application. type AppConfig struct { - Name string - Debug bool - Docs DocsConfig + Name string + Debug bool + Docs DocsConfig + DisableZipValidation bool } // TwilioConfig represents the configuration values about the twilio. @@ -139,7 +140,7 @@ type GoogleShortenerConfig struct { } type EligibilityConfig struct { - Url string + Url string } // Read returns the configuration values, @@ -160,8 +161,9 @@ func Read() (*Config, error) { return &Config{ App: AppConfig{ - Name: viper.GetString("app.name"), - Debug: viper.GetBool("app.debug"), + Name: viper.GetString("app.name"), + Debug: viper.GetBool("app.debug"), + DisableZipValidation: viper.GetBool("app.disable-zip-validation"), Docs: DocsConfig{ YAMLPath: viper.GetString("app.docs.yaml-path"), SwaggerPath: viper.GetString("app.docs.swagger-path"), @@ -243,8 +245,8 @@ func Read() (*Config, error) { ClientID: viper.GetString("google-shortener.client-id"), SecretKey: viper.GetString("google-shortener.secret-key"), }, - Eligibility : EligibilityConfig{ - Url : viper.GetString("eligibility.url"), + Eligibility: EligibilityConfig{ + Url: viper.GetString("eligibility.url"), }, }, nil } diff --git a/server/router/eligibilityroute/controller.go b/server/router/eligibilityroute/controller.go index 3ed5dfb..3512086 100644 --- a/server/router/eligibilityroute/controller.go +++ b/server/router/eligibilityroute/controller.go @@ -1,12 +1,8 @@ package eligibilityroute import ( - "context" - "encoding/xml" "fmt" - "html" "math/rand" - "strings" "sync" "time" @@ -16,9 +12,7 @@ import ( "bitbucket.org/nemt/nemt-portal-api/infra/auth" "bitbucket.org/nemt/nemt-portal-api/infra/config" "bitbucket.org/nemt/nemt-portal-api/server/router/routeutils" - "bitbucket.org/nemt/nemt-portal-api/server/validation" "github.com/labstack/echo" - "googlemaps.github.io/maps" ) var ( @@ -78,185 +72,53 @@ func (c *controller) handleEligibility(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } - if validationErrors := validation.ValidateEligibility(&eligibility.User); len(validationErrors) > 0 { - return routeutils.ResponseAPICustomValidationError(ctx, "eligibility validation failed", validationErrors) - } - - if eligibility.Provider.ProviderNPI == "" { - provider, err := c.svc.Provider.GetByOrganization(authUser.Profiles[0].Organization.UUID, authUser) - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - eligibility.Provider.ProviderNPI = provider.InternalID - eligibility.Provider.ProviderName = provider.OrganizatioName - } - - loc, _ := time.LoadLocation("America/Chicago") - eligibility.TrackingID = c.rangeIn(1000000, 9999999) - eligibility.ServiceInfo.DateOfService = time.Now().In(loc) - eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"} - - var plan viewmodel.Plan - if eligibility.Subscriber.SubscriberID != "" { - plan, err = c.svc.Plan.GetByAlphaPrefix(eligibility.Subscriber.SubscriberID[:3]) - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - - eligibility.Payer.PayerID = fmt.Sprintf("%v", plan.PayerID) - eligibility.Payer.PayerName = plan.PayerName - } else { - return routeutils.ResponseAPIFieldValidationError(ctx, "subscriber_id", "member # is required.") - } - - ret, err := c.bcbsi.BXE.CheckEligibility(eligibility) + var provider viewmodel.ProviderResp + provider, err = c.svc.Provider.GetByNPI(eligibility.RawProvider.FivePartKeyGroups[0].ProviderNum, authUser) if err != nil { - fmt.Println("Error Here: ", err.Error()) return routeutils.HandleAPIError(ctx, err) } - if ret.QueryResult.QueryResultSummary.BenefitsFound { - xmlString := html.UnescapeString(ret.QueryResult.HIPPA271.T271) - xmlReader := strings.NewReader(xmlString) - - var f viewmodel.Interchange271 - err = xml.NewDecoder(xmlReader).Decode(&f) - if err != nil { - fmt.Println("Error to unmarshal: ", err.Error()) + if provider.ProviderUUID == "" { + org := viewmodel.Organization{ + Type: viewmodel.OrganizationType{ + Key: "provider", + Name: "Provider", + }, + Name: eligibility.RawProvider.OrgName, + Description: eligibility.RawProvider.OrgName, + Main: false, + Author: authUser, + LastEditor: authUser, + Reference: eligibility.RawProvider, } - user, err := c.svc.Users.GetByMemberID(eligibility.Subscriber.SubscriberID) + org, err = c.svc.Organization.AddOrganization(org, authUser) if err != nil { - fmt.Println(err) return routeutils.HandleAPIError(ctx, err) } - if user.ID == "" { - user.Pass = c.generatePassword(8) - user.BirthDate = &eligibility.Subscriber.DemographicInfo.DateOfBirth - user.Member = &eligibility.Subscriber.SubscriberID - user.Gender = &eligibility.Subscriber.DemographicInfo.Gender - user.First = eligibility.Subscriber.Name.First - user.Last = eligibility.Subscriber.Name.Last - user.Active = true - user.Type = &eligibility.Subscriber.PatientType - user.PhoneNumber = eligibility.User.PhoneNumber - user.Email = eligibility.User.Email + provider, err = c.svc.Provider.GetByOrganization(org.UUID, authUser) + if err != nil { + return routeutils.HandleAPIError(ctx, err) + } + } - profile, err := c.svc.Profile.GetByKey("US") - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - user.Profiles = append(user.Profiles, profile) - - if user.BirthDate == nil || user.BirthDate.IsZero() { - return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false) - } - - if user.Member == nil || len(*user.Member) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "member is required", false) - } - - if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") { - return routeutils.ResponseAPIAuthError(ctx, "gender is required", false) - } - - if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "name is required", false) - } - - if len(user.First) != 0 && len(user.Last) != 0 { - user.Name = fmt.Sprintf("%s %s", user.First, user.Last) - } - - if user.PhoneNumber != nil && len(*user.PhoneNumber) == 10 && !strings.Contains(*user.PhoneNumber, "+1") { - phoneNumber := fmt.Sprintf("+1%s", *user.PhoneNumber) - user.PhoneNumber = &phoneNumber + user, err := c.svc.Users.CheckAndCreateMember(eligibility.User, provider, authUser) + if err != nil { + if validationError, ok := err.(*viewmodel.ValidationError); ok { + if len(validationError.Errors) > 0 { + return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors) } else { - return routeutils.ResponseAPIFieldValidationError(ctx, "phonenumber", "Phone number is required") - } - - user, err = c.svc.Users.Create(user, authUser) - if err != nil { - return routeutils.HandleAPIError(ctx, err) + return routeutils.ResponseAPIServiceError(ctx, validationError.Message) } } else { - if eligibility.User.Email != nil && len(*eligibility.User.Email) > 0 { - user.Email = eligibility.User.Email - } - - if eligibility.User.PhoneNumber != nil { - if len(*eligibility.User.PhoneNumber) == 10 && !strings.Contains(*eligibility.User.PhoneNumber, "+1") { - phoneNumber := fmt.Sprintf("+1%s", *eligibility.User.PhoneNumber) - eligibility.User.PhoneNumber = &phoneNumber - } - - if len(*eligibility.User.PhoneNumber) > 0 { - user.PhoneNumber = eligibility.User.PhoneNumber - } - } - - err = c.svc.Users.UpdateLogin(user) - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - } - - address := viewmodel.Address{} - header := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - body := f.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - zipCode := strings.TrimSpace(body.N403) - - address.AddressTypeName = "Home" - address.AddressType = "home" - address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode) - address.CreatedUserUUID = authUser.ID - address.User = user - address.Type = "provider" - - googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) return routeutils.HandleAPIError(ctx, err) } + } - r := &maps.GeocodingRequest{ - Address: address.Address + " " + body.N402 + ", " + zipCode, - } - - result, err := googleMapsAPI.Geocode(context.Background(), r) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - if len(result) > 0 { - address.Latitude = result[0].Geometry.Location.Lat - address.Longitude = result[0].Geometry.Location.Lng - } - - if len(user.Addresses) > 0 { - for _, a := range user.Addresses { - if a.AddressType == "home" { - err := c.svc.Users.RemoveAddress(a.UUID) - if err != nil { - fmt.Println("Error to remove old address: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - } - } - } - - address, err = c.svc.Users.SaveAddress(address) - if err != nil { - fmt.Println("Error to save address: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - user.Addresses = append(user.Addresses, address) - - return ctx.JSON(200, user) + if authUser.Profiles[0].Key == "VIRPT" { + return ctx.JSON(204, nil) } else { - return routeutils.ResponseAPINotEligibleWithMessageError(ctx, "No benefits found for member") + return ctx.JSON(200, user) } } diff --git a/server/router/externalroute/controller.go b/server/router/externalroute/controller.go index cf2628c..f05b4e0 100644 --- a/server/router/externalroute/controller.go +++ b/server/router/externalroute/controller.go @@ -63,12 +63,17 @@ func (c *controller) handleCancel(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } + authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "") + if err != nil { + return routeutils.HandleAPIError(ctx, err) + } + lyftRide := viewmodel.RideRequest{RideID: ride.InternalID} if ride.Status.Key == "scheduled" && !strings.Contains(ride.InternalID, "s_") { lyftRide.RideID = "s_" + ride.InternalID } - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if authUser.Test { if err = c.tnc.Lyft.CancelRide(lyftRide); err != nil { if err.Error() != "ride_not_found" { fmt.Println("Error to cancel with Lyft: ", err.Error()) @@ -144,13 +149,18 @@ func (c *controller) handle(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } + authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "") + if err != nil { + fmt.Println("Error: ", err.Error()) + return routeutils.HandleAPIError(ctx, err) + } + if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" { var lyftRide viewmodel.RideRequest var err error - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if authUser.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { - fmt.Println("In Production") lyftRide, err = c.tnc.LyftProd.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } if err != nil { @@ -236,7 +246,7 @@ func (c *controller) handleReady(ctx echo.Context) error { } var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if authUser.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { fmt.Println("In Production") @@ -258,7 +268,7 @@ func (c *controller) handleReady(ctx echo.Context) error { lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber lyftRide.RideType = "lyft" - if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID { + if authUser.Test { lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide) } else { fmt.Println("In Production") diff --git a/server/router/lyfthookroute/controller.go b/server/router/lyfthookroute/controller.go index df15fc5..a872c64 100644 --- a/server/router/lyfthookroute/controller.go +++ b/server/router/lyfthookroute/controller.go @@ -57,8 +57,14 @@ func (c *controller) handleStateChange(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } + authUser, err := c.svc.Users.GetByUUID(ride.CreatedUser.ID, "") + if err != nil { + fmt.Println("Error: ", err.Error()) + return routeutils.HandleAPIError(ctx, err) + } + // var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if authUser.Test { _, err = c.tnc.Lyft.GetRideStatus(viewmodel.RideRequest{RideID: ride.InternalID}, status) go func() { secondCall := func() { diff --git a/server/router/selfregisterroute/controller.go b/server/router/selfregisterroute/controller.go index 51f2b53..b344496 100644 --- a/server/router/selfregisterroute/controller.go +++ b/server/router/selfregisterroute/controller.go @@ -2,6 +2,7 @@ package selfregisterroute import ( "fmt" + "strings" "sync" b64 "encoding/base64" @@ -22,6 +23,10 @@ const ( notificationSmsBody = "You have registered as a Visit Reporter for CHM NEMT. Login: https://portal.bcbsinstitute.com Reset PW: https://portal.bcbsinstitute.com/forgot" ) +const ( + phoneNumberMaxLength = 12 +) + var ( instance *controller once sync.Once @@ -44,12 +49,34 @@ func controllerInstance(svc *applicationservice.Service, cfg *config.Config) *co return instance } +func removeNonNumberChars(input string) string { + result := "" + for _, char := range input { + if char >= '0' && char <= '9' { + result += string(char) + } + } + + return result +} + func (c *controller) handle(ctx echo.Context) error { var user viewmodel.User if err := ctx.Bind(&user); err != nil { return routeutils.HandleAPIError(ctx, err) } + //format phone number - max length in database is 12 chars + formatedPhoneNumber := strings.TrimSpace(*user.PhoneNumber) + formatedPhoneNumber = strings.Replace(formatedPhoneNumber, "+1", "", -1) + formatedPhoneNumber = removeNonNumberChars(formatedPhoneNumber) + + if len(formatedPhoneNumber) > phoneNumberMaxLength { + formatedPhoneNumber = formatedPhoneNumber[:phoneNumberMaxLength] + } + + *user.PhoneNumber = formatedPhoneNumber + authUser, err := c.svc.Users.GetByUUID("573c70ff-733d-11e7-ba8f-0a6ad3fcdeaa", "") if err != nil { return routeutils.HandleAPIError(ctx, err) @@ -132,10 +159,14 @@ func (c *controller) handle(ctx echo.Context) error { notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification) if err != nil { - return routeutils.HandleAPIError(ctx, err) + logger := ctx.Logger() + logger.Warnf("Application Error: Could not send email notification to user email : %s", *user.Email) } //Send sms notification to Authorized user + + formatedPhoneNumber = *user.PhoneNumber + notification = viewmodel.Notification{ Type: applicationservice.NOtificationTypeSMS, To: *user.PhoneNumber, @@ -144,7 +175,8 @@ func (c *controller) handle(ctx echo.Context) error { notification, err = c.svc.Notification.SendNotificationWithoutWritingToDatabase(notification) if err != nil { - return routeutils.HandleAPIError(ctx, err) + logger := ctx.Logger() + logger.Warnf("Application Error: Could not send sms notification to user mobile : %s", *user.PhoneNumber) } return routeutils.ResponseAPIOK(ctx, user) diff --git a/server/router/tncroute/controller.go b/server/router/tncroute/controller.go index 527811e..3b83658 100644 --- a/server/router/tncroute/controller.go +++ b/server/router/tncroute/controller.go @@ -289,7 +289,7 @@ func (c *controller) handle(ctx echo.Context) error { requestRide.Destination = newOrigin } - if c.cfg.LyftProd.UserUUID != createdUser.ID { + if createdUser.Test { resp, err = c.tnc.Lyft.RequestRide(requestRide) } else { fmt.Println("In Production") @@ -404,7 +404,7 @@ func (c *controller) handle(ctx echo.Context) error { newRide.Passenger.LastName = " " newRide.Passenger.PhoneNumber = *user.PhoneNumber - if c.cfg.LyftProd.UserUUID != createdUser.ID { + if createdUser.Test { newRide, err = c.tnc.Lyft.RequestRide(newRide) } else { fmt.Println("In Production") @@ -514,7 +514,7 @@ func (c *controller) handleRawLyft(ctx echo.Context) error { } var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if user.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { fmt.Println("In Production") @@ -546,7 +546,7 @@ func (c *controller) handleRideETA(ctx echo.Context) error { if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" { var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if user.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { fmt.Println("In Production") @@ -614,7 +614,7 @@ func (c *controller) handleCancel(ctx echo.Context) error { requestRide.RideID = "s_" + ride.InternalID } - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if user.Test { err = c.tnc.Lyft.CancelRide(requestRide) if err != nil && err.Error() == "ride_not_found" { err = nil @@ -719,7 +719,7 @@ func (c *controller) handleRide(ctx echo.Context) error { if ride.Status.Key == "accepted" || ride.Status.Key == "pickedUp" || ride.Status.Key == "arrived" { var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if user.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { fmt.Println("In Production") @@ -858,7 +858,7 @@ func (c *controller) handleReady(ctx echo.Context) error { } var lyftRide viewmodel.RideRequest - if ride.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if user.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: ride.InternalID}) } else { fmt.Println("In Production") @@ -880,7 +880,7 @@ func (c *controller) handleReady(ctx echo.Context) error { lyftRide.Passenger.PhoneNumber = *nextRide.User.PhoneNumber lyftRide.RideType = "lyft" - if c.cfg.LyftProd.UserUUID != nextRide.CreatedUser.ID { + if user.Test { lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide) } else { fmt.Println("In Production") diff --git a/server/router/twilioroute/controller.go b/server/router/twilioroute/controller.go index 3110aca..4372327 100644 --- a/server/router/twilioroute/controller.go +++ b/server/router/twilioroute/controller.go @@ -128,6 +128,13 @@ func (c *controller) handleTwilio(ctx echo.Context) error { message = fmt.Sprintf("We received a request to cancel a ride from you at %s, but cannot find a ride for this mobile number.", libphonenumber.Format(num, libphonenumber.NATIONAL)) } } + + authUser, err := c.svc.Users.GetByUUID(lastRide.CreatedUser.ID, "") + if err != nil { + fmt.Println("Error to author of the ride: ", err.Error()) + message = "There was a problem to call your ride" + } + if !isDriver { if requestMessage == "I AM READY" && lastRide.UUID != "" { if (lastRide.Visit.TripType.Key == "roundtrip_call" && (lastRide.TripType.Key == "to_visit" || lastRide.TripType.Key == "from_visit_call")) || lastRide.Visit.TripType.Key == "from_visit_call" { @@ -161,7 +168,7 @@ func (c *controller) handleTwilio(ctx echo.Context) error { lyftRide.Passenger.PhoneNumber = *readyRide.User.PhoneNumber lyftRide.RideType = "lyft" - if c.cfg.LyftProd.UserUUID != readyRide.CreatedUser.ID { + if authUser.Test { lyftRide, err = c.tnc.Lyft.RequestRide(lyftRide) } else { fmt.Println("In Production") @@ -209,7 +216,7 @@ func (c *controller) handleTwilio(ctx echo.Context) error { if requestMessage == "YES" && lastRide.UUID != "" { var lyftRide viewmodel.RideRequest - if lastRide.CreatedUser.ID != c.cfg.LyftProd.UserUUID { + if authUser.Test { lyftRide, err = c.tnc.Lyft.GetRideDetails(viewmodel.RideRequest{RideID: lastRide.InternalID}) if err != nil { fmt.Println("Error: ", err.Error()) diff --git a/server/router/usersroute/controller.go b/server/router/usersroute/controller.go index 17b057b..c34501f 100644 --- a/server/router/usersroute/controller.go +++ b/server/router/usersroute/controller.go @@ -1,13 +1,9 @@ package usersroute import ( - "bytes" b64 "encoding/base64" - "encoding/json" "fmt" "math/rand" - "net/http" - "strings" "sync" "time" @@ -22,8 +18,6 @@ import ( "bitbucket.org/nemt/nemt-portal-api/server/router/routeutils" "bitbucket.org/nemt/nemt-portal-api/server/validation" "github.com/labstack/echo" - "golang.org/x/net/context" - "googlemaps.github.io/maps" ) const ( @@ -345,11 +339,6 @@ func (c *controller) handleMember(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } - //Validate member - if validationErrors := validation.ValidateEligibility(&user); len(validationErrors) > 0 { - return routeutils.ResponseAPICustomValidationError(ctx, "eligibility validation failed", validationErrors) - } - authUser, err := auth.GetUserDetail(ctx, c.cfg) if err != nil { return routeutils.HandleAPIError(ctx, err) @@ -359,153 +348,24 @@ func (c *controller) handleMember(ctx echo.Context) error { return routeutils.ResponseAPIAuthError(ctx, "phonenumber or email is required", false) } - if len(user.Pass) == 0 { - user.Pass = c.generatePassword(8) - } else { - pass, err := b64.StdEncoding.DecodeString(user.Pass) - if err != nil { - return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false) - } - user.Pass = string(pass) - } - - if user.BirthDate == nil || user.BirthDate.IsZero() { - return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false) - } - - if user.Member == nil || len(*user.Member) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "member is required", false) - } - - if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") { - return routeutils.ResponseAPIAuthError(ctx, "gender is required", false) - } - - if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "name is required", false) - } - - if len(user.First) != 0 && len(user.Last) != 0 { - user.Name = fmt.Sprintf("%s %s", user.First, user.Last) - } - - profile, err := c.svc.Profile.GetByKey("US") + provider, err := c.svc.Provider.GetByNPI("1699849786", authUser) if err != nil { - return routeutils.HandleAPIError(ctx, err) + return routeutils.ResponseAPIAuthError(ctx, "Provider not found", false) } - user.Profiles = append(user.Profiles, profile) - eligibility := viewmodel.Eligibility{} - eligibility.Provider.ProviderNPI = "1699849786" - eligibility.Provider.ProviderName = "LITHOLINK CORPORATION" - eligibility.TrackingID = user.ID - eligibility.Subscriber.SubscriberID = *user.Member - eligibility.Subscriber.PatientType = "S" - eligibility.Subscriber.Name.First = user.First - eligibility.Subscriber.Name.Last = user.Last - eligibility.Subscriber.DemographicInfo.DateOfBirth = *user.BirthDate - eligibility.Subscriber.DemographicInfo.Gender = *user.Gender - eligibility.ServiceInfo.DateOfService = time.Now() - eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"} - - /* - resp, err := c.bcbsi.BXE.Get271(eligibility) - if err != nil { - fmt.Println("Eligibility Not Found or Error: ", err.Error()) - return routeutils.ResponseAPINotEligibleError(ctx) - } - */ - - //This part is emulating eligibility check for testing purposes - client := &http.Client{} - eligibilityJson, err := json.Marshal(eligibility) + user, err = c.svc.Users.CheckAndCreateMember(user, provider, authUser) if err != nil { - return routeutils.ResponseAPINotEligibleError(ctx) - } - req, _ := http.NewRequest("POST", c.cfg.Eligibility.Url, bytes.NewBuffer(eligibilityJson)) - req.Header.Add("App", c.cfg.HTTP.Auth.AppKey) - req.Header.Add("Token", ctx.Request().Header.Get("Token")) - req.Header.Add("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - return routeutils.ResponseAPINotEligibleError(ctx) - } - defer resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode > 300 { - return routeutils.ResponseAPINotEligibleError(ctx) - } - - eligibilityResponse := viewmodel.Interchange271{} - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(&eligibilityResponse) - if err != nil { - return routeutils.ResponseAPINotEligibleError(ctx) - } - //================================================================ - - if len(eligibilityResponse.Division.HealthCareEligibilityResponse.LoopHL0030) < 1 { - return routeutils.ResponseAPINotEligibleError(ctx) - } - - address := viewmodel.Address{} - //header := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - //body := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - header := eligibilityResponse.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - body := eligibilityResponse.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - - address.AddressTypeName = "Home" - address.AddressType = "home" - address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.Address = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.CreatedUserUUID = authUser.ID - address.User = user - - cleanZipcode := strings.TrimSpace(body.N403) - trimmedZipcode := cleanZipcode - if len(cleanZipcode) > zipcodeTrimLength { - trimmedZipcode = cleanZipcode[:zipcodeTrimLength] - } - - _, err = c.svc.Zipcodes.GetByParticipatingZipcode(trimmedZipcode) - - if err != nil { - return routeutils.ResponseAPINotEligibleWithMessageError(ctx, "Member's Home zipcode, "+trimmedZipcode+", is not currently eligible for participation in this program") - } - - googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - r := &maps.GeocodingRequest{ - Address: address.Address + " " + body.N402 + ", " + body.N403, - } - - result, err := googleMapsAPI.Geocode(context.Background(), r) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - if len(result) > 0 { - address.Latitude = result[0].Geometry.Location.Lat - address.Longitude = result[0].Geometry.Location.Lng - - _, err := c.svc.Users.SaveAddress(address) - if err != nil { - fmt.Println("Error to save address: ", err.Error()) + if validationError, ok := err.(*viewmodel.ValidationError); ok { + if len(validationError.Errors) > 0 { + return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors) + } else { + return routeutils.ResponseAPIServiceError(ctx, validationError.Message) + } + } else { return routeutils.HandleAPIError(ctx, err) } } - user, err = c.svc.Users.Create(user, authUser) - if err != nil { - return routeutils.HandleAPIError(ctx, err) - } - return routeutils.ResponseAPIOK(ctx, user) } @@ -547,6 +407,7 @@ func (c *controller) handleBulkPortal(ctx echo.Context) error { return routeutils.ResponseAPIAuthError(ctx, "name is required", false) } + users[i].Test = true if len(users[i].First) != 0 && len(users[i].Last) != 0 { users[i].Name = fmt.Sprintf("%s %s", users[i].First, users[i].Last) } @@ -608,6 +469,7 @@ func (c *controller) handlePortal(ctx echo.Context) error { if len(user.First) != 0 && len(user.Last) != 0 { user.Name = fmt.Sprintf("%s %s", user.First, user.Last) } + user.Test = true user, err = c.svc.Users.Create(user, authUser) if err != nil { diff --git a/server/router/visitroute/controller.go b/server/router/visitroute/controller.go index 9669b91..bc1b8b6 100644 --- a/server/router/visitroute/controller.go +++ b/server/router/visitroute/controller.go @@ -1,15 +1,12 @@ package visitroute import ( - "context" "fmt" "math/rand" "strings" "sync" "time" - b64 "encoding/base64" - "bitbucket.org/nemt/nemt-portal-api/application/applicationservice" "bitbucket.org/nemt/nemt-portal-api/application/third/eligibility/bcbsi" "bitbucket.org/nemt/nemt-portal-api/application/tncservice" @@ -20,7 +17,6 @@ import ( "bitbucket.org/nemt/nemt-portal-api/server/validation" "github.com/labstack/echo" uuid "github.com/satori/go.uuid" - "googlemaps.github.io/maps" ) var ( @@ -81,103 +77,34 @@ func (c *controller) handleRide(ctx echo.Context) error { if err != nil { return routeutils.HandleAPIError(ctx, err) } + + visit.User.PhoneNumber = ride.User.PhoneNumber + visit.User.Email = ride.User.Email + visit.User.Consent = true + ride.Visit = visit - user, err := c.svc.Users.GetByMemberID(*visit.User.Member) - if err != nil { - fmt.Println(err) - return routeutils.HandleAPIError(ctx, err) - } - user.PhoneNumber = ride.User.PhoneNumber - user.Email = ride.User.Email - - ride.Visit.User = user - if ride.Visit.User.Type == nil { - subscriber := "S" - ride.Visit.User.Type = &subscriber - } - var provider viewmodel.ProviderResp provider, err = c.svc.Provider.GetByUUID(ride.Visit.Provider.ProviderUUID, authUser) if err != nil { return routeutils.HandleAPIError(ctx, err) } - visit.Provider = provider + ride.Visit.Provider = provider - eligibility := viewmodel.Eligibility{} - eligibility.Provider.ProviderNPI = provider.InternalID - eligibility.Provider.ProviderName = provider.OrganizatioName - eligibility.TrackingID = c.rangeIn(1000000, 9999999) - eligibility.Subscriber.SubscriberID = *ride.Visit.User.Member - eligibility.Subscriber.PatientType = *ride.Visit.User.Type - eligibility.Subscriber.Name.First = ride.Visit.User.First - eligibility.Subscriber.Name.Last = ride.Visit.User.Last - eligibility.Subscriber.DemographicInfo.DateOfBirth = *ride.Visit.User.BirthDate - eligibility.Subscriber.DemographicInfo.Gender = *ride.Visit.User.Gender - loc, _ := time.LoadLocation("America/Chicago") - eligibility.ServiceInfo.DateOfService = time.Now().In(loc) - eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"} - - resp271, err := c.bcbsi.BXE.Get271(eligibility) + user, err := c.svc.Users.CheckAndCreateMember(ride.Visit.User, provider, authUser) if err != nil { - fmt.Println(err) - return routeutils.ResponseAPIValidationError(ctx, err.Error()) - } - - address := viewmodel.Address{} - header := resp271.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - body := resp271.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - zipCode := strings.TrimSpace(body.N403) - - address.AddressTypeName = "Home" - address.AddressType = "home" - address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode) - address.CreatedUserUUID = authUser.ID - address.User = user - address.Type = "provider" - - googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - r := &maps.GeocodingRequest{ - Address: address.Address + " " + body.N402 + ", " + zipCode, - } - - result, err := googleMapsAPI.Geocode(context.Background(), r) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - if len(result) > 0 { - address.Latitude = result[0].Geometry.Location.Lat - address.Longitude = result[0].Geometry.Location.Lng - } - - if address.Latitude != 0 && address.Longitude != 0 { - if len(user.Addresses) > 0 { - for _, a := range user.Addresses { - if a.AddressType == "home" { - err := c.svc.Users.RemoveAddress(a.UUID) - if err != nil { - fmt.Println("Error to remove old address: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - } + if validationError, ok := err.(*viewmodel.ValidationError); ok { + if len(validationError.Errors) > 0 { + return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors) + } else { + return routeutils.ResponseAPIServiceError(ctx, validationError.Message) } - } - - address, err = c.svc.Users.SaveAddress(address) - if err != nil { - fmt.Println("Error to save address: ", err.Error()) + } else { return routeutils.HandleAPIError(ctx, err) } - user.Addresses = append(user.Addresses, address) } + ride.Visit.User = user + ride.User = user homeAddress := viewmodel.Address{} for _, a := range visit.User.Addresses { @@ -226,7 +153,7 @@ func (c *controller) handleRide(ctx echo.Context) error { ride.Destination = newOrigin } - if c.cfg.LyftProd.UserUUID != authUser.ID { + if authUser.Test { resp, err = c.tnc.Lyft.RequestRide(ride) } else { fmt.Println("In Production") @@ -340,7 +267,7 @@ func (c *controller) handleRide(ctx echo.Context) error { newRide.Passenger.LastName = " " newRide.Passenger.PhoneNumber = *visit.User.PhoneNumber - if c.cfg.LyftProd.UserUUID != authUser.ID { + if authUser.Test { newRide, err = c.tnc.Lyft.RequestRide(newRide) } else { fmt.Println("In Production") @@ -471,154 +398,19 @@ func (c *controller) handle(ctx echo.Context) error { return routeutils.ResponseAPICustomValidationError(ctx, "visit validation failed", validationErrors) } - eligibility := viewmodel.Eligibility{} - eligibility.Provider.ProviderNPI = provider.InternalID - eligibility.Provider.ProviderName = provider.OrganizatioName - eligibility.TrackingID = c.rangeIn(1000000, 9999999) - eligibility.Subscriber.SubscriberID = *visit.User.Member - eligibility.Subscriber.PatientType = *visit.User.Type - eligibility.Subscriber.Name.First = visit.User.First - eligibility.Subscriber.Name.Last = visit.User.Last - eligibility.Subscriber.DemographicInfo.DateOfBirth = *visit.User.BirthDate - eligibility.Subscriber.DemographicInfo.Gender = *visit.User.Gender - loc, _ := time.LoadLocation("America/Chicago") - eligibility.ServiceInfo.DateOfService = time.Now().In(loc) - eligibility.ServiceInfo.ServiceTypeCodes = []string{"30"} - - resp, err := c.bcbsi.BXE.Get271(eligibility) + user, err := c.svc.Users.CheckAndCreateMember(visit.User, provider, authUser) if err != nil { - fmt.Println("Error to get eligibility: ", err.Error()) - return routeutils.ResponseAPIValidationError(ctx, err.Error()) - } - - user, err := c.svc.Users.GetByMemberID(*visit.User.Member) - if err != nil { - fmt.Println("Error to get user by memberID: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - if user.ID == "" { - if len(user.Pass) == 0 { - user.Pass = c.generatePassword(8) + if validationError, ok := err.(*viewmodel.ValidationError); ok { + if len(validationError.Errors) > 0 { + return routeutils.ResponseAPICustomValidationError(ctx, validationError.Message, validationError.Errors) + } else { + return routeutils.ResponseAPIServiceError(ctx, validationError.Message) + } } else { - pass, err := b64.StdEncoding.DecodeString(user.Pass) - if err != nil { - return routeutils.ResponseAPIAuthError(ctx, "Invalid password", false) - } - user.Pass = string(pass) - } - - if user.BirthDate == nil || user.BirthDate.IsZero() { - return routeutils.ResponseAPIAuthError(ctx, "birthdate is required", false) - } - - if user.Member == nil || len(*user.Member) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "member is required", false) - } - - if user.Gender == nil || len(*user.Gender) == 0 || (*user.Gender != "M" && *user.Gender != "F" && *user.Gender != "U") { - return routeutils.ResponseAPIAuthError(ctx, "gender is required", false) - } - - if len(user.Name) == 0 && len(user.First) == 0 && len(user.Last) == 0 { - return routeutils.ResponseAPIAuthError(ctx, "name is required", false) - } - - if len(user.First) != 0 && len(user.Last) != 0 { - user.Name = fmt.Sprintf("%s %s", user.First, user.Last) - } - - if user.PhoneNumber != nil && len(*user.PhoneNumber) == 10 && !strings.Contains(*user.PhoneNumber, "+1") { - phoneNumber := fmt.Sprintf("+1%s", *user.PhoneNumber) - user.PhoneNumber = &phoneNumber - } - - profile, err := c.svc.Profile.GetByKey("US") - if err != nil { - fmt.Println("Error to get Profile: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - user.Profiles = append(user.Profiles, profile) - - user, err = c.svc.Users.Create(user, authUser) - if err != nil { - fmt.Println("Error to create the user: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - } else { - if visit.User.Email != nil && len(*visit.User.Email) > 0 { - user.Email = visit.User.Email - } - - if visit.User.PhoneNumber != nil { - if len(*visit.User.PhoneNumber) == 10 && !strings.Contains(*visit.User.PhoneNumber, "+1") { - phoneNumber := fmt.Sprintf("+1%s", *visit.User.PhoneNumber) - visit.User.PhoneNumber = &phoneNumber - } - user.PhoneNumber = visit.User.PhoneNumber - } - - err = c.svc.Users.UpdateLogin(user) - if err != nil { - fmt.Println("Error to update login: ", err.Error()) return routeutils.HandleAPIError(ctx, err) } } - - address := viewmodel.Address{} - header := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N3_0950 - body := resp.Division.HealthCareEligibilityResponse.LoopHL0030[0].HL_0460[0].HL_0890[0].NM1_0920[0].N4_0960 - zipCode := strings.TrimSpace(body.N403) - - address.AddressTypeName = "Home" - address.AddressType = "home" - address.Name = fmt.Sprintf("%s, %s", header.N301, body.N401) - address.Address = fmt.Sprintf("%s, %s (%s)", header.N301, body.N401, zipCode) - address.CreatedUserUUID = authUser.ID - address.User = user - address.Type = "provider" - - googleMapsAPI, err := maps.NewClient(maps.WithClientIDAndSignature("gme-bluecrossandblue1", "msqgD-jdqCyR0M_1u5C1HION5iI=")) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - r := &maps.GeocodingRequest{ - Address: address.Address + " " + body.N402 + ", " + zipCode, - } - - result, err := googleMapsAPI.Geocode(context.Background(), r) - if err != nil { - fmt.Println("Error to instantiate googles api: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - - if len(result) > 0 { - address.Latitude = result[0].Geometry.Location.Lat - address.Longitude = result[0].Geometry.Location.Lng - } - - if address.Latitude != 0 && address.Longitude != 0 { - if len(user.Addresses) > 0 { - for _, a := range user.Addresses { - if a.AddressType == "home" { - err := c.svc.Users.RemoveAddress(a.UUID) - if err != nil { - fmt.Println("Error to remove old address: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - } - } - } - - address, err = c.svc.Users.SaveAddress(address) - if err != nil { - fmt.Println("Error saving address: ", err.Error()) - return routeutils.HandleAPIError(ctx, err) - } - user.Addresses = append(user.Addresses, address) - } + visit.User = user visit.TripType = viewmodel.TripType{ Key: "no_trip", @@ -634,7 +426,11 @@ func (c *controller) handle(ctx echo.Context) error { return routeutils.HandleAPIError(ctx, err) } - return routeutils.ResponseAPIOK(ctx, visit) + if authUser.Profiles[0].Key == "VIRPT" { + return routeutils.ResponseNoContent(ctx, nil) + } else { + return routeutils.ResponseAPIOK(ctx, visit) + } } func (c *controller) handleGetByID(ctx echo.Context) error {