001// --------------------------------------------------------------------------------
002// Copyright 2002-2025 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.contact.server.command;
018
019import com.echothree.control.user.contact.common.form.CreateContactPostalAddressForm;
020import com.echothree.control.user.contact.common.result.ContactResultFactory;
021import com.echothree.model.control.contact.common.ContactMechanismTypes;
022import com.echothree.model.control.contact.common.workflow.PostalAddressStatusConstants;
023import com.echothree.model.control.contact.server.control.ContactControl;
024import com.echothree.model.control.core.server.control.EntityInstanceControl;
025import com.echothree.model.control.geo.server.control.GeoControl;
026import com.echothree.model.control.party.common.PartyTypes;
027import com.echothree.model.control.party.server.control.PartyControl;
028import com.echothree.model.control.security.common.SecurityRoleGroups;
029import com.echothree.model.control.security.common.SecurityRoles;
030import com.echothree.model.control.sequence.common.SequenceTypes;
031import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic;
032import com.echothree.model.control.workflow.server.control.WorkflowControl;
033import com.echothree.model.data.geo.server.entity.GeoCode;
034import com.echothree.model.data.user.common.pk.UserVisitPK;
035import com.echothree.util.common.command.BaseResult;
036import com.echothree.util.common.command.SecurityResult;
037import com.echothree.util.common.form.ValidationResult;
038import com.echothree.util.common.message.ExecutionErrors;
039import com.echothree.util.common.persistence.BasePK;
040import com.echothree.util.common.string.StringUtils;
041import com.echothree.util.common.validation.FieldDefinition;
042import com.echothree.util.common.validation.FieldType;
043import com.echothree.util.server.control.BaseSimpleCommand;
044import com.echothree.util.server.control.CommandSecurityDefinition;
045import com.echothree.util.server.control.PartyTypeDefinition;
046import com.echothree.util.server.control.SecurityRoleDefinition;
047import com.echothree.util.server.persistence.EntityPermission;
048import com.echothree.util.server.persistence.Session;
049import com.echothree.util.server.validation.Validator;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collections;
053import java.util.List;
054import java.util.Locale;
055import org.apache.commons.codec.language.Soundex;
056import javax.enterprise.context.RequestScoped;
057
058@RequestScoped
059public class CreateContactPostalAddressCommand
060        extends BaseSimpleCommand<CreateContactPostalAddressForm> {
061    
062    private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION;
063    private final static List<FieldDefinition> customerFormFieldDefinitions;
064    private final static List<FieldDefinition> otherFormFieldDefinitions;
065    
066    static {
067        COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(Collections.unmodifiableList(Arrays.asList(
068                new PartyTypeDefinition(PartyTypes.UTILITY.name(), null),
069                new PartyTypeDefinition(PartyTypes.CUSTOMER.name(), null),
070                new PartyTypeDefinition(PartyTypes.VENDOR.name(), null),
071                new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), Collections.unmodifiableList(Arrays.asList(
072                        new SecurityRoleDefinition(SecurityRoleGroups.ContactMechanism.name(), SecurityRoles.Create.name())
073                        )))
074                )));
075
076        // customerFormFieldDefinitions differs from otherFormFieldDefinitions in that when the PartyType
077        // executing this command = CUSTOMER, FirstName and LastName are required fields. For all other
078        // PartyTypes, that requirement is relaxed.
079        List<FieldDefinition> temp = new ArrayList<>(17);
080        temp.add(new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null));
081        temp.add(new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null));
082        temp.add(new FieldDefinition("FirstName", FieldType.STRING, true, 1L, 20L));
083        temp.add(new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L));
084        temp.add(new FieldDefinition("LastName", FieldType.STRING, true, 1L, 20L));
085        temp.add(new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null));
086        temp.add(new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L));
087        temp.add(new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L));
088        temp.add(new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L));
089        temp.add(new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L));
090        temp.add(new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L));
091        temp.add(new FieldDefinition("City", FieldType.STRING, false, 1L, 30L));
092        temp.add(new FieldDefinition("State", FieldType.STRING, false, 1L, 30L));
093        temp.add(new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L));
094        temp.add(new FieldDefinition("CountryName", FieldType.ENTITY_NAME, true, null, null));
095        temp.add(new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null));
096        temp.add(new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null));
097        temp.add(new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L));
098        customerFormFieldDefinitions = Collections.unmodifiableList(temp);
099        
100        temp = new ArrayList<>(17);
101        temp.add(new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null));
102        temp.add(new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null));
103        temp.add(new FieldDefinition("FirstName", FieldType.STRING, false, 1L, 20L));
104        temp.add(new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L));
105        temp.add(new FieldDefinition("LastName", FieldType.STRING, false, 1L, 20L));
106        temp.add(new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null));
107        temp.add(new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L));
108        temp.add(new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L));
109        temp.add(new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L));
110        temp.add(new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L));
111        temp.add(new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L));
112        temp.add(new FieldDefinition("City", FieldType.STRING, false, 1L, 30L));
113        temp.add(new FieldDefinition("State", FieldType.STRING, false, 1L, 30L));
114        temp.add(new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L));
115        temp.add(new FieldDefinition("CountryName", FieldType.ENTITY_NAME, true, null, null));
116        temp.add(new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null));
117        temp.add(new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null));
118        temp.add(new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L));
119        otherFormFieldDefinitions = Collections.unmodifiableList(temp);
120    }
121    
122    /** Creates a new instance of CreateContactPostalAddressCommand */
123    public CreateContactPostalAddressCommand() {
124        super(COMMAND_SECURITY_DEFINITION, null, false);
125    }
126
127    @Override
128    protected SecurityResult security() {
129        var securityResult = super.security();
130
131        return securityResult != null ? securityResult : selfOnly(form);
132    }
133
134    @Override
135    protected ValidationResult validate() {
136        var partyTypeName = getPartyTypeName();
137        var FORM_FIELD_DEFINITIONS = partyTypeName.equals(PartyTypes.CUSTOMER.name())? customerFormFieldDefinitions: otherFormFieldDefinitions;
138        var validator = new Validator(this);
139        var validationResult = validator.validate(form, FORM_FIELD_DEFINITIONS);
140        
141        return validationResult;
142    }
143    
144    @Override
145    protected BaseResult execute() {
146        var result = ContactResultFactory.getCreateContactPostalAddressResult();
147        var partyControl = Session.getModelController(PartyControl.class);
148        var partyName = form.getPartyName();
149        var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName);
150        
151        if(party != null) {
152            var geoControl = Session.getModelController(GeoControl.class);
153            var countryName = form.getCountryName();
154            var countryAlias = StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault());
155            var countryGeoCode = geoControl.getCountryByAlias(countryAlias);
156            
157            if(countryGeoCode != null) {
158                var geoCodeCountry = geoControl.getGeoCodeCountry(countryGeoCode);
159                var postalCode = form.getPostalCode();
160
161                if(postalCode != null) {
162                    postalCode = postalCode.toUpperCase(Locale.getDefault());
163                }
164
165                if(!geoCodeCountry.getPostalCodeRequired() || postalCode != null) {
166                    var postalCodePattern = geoCodeCountry.getPostalCodePattern();
167                    var postalCodeLength = geoCodeCountry.getPostalCodeLength();
168                    
169                    if(postalCodeLength == null) {
170                        postalCodeLength = Integer.MAX_VALUE;
171                    }
172                    
173                    if(postalCode == null || ((postalCodePattern == null || postalCode.matches(postalCodePattern)) && (postalCode.length() <= postalCodeLength))) {
174                        GeoCode postalCodeGeoCode = null;
175                        var postalCodeAlias = postalCode == null? null: StringUtils.getInstance().cleanStringToLettersOrDigits(StringUtils.getInstance().cleanStringToName(postalCode));
176                        
177                        if(postalCodeAlias != null) {
178                            var postalCodeAliasLength = postalCodeAlias.length();
179                            var postalCodeGeoCodeLength = geoCodeCountry.getPostalCodeGeoCodeLength();
180                            
181                            if(postalCodeGeoCodeLength == null || postalCodeAliasLength >= postalCodeGeoCodeLength) {
182                                if(postalCodeGeoCodeLength != null && postalCodeAliasLength > postalCodeGeoCodeLength) {
183                                    postalCodeAlias = postalCodeAlias.substring(0, postalCodeGeoCodeLength);
184                                }
185
186                                postalCodeGeoCode = geoControl.getPostalCodeByAlias(countryGeoCode, postalCodeAlias);
187                            }
188                        }
189                        
190                        if(!geoCodeCountry.getPostalCodeGeoCodeRequired() || postalCodeGeoCode != null) {
191                            var state = form.getState();
192                            
193                            if(!geoCodeCountry.getStateRequired() || state != null) {
194                                GeoCode stateGeoCode = null;
195                                var stateAlias = state == null? null: StringUtils.getInstance().cleanStringToName(state).toUpperCase(Locale.getDefault());
196                                
197                                if(stateAlias != null) {
198                                    stateGeoCode = geoControl.getStateByAlias(countryGeoCode, stateAlias);
199                                }
200                                
201                                if(!geoCodeCountry.getStateGeoCodeRequired() || stateGeoCode != null) {
202                                    var city = form.getCity();
203                                    
204                                    if(!geoCodeCountry.getCityRequired() || city != null) {
205                                        GeoCode cityGeoCode = null;
206                                        var cityAlias = city == null? null: StringUtils.getInstance().cleanStringToName(city).toUpperCase(Locale.getDefault());
207                                        
208                                        if(stateGeoCode != null && cityAlias != null) {
209                                            cityGeoCode = geoControl.getCityByAlias(stateGeoCode, cityAlias);
210                                        }
211
212                                        if(!geoCodeCountry.getCityGeoCodeRequired() || cityGeoCode != null) {
213                                            GeoCode countyGeoCode = null;
214                                            
215                                            var contactControl = Session.getModelController(ContactControl.class);
216                                            var entityInstanceControl = Session.getModelController(EntityInstanceControl.class);
217                                            var workflowControl = Session.getModelController(WorkflowControl.class);
218                                            var soundex = new Soundex();
219                                            BasePK createdBy = getPartyPK();
220                                            var personalTitleId = form.getPersonalTitleId();
221                                            var personalTitle = personalTitleId == null? null: partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY);
222                                            var firstName = form.getFirstName();
223                                            var middleName = form.getMiddleName();
224                                            var lastName = form.getLastName();
225                                            var nameSuffixId = form.getNameSuffixId();
226                                            var nameSuffix = nameSuffixId == null? null: partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY);
227                                            var companyName = form.getCompanyName();
228                                            var attention = form.getAttention();
229                                            var address1 = form.getAddress1();
230                                            var address2 = form.getAddress2();
231                                            var address3 = form.getAddress3();
232                                            var allowSolicitation = Boolean.valueOf(form.getAllowSolicitation());
233                                            var isCommercial = Boolean.valueOf(form.getIsCommercial());
234                                            var description = form.getDescription();
235                                            var contactMechanismName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(null, SequenceTypes.CONTACT_MECHANISM.name());
236                                            
237                                            String firstNameSdx;
238                                            try {
239                                                firstNameSdx = firstName == null? null: soundex.encode(firstName);
240                                            } catch (IllegalArgumentException iae) {
241                                                firstNameSdx = null;
242                                            }
243                                            
244                                            String middleNameSdx;
245                                            try {
246                                                middleNameSdx = middleName == null? null: soundex.encode(middleName);
247                                            } catch (IllegalArgumentException iae) {
248                                                middleNameSdx = null;
249                                            }
250                                            
251                                            String lastNameSdx;
252                                            try {
253                                                lastNameSdx = lastName == null? null: soundex.encode(lastName);
254                                            } catch (IllegalArgumentException iae) {
255                                                lastNameSdx = null;
256                                            }
257
258                                            var contactMechanismType = contactControl.getContactMechanismTypeByName(ContactMechanismTypes.POSTAL_ADDRESS.name());
259                                            var contactMechanism = contactControl.createContactMechanism(contactMechanismName, contactMechanismType,
260                                                    allowSolicitation, createdBy);
261                                            
262                                            contactControl.createContactPostalAddress(contactMechanism, personalTitle, firstName, firstNameSdx, middleName,
263                                                    middleNameSdx, lastName, lastNameSdx, nameSuffix, companyName, attention, address1, address2, address3, city,
264                                                    cityGeoCode, countyGeoCode, state, stateGeoCode, postalCode, postalCodeGeoCode, countryGeoCode, isCommercial,
265                                                    createdBy);
266                                            
267                                            contactControl.createPartyContactMechanism(party, contactMechanism, description,
268                                                    false, 1, createdBy);
269
270                                            var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(contactMechanism.getPrimaryKey());
271                                            workflowControl.addEntityToWorkflowUsingNames(null, PostalAddressStatusConstants.Workflow_POSTAL_ADDRESS_STATUS,
272                                                    PostalAddressStatusConstants.WorkflowEntrance_NEW_POSTAL_ADDRESS, entityInstance, null, null, createdBy);
273                                            
274                                            result.setContactMechanismName(contactMechanism.getLastDetail().getContactMechanismName());
275                                            result.setEntityRef(contactMechanism.getPrimaryKey().getEntityRef());
276                                        } else {
277                                            addExecutionError(ExecutionErrors.UnknownCity.name(), city, cityAlias);
278                                        }
279                                    } else {
280                                        addExecutionError(ExecutionErrors.MissingCity.name());
281                                    }
282                                } else {
283                                    addExecutionError(ExecutionErrors.UnknownState.name(), state, stateAlias);
284                                }
285                            } else {
286                                addExecutionError(ExecutionErrors.MissingState.name());
287                            }
288                        } else {
289                            addExecutionError(ExecutionErrors.UnknownPostalCode.name(), postalCode);
290                        }
291                    } else {
292                        addExecutionError(ExecutionErrors.InvalidPostalCode.name(), postalCode);
293                    }
294                } else {
295                    addExecutionError(ExecutionErrors.MissingPostalCode.name());
296                }
297            } else {
298                addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName, countryAlias);
299            }
300        } else {
301            addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName);
302        }
303        
304        return result;
305    }
306    
307}