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.edit.ContactEditFactory; 020import com.echothree.control.user.contact.common.edit.ContactPostalAddressEdit; 021import com.echothree.control.user.contact.common.result.ContactResultFactory; 022import com.echothree.control.user.contact.common.result.EditContactPostalAddressResult; 023import com.echothree.control.user.contact.common.spec.PartyContactMechanismSpec; 024import com.echothree.model.control.contact.common.ContactMechanismTypes; 025import com.echothree.model.control.contact.server.control.ContactControl; 026import com.echothree.model.control.geo.server.control.GeoControl; 027import com.echothree.model.control.party.common.PartyTypes; 028import com.echothree.model.control.party.server.control.PartyControl; 029import com.echothree.model.control.security.common.SecurityRoleGroups; 030import com.echothree.model.control.security.common.SecurityRoles; 031import com.echothree.model.data.contact.server.entity.ContactMechanism; 032import com.echothree.model.data.contact.server.entity.PartyContactMechanism; 033import com.echothree.model.data.geo.server.entity.GeoCode; 034import com.echothree.util.common.command.EditMode; 035import com.echothree.util.common.command.SecurityResult; 036import com.echothree.util.common.message.ExecutionErrors; 037import com.echothree.util.common.string.StringUtils; 038import com.echothree.util.common.validation.FieldDefinition; 039import com.echothree.util.common.validation.FieldType; 040import com.echothree.util.server.control.BaseAbstractEditCommand; 041import com.echothree.util.server.control.CommandSecurityDefinition; 042import com.echothree.util.server.control.PartyTypeDefinition; 043import com.echothree.util.server.control.SecurityRoleDefinition; 044import com.echothree.util.server.persistence.EntityPermission; 045import java.util.List; 046import java.util.Locale; 047import org.apache.commons.codec.language.Soundex; 048import javax.enterprise.context.Dependent; 049import javax.inject.Inject; 050 051@Dependent 052public class EditContactPostalAddressCommand 053 extends BaseAbstractEditCommand<PartyContactMechanismSpec, ContactPostalAddressEdit, EditContactPostalAddressResult, PartyContactMechanism, ContactMechanism> { 054 055 private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION; 056 private final static List<FieldDefinition> SPEC_FIELD_DEFINITIONS; 057 private final static List<FieldDefinition> editCustomerFieldDefinitions; 058 private final static List<FieldDefinition> editOtherFieldDefinitions; 059 060 static { 061 COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(List.of( 062 new PartyTypeDefinition(PartyTypes.UTILITY.name(), null), 063 new PartyTypeDefinition(PartyTypes.CUSTOMER.name(), null), 064 new PartyTypeDefinition(PartyTypes.VENDOR.name(), null), 065 new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), List.of( 066 new SecurityRoleDefinition(SecurityRoleGroups.ContactMechanism.name(), SecurityRoles.Edit.name()) 067 )) 068 )); 069 070 SPEC_FIELD_DEFINITIONS = List.of( 071 new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null), 072 new FieldDefinition("ContactMechanismName", FieldType.ENTITY_NAME, true, null, null) 073 ); 074 075 // customerFormFieldDefinitions differs from otherFormFieldDefinitions in that when the PartyType 076 // executing this command = CUSTOMER, FirstName and LastName are required fields. For all other 077 // PartyTypes, that requirement is relaxed. 078 editCustomerFieldDefinitions = List.of( 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, false, 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 editOtherFieldDefinitions = List.of( 099 new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null), 100 new FieldDefinition("FirstName", FieldType.STRING, false, 1L, 20L), 101 new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L), 102 new FieldDefinition("LastName", FieldType.STRING, false, 1L, 20L), 103 new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null), 104 new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L), 105 new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L), 106 new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L), 107 new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L), 108 new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L), 109 new FieldDefinition("City", FieldType.STRING, false, 1L, 30L), 110 new FieldDefinition("State", FieldType.STRING, false, 1L, 30L), 111 new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L), 112 new FieldDefinition("CountryName", FieldType.ENTITY_NAME, false, null, null), 113 new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null), 114 new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null), 115 new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L) 116 ); 117 } 118 119 @Inject 120 ContactControl contactControl; 121 122 @Inject 123 GeoControl geoControl; 124 125 @Inject 126 PartyControl partyControl; 127 128 /** Creates a new instance of EditContactPostalAddressCommand */ 129 public EditContactPostalAddressCommand() { 130 super(COMMAND_SECURITY_DEFINITION, SPEC_FIELD_DEFINITIONS, null); 131 } 132 133 @Override 134 protected List<FieldDefinition> getEditFieldDefinitions() { 135 var partyTypeName = getPartyTypeName(); 136 137 return partyTypeName.equals(PartyTypes.CUSTOMER.name()) ? editCustomerFieldDefinitions : editOtherFieldDefinitions; 138 } 139 140 @Override 141 protected SecurityResult security() { 142 var securityResult = super.security(); 143 144 return securityResult != null ? securityResult : selfOnly(spec); 145 } 146 147 @Override 148 public EditContactPostalAddressResult getResult() { 149 return ContactResultFactory.getEditContactPostalAddressResult(); 150 } 151 152 @Override 153 public ContactPostalAddressEdit getEdit() { 154 return ContactEditFactory.getContactPostalAddressEdit(); 155 } 156 157 @Override 158 public PartyContactMechanism getEntity(EditContactPostalAddressResult result) { 159 PartyContactMechanism partyContactMechanism = null; 160 var partyName = spec.getPartyName(); 161 var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName); 162 163 if(party != null) { 164 var contactMechanismName = spec.getContactMechanismName(); 165 var contactMechanism = contactControl.getContactMechanismByName(contactMechanismName); 166 167 if(contactMechanism != null) { 168 if(editMode.equals(EditMode.LOCK) || editMode.equals(EditMode.ABANDON)) { 169 partyContactMechanism = contactControl.getPartyContactMechanism(party, contactMechanism); 170 } else { // EditMode.UPDATE 171 partyContactMechanism = contactControl.getPartyContactMechanismForUpdate(party, contactMechanism); 172 } 173 174 if(partyContactMechanism != null) { 175 var lastContactMechanismDetail = contactMechanism.getLastDetail(); 176 var contactMechanismTypeName = lastContactMechanismDetail.getContactMechanismType().getContactMechanismTypeName(); 177 178 result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(), contactMechanism)); 179 180 if(!ContactMechanismTypes.POSTAL_ADDRESS.name().equals(contactMechanismTypeName)) { 181 addExecutionError(ExecutionErrors.InvalidContactMechanismType.name(), contactMechanismTypeName); 182 } 183 } else { 184 addExecutionError(ExecutionErrors.UnknownPartyContactMechanism.name(), partyName, contactMechanismName); 185 } 186 } else { 187 addExecutionError(ExecutionErrors.UnknownContactMechanismName.name(), contactMechanismName); 188 } 189 } else { 190 addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName); 191 } 192 193 return partyContactMechanism; 194 } 195 196 @Override 197 public ContactMechanism getLockEntity(PartyContactMechanism partyContactMechanism) { 198 return partyContactMechanism.getLastDetail().getContactMechanism(); 199 } 200 201 @Override 202 public void fillInResult(EditContactPostalAddressResult result, PartyContactMechanism partyContactMechanism) { 203 result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(), 204 partyContactMechanism.getLastDetail().getContactMechanism())); 205 } 206 207 @Override 208 public void doLock(ContactPostalAddressEdit edit, PartyContactMechanism partyContactMechanism) { 209 var contactMechanism = partyContactMechanism.getLastDetail().getContactMechanism(); 210 var contactMechanismDetail = contactMechanism.getLastDetail(); 211 var contactPostalAddress = contactControl.getContactPostalAddress(contactMechanism); 212 var partyContactMechanismDetail = partyContactMechanism.getLastDetail(); 213 214 var personalTitle = contactPostalAddress.getPersonalTitle(); 215 var nameSuffix = contactPostalAddress.getNameSuffix(); 216 217 var city = contactPostalAddress.getCity(); 218 var state = contactPostalAddress.getState(); 219 var postalCode = contactPostalAddress.getPostalCode(); 220 221 var cityGeoCode = city == null ? contactPostalAddress.getCityGeoCode() : null; 222 var stateGeoCode = state == null ? contactPostalAddress.getStateGeoCode() : null; 223 var postalCodeGeoCode = postalCode == null ? contactPostalAddress.getPostalCodeGeoCode() : null; 224 225 var cityGeoCodeDescription = cityGeoCode == null ? null : geoControl.getBestGeoCodeDescription(cityGeoCode, getPreferredLanguage()); 226 var stateGeoCodeDescription = stateGeoCode == null ? null : geoControl.getBestGeoCodeDescription(stateGeoCode, getPreferredLanguage()); 227 var postalCodeGeoCodeDescription = postalCodeGeoCode == null ? null : geoControl.getBestGeoCodeDescription(postalCodeGeoCode, getPreferredLanguage()); 228 229 edit.setAllowSolicitation(contactMechanismDetail.getAllowSolicitation().toString()); 230 edit.setPersonalTitleId(personalTitle == null ? null : personalTitle.getPrimaryKey().getEntityId().toString()); 231 edit.setFirstName(contactPostalAddress.getFirstName()); 232 edit.setMiddleName(contactPostalAddress.getMiddleName()); 233 edit.setLastName(contactPostalAddress.getLastName()); 234 edit.setNameSuffixId(nameSuffix == null ? null : nameSuffix.getPrimaryKey().getEntityId().toString()); 235 edit.setCompanyName(contactPostalAddress.getCompanyName()); 236 edit.setAttention(contactPostalAddress.getAttention()); 237 edit.setAddress1(contactPostalAddress.getAddress1()); 238 edit.setAddress2(contactPostalAddress.getAddress2()); 239 edit.setAddress3(contactPostalAddress.getAddress3()); 240 edit.setCity(city == null ? (cityGeoCode == null ? null : (cityGeoCodeDescription == null ? geoControl.getAliasForCity(cityGeoCode) : cityGeoCodeDescription)) : city); 241 edit.setState(state == null ? (stateGeoCode == null ? null : (stateGeoCodeDescription == null ? geoControl.getAliasForState(stateGeoCode) : stateGeoCodeDescription)) : state); 242 edit.setPostalCode(postalCode == null ? (postalCodeGeoCode == null ? null : (postalCodeGeoCodeDescription == null ? geoControl.getAliasForPostalCode(postalCodeGeoCode) : postalCodeGeoCodeDescription)) : postalCode); 243 edit.setCountryName(geoControl.getAliasForCountry(contactPostalAddress.getCountryGeoCode())); 244 edit.setIsCommercial(contactPostalAddress.getIsCommercial().toString()); 245 edit.setDescription(partyContactMechanismDetail.getDescription()); 246 } 247 248 GeoCode countryGeoCode; 249 String state; 250 GeoCode stateGeoCode; 251 String city; 252 GeoCode cityGeoCode; 253 String postalCode; 254 GeoCode postalCodeGeoCode; 255 GeoCode countyGeoCode; 256 257 @Override 258 public void canUpdate(PartyContactMechanism partyContactMechanism) { 259 var countryName = edit.getCountryName(); 260 var countryAlias = StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault()); 261 262 countryGeoCode = geoControl.getCountryByAlias(countryAlias); 263 264 if(countryGeoCode != null) { 265 var geoCodeCountry = geoControl.getGeoCodeCountry(countryGeoCode); 266 267 postalCode = edit.getPostalCode(); 268 269 if(postalCode != null) { 270 postalCode = postalCode.toUpperCase(Locale.getDefault()); 271 } 272 273 if(!geoCodeCountry.getPostalCodeRequired() || postalCode != null) { 274 var postalCodePattern = geoCodeCountry.getPostalCodePattern(); 275 var postalCodeLength = geoCodeCountry.getPostalCodeLength(); 276 277 if(postalCodeLength == null) { 278 postalCodeLength = Integer.MAX_VALUE; 279 } 280 281 if(postalCode == null || ((postalCodePattern == null || postalCode.matches(postalCodePattern)) && (postalCode.length() <= postalCodeLength))) { 282 var postalCodeAlias = postalCode == null ? null : StringUtils.getInstance().cleanStringToLettersOrDigits(StringUtils.getInstance().cleanStringToName(postalCode)); 283 284 if(postalCodeAlias != null) { 285 var postalCodeAliasLength = postalCodeAlias.length(); 286 var postalCodeGeoCodeLength = geoCodeCountry.getPostalCodeGeoCodeLength(); 287 288 if(postalCodeGeoCodeLength == null || postalCodeAliasLength >= postalCodeGeoCodeLength) { 289 if(postalCodeGeoCodeLength != null && postalCodeAliasLength > postalCodeGeoCodeLength) { 290 postalCodeAlias = postalCodeAlias.substring(0, postalCodeGeoCodeLength); 291 } 292 293 postalCodeGeoCode = geoControl.getPostalCodeByAlias(countryGeoCode, postalCodeAlias); 294 } 295 } 296 297 if(!geoCodeCountry.getPostalCodeGeoCodeRequired() || postalCodeGeoCode != null) { 298 state = edit.getState(); 299 300 if(!geoCodeCountry.getStateRequired() || state != null) { 301 var stateAlias = state == null ? null : StringUtils.getInstance().cleanStringToName(state).toUpperCase(Locale.getDefault()); 302 303 if(stateAlias != null) { 304 stateGeoCode = geoControl.getStateByAlias(countryGeoCode, stateAlias); 305 } 306 307 if(!geoCodeCountry.getStateGeoCodeRequired() || stateGeoCode != null) { 308 city = edit.getCity(); 309 310 if(!geoCodeCountry.getCityRequired() || city != null) { 311 var cityAlias = city == null ? null : StringUtils.getInstance().cleanStringToName(city).toUpperCase(Locale.getDefault()); 312 313 if(stateGeoCode != null && cityAlias != null) { 314 cityGeoCode = geoControl.getCityByAlias(stateGeoCode, cityAlias); 315 } 316 317 if(!geoCodeCountry.getCityGeoCodeRequired() || cityGeoCode != null) { 318 // TODO: countyGeoCode 319 } else { 320 addExecutionError(ExecutionErrors.UnknownCity.name(), city, cityAlias); 321 } 322 } else { 323 addExecutionError(ExecutionErrors.MissingCity.name()); 324 } 325 } else { 326 addExecutionError(ExecutionErrors.UnknownState.name(), state, stateAlias); 327 } 328 } else { 329 addExecutionError(ExecutionErrors.MissingState.name()); 330 } 331 } else { 332 addExecutionError(ExecutionErrors.UnknownPostalCode.name(), postalCode); 333 } 334 } else { 335 addExecutionError(ExecutionErrors.InvalidPostalCode.name(), postalCode); 336 } 337 } else { 338 addExecutionError(ExecutionErrors.MissingPostalCode.name()); 339 } 340 } else { 341 addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName, countryAlias); 342 } 343 } 344 345 @Override 346 public void doUpdate(PartyContactMechanism partyContactMechanism) { 347 var soundex = new Soundex(); 348 var updatedBy = getPartyPK(); 349 var contactMechanism = partyContactMechanism.getLastDetail().getContactMechanism(); 350 var contactMechanismDetailValue = contactControl.getContactMechanismDetailValue(contactMechanism.getLastDetail()); 351 var contactPostalAddressValue = contactControl.getContactPostalAddressValueForUpdate(contactMechanism); 352 var partyContactMechanismDetailValue = contactControl.getPartyContactMechanismDetailValueForUpdate(partyContactMechanism); 353 354 var personalTitleId = edit.getPersonalTitleId(); 355 var personalTitle = personalTitleId == null ? null : partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY); 356 var firstName = edit.getFirstName(); 357 var middleName = edit.getMiddleName(); 358 var lastName = edit.getLastName(); 359 var nameSuffixId = edit.getNameSuffixId(); 360 var nameSuffix = nameSuffixId == null ? null : partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY); 361 362 String firstNameSdx; 363 try { 364 firstNameSdx = firstName == null ? null : soundex.encode(firstName); 365 } catch(IllegalArgumentException iae) { 366 firstNameSdx = null; 367 } 368 369 String middleNameSdx; 370 try { 371 middleNameSdx = middleName == null ? null : soundex.encode(middleName); 372 } catch(IllegalArgumentException iae) { 373 middleNameSdx = null; 374 } 375 376 String lastNameSdx; 377 try { 378 lastNameSdx = lastName == null ? null : soundex.encode(lastName); 379 } catch(IllegalArgumentException iae) { 380 lastNameSdx = null; 381 } 382 383 contactMechanismDetailValue.setAllowSolicitation(Boolean.valueOf(edit.getAllowSolicitation())); 384 contactPostalAddressValue.setCountryGeoCodePK(countryGeoCode.getPrimaryKey()); 385 contactPostalAddressValue.setPersonalTitlePK(personalTitle == null ? null : personalTitle.getPrimaryKey()); 386 contactPostalAddressValue.setFirstName(firstName); 387 contactPostalAddressValue.setFirstNameSdx(firstNameSdx); 388 contactPostalAddressValue.setMiddleName(middleName); 389 contactPostalAddressValue.setMiddleNameSdx(middleNameSdx); 390 contactPostalAddressValue.setLastName(lastName); 391 contactPostalAddressValue.setLastNameSdx(lastNameSdx); 392 contactPostalAddressValue.setNameSuffixPK(nameSuffix == null ? null : nameSuffix.getPrimaryKey()); 393 contactPostalAddressValue.setCompanyName(edit.getCompanyName()); 394 contactPostalAddressValue.setAttention(edit.getAttention()); 395 contactPostalAddressValue.setAddress1(edit.getAddress1()); 396 contactPostalAddressValue.setAddress2(edit.getAddress2()); 397 contactPostalAddressValue.setAddress3(edit.getAddress3()); 398 contactPostalAddressValue.setCity(city); 399 contactPostalAddressValue.setCityGeoCodePK(cityGeoCode == null ? null : cityGeoCode.getPrimaryKey()); 400 contactPostalAddressValue.setCountyGeoCodePK(countyGeoCode == null ? null : countyGeoCode.getPrimaryKey()); 401 contactPostalAddressValue.setState(state); 402 contactPostalAddressValue.setStateGeoCodePK(stateGeoCode == null ? null : stateGeoCode.getPrimaryKey()); 403 contactPostalAddressValue.setPostalCode(postalCode); 404 contactPostalAddressValue.setPostalCodeGeoCodePK(postalCodeGeoCode == null ? null : postalCodeGeoCode.getPrimaryKey()); 405 contactPostalAddressValue.setCountryGeoCodePK(countryGeoCode == null ? null : countryGeoCode.getPrimaryKey()); 406 contactPostalAddressValue.setIsCommercial(Boolean.valueOf(edit.getIsCommercial())); 407 partyContactMechanismDetailValue.setDescription(edit.getDescription()); 408 409 contactControl.updateContactMechanismFromValue(contactMechanismDetailValue, updatedBy); 410 contactControl.updateContactPostalAddressFromValue(contactPostalAddressValue, updatedBy); 411 contactControl.updatePartyContactMechanismFromValue(partyContactMechanismDetailValue, updatedBy); 412 } 413 414} 415