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