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