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