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.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 com.echothree.util.server.persistence.Session; 046import java.util.Arrays; 047import java.util.Collections; 048import java.util.List; 049import java.util.Locale; 050import org.apache.commons.codec.language.Soundex; 051import javax.enterprise.context.RequestScoped; 052 053@RequestScoped 054public class EditContactPostalAddressCommand 055 extends BaseAbstractEditCommand<PartyContactMechanismSpec, ContactPostalAddressEdit, EditContactPostalAddressResult, PartyContactMechanism, ContactMechanism> { 056 057 private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION; 058 private final static List<FieldDefinition> SPEC_FIELD_DEFINITIONS; 059 private final static List<FieldDefinition> editCustomerFieldDefinitions; 060 private final static List<FieldDefinition> editOtherFieldDefinitions; 061 062 static { 063 COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(Collections.unmodifiableList(Arrays.asList( 064 new PartyTypeDefinition(PartyTypes.UTILITY.name(), null), 065 new PartyTypeDefinition(PartyTypes.CUSTOMER.name(), null), 066 new PartyTypeDefinition(PartyTypes.VENDOR.name(), null), 067 new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), Collections.unmodifiableList(Arrays.asList( 068 new SecurityRoleDefinition(SecurityRoleGroups.ContactMechanism.name(), SecurityRoles.Edit.name()) 069 ))) 070 ))); 071 072 SPEC_FIELD_DEFINITIONS = Collections.unmodifiableList(Arrays.asList( 073 new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null), 074 new FieldDefinition("ContactMechanismName", FieldType.ENTITY_NAME, true, null, null) 075 )); 076 077 // customerFormFieldDefinitions differs from otherFormFieldDefinitions in that when the PartyType 078 // executing this command = CUSTOMER, FirstName and LastName are required fields. For all other 079 // PartyTypes, that requirement is relaxed. 080 editCustomerFieldDefinitions = Collections.unmodifiableList(Arrays.asList( 081 new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null), 082 new FieldDefinition("FirstName", FieldType.STRING, true, 1L, 20L), 083 new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L), 084 new FieldDefinition("LastName", FieldType.STRING, true, 1L, 20L), 085 new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null), 086 new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L), 087 new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L), 088 new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L), 089 new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L), 090 new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L), 091 new FieldDefinition("City", FieldType.STRING, false, 1L, 30L), 092 new FieldDefinition("State", FieldType.STRING, false, 1L, 30L), 093 new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L), 094 new FieldDefinition("CountryName", FieldType.ENTITY_NAME, false, null, null), 095 new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null), 096 new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null), 097 new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L) 098 )); 099 100 editOtherFieldDefinitions = Collections.unmodifiableList(Arrays.asList( 101 new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null), 102 new FieldDefinition("FirstName", FieldType.STRING, false, 1L, 20L), 103 new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L), 104 new FieldDefinition("LastName", FieldType.STRING, false, 1L, 20L), 105 new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null), 106 new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L), 107 new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L), 108 new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L), 109 new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L), 110 new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L), 111 new FieldDefinition("City", FieldType.STRING, false, 1L, 30L), 112 new FieldDefinition("State", FieldType.STRING, false, 1L, 30L), 113 new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L), 114 new FieldDefinition("CountryName", FieldType.ENTITY_NAME, false, null, null), 115 new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null), 116 new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null), 117 new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L) 118 )); 119 } 120 121 /** Creates a new instance of EditContactPostalAddressCommand */ 122 public EditContactPostalAddressCommand() { 123 super(COMMAND_SECURITY_DEFINITION, SPEC_FIELD_DEFINITIONS, null); 124 } 125 126 @Override 127 protected List<FieldDefinition> getEditFieldDefinitions() { 128 var partyTypeName = getPartyTypeName(); 129 130 return partyTypeName.equals(PartyTypes.CUSTOMER.name()) ? editCustomerFieldDefinitions : editOtherFieldDefinitions; 131 } 132 133 @Override 134 protected SecurityResult security() { 135 var securityResult = super.security(); 136 137 return securityResult != null ? securityResult : selfOnly(spec); 138 } 139 140 @Override 141 public EditContactPostalAddressResult getResult() { 142 return ContactResultFactory.getEditContactPostalAddressResult(); 143 } 144 145 @Override 146 public ContactPostalAddressEdit getEdit() { 147 return ContactEditFactory.getContactPostalAddressEdit(); 148 } 149 150 @Override 151 public PartyContactMechanism getEntity(EditContactPostalAddressResult result) { 152 var partyControl = Session.getModelController(PartyControl.class); 153 PartyContactMechanism partyContactMechanism = null; 154 var partyName = spec.getPartyName(); 155 var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName); 156 157 if(party != null) { 158 var contactControl = Session.getModelController(ContactControl.class); 159 var contactMechanismName = spec.getContactMechanismName(); 160 var contactMechanism = contactControl.getContactMechanismByName(contactMechanismName); 161 162 if(contactMechanism != null) { 163 if(editMode.equals(EditMode.LOCK) || editMode.equals(EditMode.ABANDON)) { 164 partyContactMechanism = contactControl.getPartyContactMechanism(party, contactMechanism); 165 } else { // EditMode.UPDATE 166 partyContactMechanism = contactControl.getPartyContactMechanismForUpdate(party, contactMechanism); 167 } 168 169 if(partyContactMechanism != null) { 170 var lastContactMechanismDetail = contactMechanism.getLastDetail(); 171 var contactMechanismTypeName = lastContactMechanismDetail.getContactMechanismType().getContactMechanismTypeName(); 172 173 result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(), contactMechanism)); 174 175 if(!ContactMechanismTypes.POSTAL_ADDRESS.name().equals(contactMechanismTypeName)) { 176 addExecutionError(ExecutionErrors.InvalidContactMechanismType.name(), contactMechanismTypeName); 177 } 178 } else { 179 addExecutionError(ExecutionErrors.UnknownPartyContactMechanism.name(), partyName, contactMechanismName); 180 } 181 } else { 182 addExecutionError(ExecutionErrors.UnknownContactMechanismName.name(), contactMechanismName); 183 } 184 } else { 185 addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName); 186 } 187 188 return partyContactMechanism; 189 } 190 191 @Override 192 public ContactMechanism getLockEntity(PartyContactMechanism partyContactMechanism) { 193 return partyContactMechanism.getLastDetail().getContactMechanism(); 194 } 195 196 @Override 197 public void fillInResult(EditContactPostalAddressResult result, PartyContactMechanism partyContactMechanism) { 198 var contactControl = Session.getModelController(ContactControl.class); 199 200 result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(), 201 partyContactMechanism.getLastDetail().getContactMechanism())); 202 } 203 204 @Override 205 public void doLock(ContactPostalAddressEdit edit, PartyContactMechanism partyContactMechanism) { 206 var contactControl = Session.getModelController(ContactControl.class); 207 var geoControl = Session.getModelController(GeoControl.class); 208 var contactMechanism = partyContactMechanism.getLastDetail().getContactMechanism(); 209 var contactMechanismDetail = contactMechanism.getLastDetail(); 210 var contactPostalAddress = contactControl.getContactPostalAddress(contactMechanism); 211 var partyContactMechanismDetail = partyContactMechanism.getLastDetail(); 212 213 var personalTitle = contactPostalAddress.getPersonalTitle(); 214 var nameSuffix = contactPostalAddress.getNameSuffix(); 215 216 var city = contactPostalAddress.getCity(); 217 var state = contactPostalAddress.getState(); 218 var postalCode = contactPostalAddress.getPostalCode(); 219 220 var cityGeoCode = city == null ? contactPostalAddress.getCityGeoCode() : null; 221 var stateGeoCode = state == null ? contactPostalAddress.getStateGeoCode() : null; 222 var postalCodeGeoCode = postalCode == null ? contactPostalAddress.getPostalCodeGeoCode() : null; 223 224 var cityGeoCodeDescription = cityGeoCode == null ? null : geoControl.getBestGeoCodeDescription(cityGeoCode, getPreferredLanguage()); 225 var stateGeoCodeDescription = stateGeoCode == null ? null : geoControl.getBestGeoCodeDescription(stateGeoCode, getPreferredLanguage()); 226 var postalCodeGeoCodeDescription = postalCodeGeoCode == null ? null : geoControl.getBestGeoCodeDescription(postalCodeGeoCode, getPreferredLanguage()); 227 228 edit.setAllowSolicitation(contactMechanismDetail.getAllowSolicitation().toString()); 229 edit.setPersonalTitleId(personalTitle == null ? null : personalTitle.getPrimaryKey().getEntityId().toString()); 230 edit.setFirstName(contactPostalAddress.getFirstName()); 231 edit.setMiddleName(contactPostalAddress.getMiddleName()); 232 edit.setLastName(contactPostalAddress.getLastName()); 233 edit.setNameSuffixId(nameSuffix == null ? null : nameSuffix.getPrimaryKey().getEntityId().toString()); 234 edit.setCompanyName(contactPostalAddress.getCompanyName()); 235 edit.setAttention(contactPostalAddress.getAttention()); 236 edit.setAddress1(contactPostalAddress.getAddress1()); 237 edit.setAddress2(contactPostalAddress.getAddress2()); 238 edit.setAddress3(contactPostalAddress.getAddress3()); 239 edit.setCity(city == null ? (cityGeoCode == null ? null : (cityGeoCodeDescription == null ? geoControl.getAliasForCity(cityGeoCode) : cityGeoCodeDescription)) : city); 240 edit.setState(state == null ? (stateGeoCode == null ? null : (stateGeoCodeDescription == null ? geoControl.getAliasForState(stateGeoCode) : stateGeoCodeDescription)) : state); 241 edit.setPostalCode(postalCode == null ? (postalCodeGeoCode == null ? null : (postalCodeGeoCodeDescription == null ? geoControl.getAliasForPostalCode(postalCodeGeoCode) : postalCodeGeoCodeDescription)) : postalCode); 242 edit.setCountryName(geoControl.getAliasForCountry(contactPostalAddress.getCountryGeoCode())); 243 edit.setIsCommercial(contactPostalAddress.getIsCommercial().toString()); 244 edit.setDescription(partyContactMechanismDetail.getDescription()); 245 } 246 247 GeoCode countryGeoCode; 248 String state; 249 GeoCode stateGeoCode; 250 String city; 251 GeoCode cityGeoCode; 252 String postalCode; 253 GeoCode postalCodeGeoCode; 254 GeoCode countyGeoCode; 255 256 @Override 257 public void canUpdate(PartyContactMechanism partyContactMechanism) { 258 var geoControl = Session.getModelController(GeoControl.class); 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 contactControl = Session.getModelController(ContactControl.class); 348 var partyControl = Session.getModelController(PartyControl.class); 349 var soundex = new Soundex(); 350 var updatedBy = getPartyPK(); 351 var contactMechanism = partyContactMechanism.getLastDetail().getContactMechanism(); 352 var contactMechanismDetailValue = contactControl.getContactMechanismDetailValue(contactMechanism.getLastDetail()); 353 var contactPostalAddressValue = contactControl.getContactPostalAddressValueForUpdate(contactMechanism); 354 var partyContactMechanismDetailValue = contactControl.getPartyContactMechanismDetailValueForUpdate(partyContactMechanism); 355 356 var personalTitleId = edit.getPersonalTitleId(); 357 var personalTitle = personalTitleId == null ? null : partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY); 358 var firstName = edit.getFirstName(); 359 var middleName = edit.getMiddleName(); 360 var lastName = edit.getLastName(); 361 var nameSuffixId = edit.getNameSuffixId(); 362 var nameSuffix = nameSuffixId == null ? null : partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY); 363 364 String firstNameSdx; 365 try { 366 firstNameSdx = firstName == null ? null : soundex.encode(firstName); 367 } catch(IllegalArgumentException iae) { 368 firstNameSdx = null; 369 } 370 371 String middleNameSdx; 372 try { 373 middleNameSdx = middleName == null ? null : soundex.encode(middleName); 374 } catch(IllegalArgumentException iae) { 375 middleNameSdx = null; 376 } 377 378 String lastNameSdx; 379 try { 380 lastNameSdx = lastName == null ? null : soundex.encode(lastName); 381 } catch(IllegalArgumentException iae) { 382 lastNameSdx = null; 383 } 384 385 contactMechanismDetailValue.setAllowSolicitation(Boolean.valueOf(edit.getAllowSolicitation())); 386 contactPostalAddressValue.setCountryGeoCodePK(countryGeoCode.getPrimaryKey()); 387 contactPostalAddressValue.setPersonalTitlePK(personalTitle == null ? null : personalTitle.getPrimaryKey()); 388 contactPostalAddressValue.setFirstName(firstName); 389 contactPostalAddressValue.setFirstNameSdx(firstNameSdx); 390 contactPostalAddressValue.setMiddleName(middleName); 391 contactPostalAddressValue.setMiddleNameSdx(middleNameSdx); 392 contactPostalAddressValue.setLastName(lastName); 393 contactPostalAddressValue.setLastNameSdx(lastNameSdx); 394 contactPostalAddressValue.setNameSuffixPK(nameSuffix == null ? null : nameSuffix.getPrimaryKey()); 395 contactPostalAddressValue.setCompanyName(edit.getCompanyName()); 396 contactPostalAddressValue.setAttention(edit.getAttention()); 397 contactPostalAddressValue.setAddress1(edit.getAddress1()); 398 contactPostalAddressValue.setAddress2(edit.getAddress2()); 399 contactPostalAddressValue.setAddress3(edit.getAddress3()); 400 contactPostalAddressValue.setCity(city); 401 contactPostalAddressValue.setCityGeoCodePK(cityGeoCode == null ? null : cityGeoCode.getPrimaryKey()); 402 contactPostalAddressValue.setCountyGeoCodePK(countyGeoCode == null ? null : countyGeoCode.getPrimaryKey()); 403 contactPostalAddressValue.setState(state); 404 contactPostalAddressValue.setStateGeoCodePK(stateGeoCode == null ? null : stateGeoCode.getPrimaryKey()); 405 contactPostalAddressValue.setPostalCode(postalCode); 406 contactPostalAddressValue.setPostalCodeGeoCodePK(postalCodeGeoCode == null ? null : postalCodeGeoCode.getPrimaryKey()); 407 contactPostalAddressValue.setCountryGeoCodePK(countryGeoCode == null ? null : countryGeoCode.getPrimaryKey()); 408 contactPostalAddressValue.setIsCommercial(Boolean.valueOf(edit.getIsCommercial())); 409 partyContactMechanismDetailValue.setDescription(edit.getDescription()); 410 411 contactControl.updateContactMechanismFromValue(contactMechanismDetailValue, updatedBy); 412 contactControl.updateContactPostalAddressFromValue(contactPostalAddressValue, updatedBy); 413 contactControl.updatePartyContactMechanismFromValue(partyContactMechanismDetailValue, updatedBy); 414 } 415 416} 417