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