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