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, bcbsi *bcbsi.Service, cfg *config.Config) *userService { return &userService{ svc: svc, mapEntity: mapper, bcbsi: bcbsi, cfg: cfg, } } // GetAll returns a list of users func (s *userService) GetAll(quantity int64, page int64) (retVal []viewmodel.User, err error) { users, err := s.svc.Users.GetAll() if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModelSlice(users), nil } // GetByID returns a specific user by its ID func (s *userService) GetByID(id int64) (retVal viewmodel.User, err error) { user, err := s.svc.Users.GetByID(id) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } // GetByID returns a specific user by its ID func (s *userService) GetByUUID(uuid string, profile string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.GetByUUID(uuid, profile) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } // GetByID returns a specific user by its ID func (s *userService) GetByMemberID(memberID string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.GetByMemberID(memberID) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } // GetByEmail returns a specific user by its email func (s *userService) GetByEmail(email string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.GetByEmail(email) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } // Login returns a specific user by email and pass func (s *userService) FullLogin(loginType string, key string, pass string, profile string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.FullLogin(loginType, key, pass, profile) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } // Login returns a specific user by email and pass func (s *userService) Login(email string, pass string) (retVal viewmodel.User, err error) { user, err := s.svc.Users.Login(email, pass) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(user), nil } func (s *userService) Create(user viewmodel.User, author viewmodel.User) (retVal viewmodel.User, err error) { entity := s.mapEntity.User.ToUserEntity(user) enAuthor := s.mapEntity.User.ToUserEntity(author) for i, _ := range entity.Organizations { entity.Organizations[i], err = s.svc.Organization.GetByUUID(entity.Organizations[i].UUID, enAuthor) if err != nil { return retVal, err } } entity, err = s.svc.Users.Create(entity) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToUserModel(entity), nil } func (s *userService) CreateBulk(users []viewmodel.User, author viewmodel.User) (retVal []viewmodel.User, err error) { entities := s.mapEntity.User.ToUserEntitySlice(users) enAuthor := s.mapEntity.User.ToUserEntity(author) organizations := make([]entity.Organization, 0) for i, _ := range entities { if i == 0 { for o, _ := range entities[i].Organizations { org, err := s.svc.Organization.GetByUUID(entities[i].Organizations[o].UUID, enAuthor) if err != nil { return nil, err } organizations = append(organizations, org) } } entities[i].Organizations = organizations } entities, err = s.svc.Users.CreateBulk(entities) if err != nil { return nil, errors.Wrap(err) } return s.mapEntity.User.ToUserModelSlice(entities), nil } // GetUsersByProfile returns a list of users by profile func (s *userService) GetUsersByProfile(profile string) (retVal []viewmodel.User, err error) { users, err := s.svc.Users.GetUsersByProfile(profile) if err != nil { return retVal, errors.Wrap(err) } list := s.mapEntity.User.ToUserModelSlice(users) return list, nil } func (s *userService) RemoveAddress(addressUUID string) error { return s.svc.Users.RemoveAddress(addressUUID) } func (s *userService) UpdateLogin(user viewmodel.User) error { eUser := s.mapEntity.User.ToUserEntity(user) return s.svc.Users.UpdateLogin(eUser) } func (s *userService) SaveAddress(address viewmodel.Address) (retVal viewmodel.Address, err error) { entity := s.mapEntity.Address.ToAddressEntity(address) entity, err = s.svc.Users.SaveAddress(entity) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.Address.ToAddressModel(entity), err } func (s *userService) GetContactType() (retVal []viewmodel.ContactType, err error) { entity, err := s.svc.Users.GetContactType() if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToContactTypeModelSlice(entity), nil } func (s *userService) SaveContact(contact viewmodel.Contact) (retVal viewmodel.Contact, err error) { entity := s.mapEntity.User.ToContactEntity(contact) entity, err = s.svc.Users.SaveContact(entity) if err != nil { return retVal, errors.Wrap(err) } return s.mapEntity.User.ToContactModel(entity), err } func (s *userService) RemoveContact(contact viewmodel.Contact) (retVal viewmodel.Contact, err error) { entity := s.mapEntity.User.ToContactEntity(contact) entity, err = s.svc.Users.RemoveContact(entity) if err != nil { return retVal, errors.Wrap(err) } 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 }