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 com.echothree.util.server.persistence.Session;
046import java.util.List;
047import java.util.Locale;
048import org.apache.commons.codec.language.Soundex;
049import javax.enterprise.context.Dependent;
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    /** Creates a new instance of EditContactPostalAddressCommand */
120    public EditContactPostalAddressCommand() {
121        super(COMMAND_SECURITY_DEFINITION, SPEC_FIELD_DEFINITIONS, null);
122    }
123
124    @Override
125    protected List<FieldDefinition> getEditFieldDefinitions() {
126        var partyTypeName = getPartyTypeName();
127
128        return partyTypeName.equals(PartyTypes.CUSTOMER.name()) ? editCustomerFieldDefinitions : editOtherFieldDefinitions;
129    }
130
131    @Override
132    protected SecurityResult security() {
133        var securityResult = super.security();
134
135        return securityResult != null ? securityResult : selfOnly(spec);
136    }
137
138    @Override
139    public EditContactPostalAddressResult getResult() {
140        return ContactResultFactory.getEditContactPostalAddressResult();
141    }
142
143    @Override
144    public ContactPostalAddressEdit getEdit() {
145        return ContactEditFactory.getContactPostalAddressEdit();
146    }
147
148    @Override
149    public PartyContactMechanism getEntity(EditContactPostalAddressResult result) {
150        var partyControl = Session.getModelController(PartyControl.class);
151        PartyContactMechanism partyContactMechanism = null;
152        var partyName = spec.getPartyName();
153        var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName);
154
155        if(party != null) {
156            var contactControl = Session.getModelController(ContactControl.class);
157            var contactMechanismName = spec.getContactMechanismName();
158            var contactMechanism = contactControl.getContactMechanismByName(contactMechanismName);
159
160            if(contactMechanism != null) {
161                if(editMode.equals(EditMode.LOCK) || editMode.equals(EditMode.ABANDON)) {
162                    partyContactMechanism = contactControl.getPartyContactMechanism(party, contactMechanism);
163                } else { // EditMode.UPDATE
164                    partyContactMechanism = contactControl.getPartyContactMechanismForUpdate(party, contactMechanism);
165                }
166
167                if(partyContactMechanism != null) {
168                    var lastContactMechanismDetail = contactMechanism.getLastDetail();
169                    var contactMechanismTypeName = lastContactMechanismDetail.getContactMechanismType().getContactMechanismTypeName();
170
171                    result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(), contactMechanism));
172
173                    if(!ContactMechanismTypes.POSTAL_ADDRESS.name().equals(contactMechanismTypeName)) {
174                        addExecutionError(ExecutionErrors.InvalidContactMechanismType.name(), contactMechanismTypeName);
175                    }
176                } else {
177                    addExecutionError(ExecutionErrors.UnknownPartyContactMechanism.name(), partyName, contactMechanismName);
178                }
179            } else {
180                addExecutionError(ExecutionErrors.UnknownContactMechanismName.name(), contactMechanismName);
181            }
182        } else {
183            addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName);
184        }
185
186        return partyContactMechanism;
187    }
188
189    @Override
190    public ContactMechanism getLockEntity(PartyContactMechanism partyContactMechanism) {
191        return partyContactMechanism.getLastDetail().getContactMechanism();
192    }
193
194    @Override
195    public void fillInResult(EditContactPostalAddressResult result, PartyContactMechanism partyContactMechanism) {
196        var contactControl = Session.getModelController(ContactControl.class);
197
198        result.setContactMechanism(contactControl.getContactMechanismTransfer(getUserVisit(),
199                partyContactMechanism.getLastDetail().getContactMechanism()));
200    }
201
202    @Override
203    public void doLock(ContactPostalAddressEdit edit, PartyContactMechanism partyContactMechanism) {
204        var contactControl = Session.getModelController(ContactControl.class);
205        var geoControl = Session.getModelController(GeoControl.class);
206        var contactMechanism = partyContactMechanism.getLastDetail().getContactMechanism();
207        var contactMechanismDetail = contactMechanism.getLastDetail();
208        var contactPostalAddress = contactControl.getContactPostalAddress(contactMechanism);
209        var partyContactMechanismDetail = partyContactMechanism.getLastDetail();
210
211        var personalTitle = contactPostalAddress.getPersonalTitle();
212        var nameSuffix = contactPostalAddress.getNameSuffix();
213
214        var city = contactPostalAddress.getCity();
215        var state = contactPostalAddress.getState();
216        var postalCode = contactPostalAddress.getPostalCode();
217
218        var cityGeoCode = city == null ? contactPostalAddress.getCityGeoCode() : null;
219        var stateGeoCode = state == null ? contactPostalAddress.getStateGeoCode() : null;
220        var postalCodeGeoCode = postalCode == null ? contactPostalAddress.getPostalCodeGeoCode() : null;
221
222        var cityGeoCodeDescription = cityGeoCode == null ? null : geoControl.getBestGeoCodeDescription(cityGeoCode, getPreferredLanguage());
223        var stateGeoCodeDescription = stateGeoCode == null ? null : geoControl.getBestGeoCodeDescription(stateGeoCode, getPreferredLanguage());
224        var postalCodeGeoCodeDescription = postalCodeGeoCode == null ? null : geoControl.getBestGeoCodeDescription(postalCodeGeoCode, getPreferredLanguage());
225
226        edit.setAllowSolicitation(contactMechanismDetail.getAllowSolicitation().toString());
227        edit.setPersonalTitleId(personalTitle == null ? null : personalTitle.getPrimaryKey().getEntityId().toString());
228        edit.setFirstName(contactPostalAddress.getFirstName());
229        edit.setMiddleName(contactPostalAddress.getMiddleName());
230        edit.setLastName(contactPostalAddress.getLastName());
231        edit.setNameSuffixId(nameSuffix == null ? null : nameSuffix.getPrimaryKey().getEntityId().toString());
232        edit.setCompanyName(contactPostalAddress.getCompanyName());
233        edit.setAttention(contactPostalAddress.getAttention());
234        edit.setAddress1(contactPostalAddress.getAddress1());
235        edit.setAddress2(contactPostalAddress.getAddress2());
236        edit.setAddress3(contactPostalAddress.getAddress3());
237        edit.setCity(city == null ? (cityGeoCode == null ? null : (cityGeoCodeDescription == null ? geoControl.getAliasForCity(cityGeoCode) : cityGeoCodeDescription)) : city);
238        edit.setState(state == null ? (stateGeoCode == null ? null : (stateGeoCodeDescription == null ? geoControl.getAliasForState(stateGeoCode) : stateGeoCodeDescription)) : state);
239        edit.setPostalCode(postalCode == null ? (postalCodeGeoCode == null ? null : (postalCodeGeoCodeDescription == null ? geoControl.getAliasForPostalCode(postalCodeGeoCode) : postalCodeGeoCodeDescription)) : postalCode);
240        edit.setCountryName(geoControl.getAliasForCountry(contactPostalAddress.getCountryGeoCode()));
241        edit.setIsCommercial(contactPostalAddress.getIsCommercial().toString());
242        edit.setDescription(partyContactMechanismDetail.getDescription());
243    }
244
245    GeoCode countryGeoCode;
246    String state;
247    GeoCode stateGeoCode;
248    String city;
249    GeoCode cityGeoCode;
250    String postalCode;
251    GeoCode postalCodeGeoCode;
252    GeoCode countyGeoCode;
253
254    @Override
255    public void canUpdate(PartyContactMechanism partyContactMechanism) {
256        var geoControl = Session.getModelController(GeoControl.class);
257        var countryName = edit.getCountryName();
258        var countryAlias = StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault());
259
260        countryGeoCode = geoControl.getCountryByAlias(countryAlias);
261
262        if(countryGeoCode != null) {
263            var geoCodeCountry = geoControl.getGeoCodeCountry(countryGeoCode);
264
265            postalCode = edit.getPostalCode();
266
267            if(postalCode != null) {
268                postalCode = postalCode.toUpperCase(Locale.getDefault());
269            }
270
271            if(!geoCodeCountry.getPostalCodeRequired() || postalCode != null) {
272                var postalCodePattern = geoCodeCountry.getPostalCodePattern();
273                var postalCodeLength = geoCodeCountry.getPostalCodeLength();
274
275                if(postalCodeLength == null) {
276                    postalCodeLength = Integer.MAX_VALUE;
277                }
278
279                if(postalCode == null || ((postalCodePattern == null || postalCode.matches(postalCodePattern)) && (postalCode.length() <= postalCodeLength))) {
280                    var postalCodeAlias = postalCode == null ? null : StringUtils.getInstance().cleanStringToLettersOrDigits(StringUtils.getInstance().cleanStringToName(postalCode));
281
282                    if(postalCodeAlias != null) {
283                        var postalCodeAliasLength = postalCodeAlias.length();
284                        var postalCodeGeoCodeLength = geoCodeCountry.getPostalCodeGeoCodeLength();
285
286                        if(postalCodeGeoCodeLength == null || postalCodeAliasLength >= postalCodeGeoCodeLength) {
287                            if(postalCodeGeoCodeLength != null && postalCodeAliasLength > postalCodeGeoCodeLength) {
288                                postalCodeAlias = postalCodeAlias.substring(0, postalCodeGeoCodeLength);
289                            }
290
291                            postalCodeGeoCode = geoControl.getPostalCodeByAlias(countryGeoCode, postalCodeAlias);
292                        }
293                    }
294
295                    if(!geoCodeCountry.getPostalCodeGeoCodeRequired() || postalCodeGeoCode != null) {
296                        state = edit.getState();
297
298                        if(!geoCodeCountry.getStateRequired() || state != null) {
299                            var stateAlias = state == null ? null : StringUtils.getInstance().cleanStringToName(state).toUpperCase(Locale.getDefault());
300
301                            if(stateAlias != null) {
302                                stateGeoCode = geoControl.getStateByAlias(countryGeoCode, stateAlias);
303                            }
304
305                            if(!geoCodeCountry.getStateGeoCodeRequired() || stateGeoCode != null) {
306                                city = edit.getCity();
307
308                                if(!geoCodeCountry.getCityRequired() || city != null) {
309                                    var cityAlias = city == null ? null : StringUtils.getInstance().cleanStringToName(city).toUpperCase(Locale.getDefault());
310
311                                    if(stateGeoCode != null && cityAlias != null) {
312                                        cityGeoCode = geoControl.getCityByAlias(stateGeoCode, cityAlias);
313                                    }
314
315                                    if(!geoCodeCountry.getCityGeoCodeRequired() || cityGeoCode != null) {
316                                        // TODO: countyGeoCode
317                                    } else {
318                                        addExecutionError(ExecutionErrors.UnknownCity.name(), city, cityAlias);
319                                    }
320                                } else {
321                                    addExecutionError(ExecutionErrors.MissingCity.name());
322                                }
323                            } else {
324                                addExecutionError(ExecutionErrors.UnknownState.name(), state, stateAlias);
325                            }
326                        } else {
327                            addExecutionError(ExecutionErrors.MissingState.name());
328                        }
329                    } else {
330                        addExecutionError(ExecutionErrors.UnknownPostalCode.name(), postalCode);
331                    }
332                } else {
333                    addExecutionError(ExecutionErrors.InvalidPostalCode.name(), postalCode);
334                }
335            } else {
336                addExecutionError(ExecutionErrors.MissingPostalCode.name());
337            }
338        } else {
339            addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName, countryAlias);
340        }
341    }
342
343    @Override
344    public void doUpdate(PartyContactMechanism partyContactMechanism) {
345        var contactControl = Session.getModelController(ContactControl.class);
346        var partyControl = Session.getModelController(PartyControl.class);
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