001// --------------------------------------------------------------------------------
002// Copyright 2002-2026 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.control.user.search.server.command;
018
019import com.echothree.control.user.search.common.form.SearchCustomersForm;
020import com.echothree.control.user.search.common.result.SearchResultFactory;
021import com.echothree.model.control.customer.server.control.CustomerControl;
022import com.echothree.model.control.geo.server.control.GeoControl;
023import com.echothree.model.control.party.common.PartyTypes;
024import com.echothree.model.control.party.server.control.PartyControl;
025import com.echothree.model.control.search.common.SearchKinds;
026import com.echothree.model.control.search.server.control.SearchControl;
027import com.echothree.model.control.customer.server.search.CustomerSearchEvaluator;
028import com.echothree.model.control.search.server.logic.SearchLogic;
029import com.echothree.model.control.security.common.SecurityRoleGroups;
030import com.echothree.model.control.security.common.SecurityRoles;
031import com.echothree.model.data.party.server.entity.PartyAliasType;
032import com.echothree.model.data.user.common.pk.UserVisitPK;
033import com.echothree.util.common.message.ExecutionErrors;
034import com.echothree.util.common.string.StringUtils;
035import com.echothree.util.common.validation.FieldDefinition;
036import com.echothree.util.common.validation.FieldType;
037import com.echothree.util.common.command.BaseResult;
038import com.echothree.util.common.form.ValidationResult;
039import com.echothree.util.server.control.BaseSimpleCommand;
040import com.echothree.util.server.control.CommandSecurityDefinition;
041import com.echothree.util.server.control.PartyTypeDefinition;
042import com.echothree.util.server.control.SecurityRoleDefinition;
043import com.echothree.util.server.validation.Validator;
044import com.google.common.base.Splitter;
045import java.util.List;
046import java.util.Locale;
047import java.util.regex.Pattern;
048import javax.enterprise.context.Dependent;
049import javax.inject.Inject;
050
051@Dependent
052public class SearchCustomersCommand
053        extends BaseSimpleCommand<SearchCustomersForm> {
054    
055    private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION;
056    private final static List<FieldDefinition> FORM_FIELD_DEFINITIONS;
057    private final static List<FieldDefinition> formAliasFieldDefinitions;
058
059    static {
060        COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(List.of(
061                new PartyTypeDefinition(PartyTypes.UTILITY.name(), null),
062                new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), List.of(
063                        new SecurityRoleDefinition(SecurityRoleGroups.Customer.name(), SecurityRoles.Search.name())
064                ))
065        ));
066        
067        FORM_FIELD_DEFINITIONS = List.of(
068                new FieldDefinition("SearchTypeName", FieldType.ENTITY_NAME, true, null, null),
069                new FieldDefinition("CustomerTypeName", FieldType.ENTITY_NAME, false, null, null),
070                new FieldDefinition("FirstName", FieldType.STRING, false, 1L, 20L),
071                new FieldDefinition("FirstNameSoundex", FieldType.BOOLEAN, false, null, null),
072                new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L),
073                new FieldDefinition("MiddleNameSoundex", FieldType.BOOLEAN, false, null, null),
074                new FieldDefinition("LastName", FieldType.STRING, false, 1L, 20L),
075                new FieldDefinition("LastNameSoundex", FieldType.BOOLEAN, false, null, null),
076                new FieldDefinition("Name", FieldType.STRING, false, null, null),
077                new FieldDefinition("CountryName", FieldType.ENTITY_NAME, false, null, null),
078                new FieldDefinition("AreaCode", FieldType.STRING, false, 1L, 5L),
079                new FieldDefinition("TelephoneNumber", FieldType.STRING, false, 1L, 25L),
080                new FieldDefinition("TelephoneExtension", FieldType.NUMBERS, false, 1L, 10L),
081                new FieldDefinition("EmailAddress", FieldType.EMAIL_ADDRESS, false, null, null),
082                new FieldDefinition("CustomerName", FieldType.ENTITY_NAME, false, null, null),
083                new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null),
084                new FieldDefinition("PartyAliasTypeName", FieldType.ENTITY_NAME, false, null, null),
085                new FieldDefinition("Alias", FieldType.ENTITY_NAME, false, null, null),
086                new FieldDefinition("CreatedSince", FieldType.DATE_TIME, false, null, null),
087                new FieldDefinition("ModifiedSince", FieldType.DATE_TIME, false, null, null),
088                new FieldDefinition("Fields", FieldType.STRING, false, null, null)
089        );
090
091        formAliasFieldDefinitions = List.of(
092                new FieldDefinition("Alias", FieldType.ENTITY_NAME, true, null, null)
093        );
094    }
095
096    @Inject
097    CustomerControl customerControl;
098
099    @Inject
100    GeoControl geoControl;
101
102    @Inject
103    PartyControl partyControl;
104
105    @Inject
106    SearchControl searchControl;
107
108    @Inject
109    SearchLogic searchLogic;
110
111    /** Creates a new instance of SearchCustomersCommand */
112    public SearchCustomersCommand() {
113        super(COMMAND_SECURITY_DEFINITION, FORM_FIELD_DEFINITIONS, false);
114    }
115    
116    @Override
117    protected ValidationResult validate() {
118        var validator = new Validator(this);
119        var validationResult = validator.validate(form, FORM_FIELD_DEFINITIONS);
120        
121        if(!validationResult.getHasErrors()) {
122            var partyAliasTypeName = form.getPartyAliasTypeName();
123            
124            if(partyAliasTypeName != null) {
125                validationResult = validator.validate(form, formAliasFieldDefinitions);
126            }
127        }
128        
129        return validationResult;
130    }
131    
132    @Override
133    protected BaseResult execute() {
134        var result = SearchResultFactory.getSearchCustomersResult();
135        var searchKind = searchControl.getSearchKindByName(SearchKinds.CUSTOMER.name());
136        
137        if(searchKind != null) {
138            var searchTypeName = form.getSearchTypeName();
139            var searchType = searchControl.getSearchTypeByName(searchKind, searchTypeName);
140            
141            if(searchType != null) {
142                var customerTypeName = form.getCustomerTypeName();
143                var customerType = customerTypeName == null? null: customerControl.getCustomerTypeByName(customerTypeName);
144                
145                if(customerTypeName == null || customerType != null) {
146                    var partyAliasTypeName = form.getPartyAliasTypeName();
147                    var alias = form.getAlias();
148                    PartyAliasType partyAliasType = null;
149                    
150                    if(partyAliasTypeName != null) {
151                        var partyType = partyControl.getPartyTypeByName(PartyTypes.CUSTOMER.name());
152                        
153                        if(partyType != null) {
154                            partyAliasType = partyControl.getPartyAliasTypeByName(partyType, partyAliasTypeName);
155
156                            if(partyAliasType == null) {
157                                addExecutionError(ExecutionErrors.UnknownPartyAliasTypeName.name(), PartyTypes.CUSTOMER.name(), partyAliasTypeName);
158                            }
159                        } else {
160                            addExecutionError(ExecutionErrors.UnknownPartyTypeName.name(), PartyTypes.CUSTOMER.name());
161                        }
162                    }
163                    
164                    if(!hasExecutionErrors()) {
165                        var countryName = form.getCountryName();
166                        var countryAlias = countryName == null ? null : StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault());
167                        var countryGeoCode = countryAlias == null ? null : geoControl.getCountryByAlias(countryAlias);
168
169                        if(countryName == null || countryGeoCode != null) {
170                            var geoCodeCountry = countryGeoCode == null ? null : geoControl.getGeoCodeCountry(countryGeoCode);
171                            var areaCode = form.getAreaCode();
172                            var areaCodePattern = geoCodeCountry == null ? null : geoCodeCountry.getAreaCodePattern();
173                            var pattern = areaCodePattern == null ? null : Pattern.compile(areaCodePattern);
174
175                            if(areaCode == null || (pattern == null || pattern.matcher(areaCode).matches())) {
176                                var telephoneNumberPattern = countryGeoCode == null ? null : geoCodeCountry.getTelephoneNumberPattern();
177                                var telephoneNumber = form.getTelephoneNumber();
178
179                                pattern = telephoneNumberPattern == null ? null : Pattern.compile(telephoneNumberPattern);
180
181                                if(telephoneNumber == null || (pattern == null || pattern.matcher(telephoneNumber).matches())) {
182                                    var userVisit = getUserVisit();
183                                    var customerSearchEvaluator = new CustomerSearchEvaluator(userVisit, searchType,
184                                            searchLogic.getDefaultSearchDefaultOperator(null), searchLogic.getDefaultSearchSortOrder(null, searchKind),
185                                            searchLogic.getDefaultSearchSortDirection(null));
186                                    var createdSince = form.getCreatedSince();
187                                    var modifiedSince = form.getModifiedSince();
188                                    var fields = form.getFields();
189
190                                    customerSearchEvaluator.setFirstName(form.getFirstName());
191                                    customerSearchEvaluator.setFirstNameSoundex(Boolean.parseBoolean(form.getFirstNameSoundex()));
192                                    customerSearchEvaluator.setMiddleName(form.getMiddleName());
193                                    customerSearchEvaluator.setMiddleNameSoundex(Boolean.parseBoolean(form.getMiddleNameSoundex()));
194                                    customerSearchEvaluator.setLastName(form.getLastName());
195                                    customerSearchEvaluator.setLastNameSoundex(Boolean.parseBoolean(form.getLastNameSoundex()));
196                                    customerSearchEvaluator.setQ(this, form.getName());
197                                    customerSearchEvaluator.setCountryGeoCode(countryGeoCode);
198                                    customerSearchEvaluator.setAreaCode(areaCode);
199                                    customerSearchEvaluator.setTelephoneNumber(telephoneNumber);
200                                    customerSearchEvaluator.setTelephoneExtension(form.getTelephoneExtension());
201                                    customerSearchEvaluator.setEmailAddress(form.getEmailAddress());
202                                    customerSearchEvaluator.setCustomerType(customerType);
203                                    customerSearchEvaluator.setPartyAliasType(partyAliasType);
204                                    customerSearchEvaluator.setAlias(alias);
205                                    customerSearchEvaluator.setCustomerName(form.getCustomerName());
206                                    customerSearchEvaluator.setPartyName(form.getPartyName());
207                                    customerSearchEvaluator.setCreatedSince(createdSince == null ? null : Long.valueOf(createdSince));
208                                    customerSearchEvaluator.setModifiedSince(modifiedSince == null ? null : Long.valueOf(modifiedSince));
209                                    customerSearchEvaluator.setFields(fields == null ? null : Splitter.on(':').trimResults().omitEmptyStrings().splitToList(fields).toArray(new String[0]));
210
211                                    if(!hasExecutionErrors()) {
212                                        result.setCount(customerSearchEvaluator.execute(this));
213                                    }
214                                } else {
215                                    addExecutionError(ExecutionErrors.InvalidTelephoneNumber.name(), telephoneNumber);
216                                }
217                            } else {
218                                addExecutionError(ExecutionErrors.InvalidAreaCode.name(), areaCode);
219                            }
220                        } else {
221                            addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName);
222                        }
223                    }
224                } else {
225                    addExecutionError(ExecutionErrors.UnknownCustomerTypeName.name(), customerTypeName);
226                }
227            } else {
228                addExecutionError(ExecutionErrors.UnknownSearchTypeName.name(), SearchKinds.CUSTOMER.name(), searchTypeName);
229            }
230        } else {
231            addExecutionError(ExecutionErrors.UnknownSearchKindName.name(), SearchKinds.CUSTOMER.name());
232        }
233        
234        return result;
235    }
236}