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.form.CreateContactPostalAddressForm;
020import com.echothree.control.user.contact.common.result.ContactResultFactory;
021import com.echothree.model.control.contact.common.ContactMechanismTypes;
022import com.echothree.model.control.contact.common.workflow.PostalAddressStatusConstants;
023import com.echothree.model.control.contact.server.control.ContactControl;
024import com.echothree.model.control.core.server.control.EntityInstanceControl;
025import com.echothree.model.control.geo.server.control.GeoControl;
026import com.echothree.model.control.party.common.PartyTypes;
027import com.echothree.model.control.party.server.control.PartyControl;
028import com.echothree.model.control.security.common.SecurityRoleGroups;
029import com.echothree.model.control.security.common.SecurityRoles;
030import com.echothree.model.control.sequence.common.SequenceTypes;
031import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic;
032import com.echothree.model.control.workflow.server.control.WorkflowControl;
033import com.echothree.model.data.geo.server.entity.GeoCode;
034import com.echothree.model.data.user.common.pk.UserVisitPK;
035import com.echothree.util.common.command.BaseResult;
036import com.echothree.util.common.command.SecurityResult;
037import com.echothree.util.common.form.ValidationResult;
038import com.echothree.util.common.message.ExecutionErrors;
039import com.echothree.util.common.persistence.BasePK;
040import com.echothree.util.common.string.StringUtils;
041import com.echothree.util.common.validation.FieldDefinition;
042import com.echothree.util.common.validation.FieldType;
043import com.echothree.util.server.control.BaseSimpleCommand;
044import com.echothree.util.server.control.CommandSecurityDefinition;
045import com.echothree.util.server.control.PartyTypeDefinition;
046import com.echothree.util.server.control.SecurityRoleDefinition;
047import com.echothree.util.server.persistence.EntityPermission;
048import com.echothree.util.server.persistence.Session;
049import com.echothree.util.server.validation.Validator;
050import java.util.ArrayList;
051import java.util.List;
052import java.util.Locale;
053import org.apache.commons.codec.language.Soundex;
054import javax.enterprise.context.Dependent;
055
056@Dependent
057public class CreateContactPostalAddressCommand
058        extends BaseSimpleCommand<CreateContactPostalAddressForm> {
059    
060    private final static CommandSecurityDefinition COMMAND_SECURITY_DEFINITION;
061    private final static List<FieldDefinition> customerFormFieldDefinitions;
062    private final static List<FieldDefinition> otherFormFieldDefinitions;
063    
064    static {
065        COMMAND_SECURITY_DEFINITION = new CommandSecurityDefinition(List.of(
066                new PartyTypeDefinition(PartyTypes.UTILITY.name(), null),
067                new PartyTypeDefinition(PartyTypes.CUSTOMER.name(), null),
068                new PartyTypeDefinition(PartyTypes.VENDOR.name(), null),
069                new PartyTypeDefinition(PartyTypes.EMPLOYEE.name(), List.of(
070                        new SecurityRoleDefinition(SecurityRoleGroups.ContactMechanism.name(), SecurityRoles.Create.name())
071                        ))
072                ));
073
074        // customerFormFieldDefinitions differs from otherFormFieldDefinitions in that when the PartyType
075        // executing this command = CUSTOMER, FirstName and LastName are required fields. For all other
076        // PartyTypes, that requirement is relaxed.
077        customerFormFieldDefinitions = List.of(
078                new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null),
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, true, 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        otherFormFieldDefinitions = List.of(
099                new FieldDefinition("PartyName", FieldType.ENTITY_NAME, false, null, null),
100                new FieldDefinition("PersonalTitleId", FieldType.ID, false, null, null),
101                new FieldDefinition("FirstName", FieldType.STRING, false, 1L, 20L),
102                new FieldDefinition("MiddleName", FieldType.STRING, false, 1L, 20L),
103                new FieldDefinition("LastName", FieldType.STRING, false, 1L, 20L),
104                new FieldDefinition("NameSuffixId", FieldType.ID, false, null, null),
105                new FieldDefinition("CompanyName", FieldType.STRING, false, 1L, 60L),
106                new FieldDefinition("Attention", FieldType.STRING, false, 1L, 60L),
107                new FieldDefinition("Address1", FieldType.STRING, true, 1L, 40L),
108                new FieldDefinition("Address2", FieldType.STRING, false, 1L, 40L),
109                new FieldDefinition("Address3", FieldType.STRING, false, 1L, 40L),
110                new FieldDefinition("City", FieldType.STRING, false, 1L, 30L),
111                new FieldDefinition("State", FieldType.STRING, false, 1L, 30L),
112                new FieldDefinition("PostalCode", FieldType.STRING, false, 1L, 15L),
113                new FieldDefinition("CountryName", FieldType.ENTITY_NAME, true, null, null),
114                new FieldDefinition("IsCommercial", FieldType.BOOLEAN, true, null, null),
115                new FieldDefinition("AllowSolicitation", FieldType.BOOLEAN, true, null, null),
116                new FieldDefinition("Description", FieldType.STRING, false, 1L, 132L)
117                );
118    }
119    
120    /** Creates a new instance of CreateContactPostalAddressCommand */
121    public CreateContactPostalAddressCommand() {
122        super(COMMAND_SECURITY_DEFINITION, null, false);
123    }
124
125    @Override
126    protected SecurityResult security() {
127        var securityResult = super.security();
128
129        return securityResult != null ? securityResult : selfOnly(form);
130    }
131
132    @Override
133    protected ValidationResult validate() {
134        var partyTypeName = getPartyTypeName();
135        var FORM_FIELD_DEFINITIONS = partyTypeName.equals(PartyTypes.CUSTOMER.name())? customerFormFieldDefinitions: otherFormFieldDefinitions;
136        var validator = new Validator(this);
137        var validationResult = validator.validate(form, FORM_FIELD_DEFINITIONS);
138        
139        return validationResult;
140    }
141    
142    @Override
143    protected BaseResult execute() {
144        var result = ContactResultFactory.getCreateContactPostalAddressResult();
145        var partyControl = Session.getModelController(PartyControl.class);
146        var partyName = form.getPartyName();
147        var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName);
148        
149        if(party != null) {
150            var geoControl = Session.getModelController(GeoControl.class);
151            var countryName = form.getCountryName();
152            var countryAlias = StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault());
153            var countryGeoCode = geoControl.getCountryByAlias(countryAlias);
154            
155            if(countryGeoCode != null) {
156                var geoCodeCountry = geoControl.getGeoCodeCountry(countryGeoCode);
157                var postalCode = form.getPostalCode();
158
159                if(postalCode != null) {
160                    postalCode = postalCode.toUpperCase(Locale.getDefault());
161                }
162
163                if(!geoCodeCountry.getPostalCodeRequired() || postalCode != null) {
164                    var postalCodePattern = geoCodeCountry.getPostalCodePattern();
165                    var postalCodeLength = geoCodeCountry.getPostalCodeLength();
166                    
167                    if(postalCodeLength == null) {
168                        postalCodeLength = Integer.MAX_VALUE;
169                    }
170                    
171                    if(postalCode == null || ((postalCodePattern == null || postalCode.matches(postalCodePattern)) && (postalCode.length() <= postalCodeLength))) {
172                        GeoCode postalCodeGeoCode = null;
173                        var postalCodeAlias = postalCode == null? null: StringUtils.getInstance().cleanStringToLettersOrDigits(StringUtils.getInstance().cleanStringToName(postalCode));
174                        
175                        if(postalCodeAlias != null) {
176                            var postalCodeAliasLength = postalCodeAlias.length();
177                            var postalCodeGeoCodeLength = geoCodeCountry.getPostalCodeGeoCodeLength();
178                            
179                            if(postalCodeGeoCodeLength == null || postalCodeAliasLength >= postalCodeGeoCodeLength) {
180                                if(postalCodeGeoCodeLength != null && postalCodeAliasLength > postalCodeGeoCodeLength) {
181                                    postalCodeAlias = postalCodeAlias.substring(0, postalCodeGeoCodeLength);
182                                }
183
184                                postalCodeGeoCode = geoControl.getPostalCodeByAlias(countryGeoCode, postalCodeAlias);
185                            }
186                        }
187                        
188                        if(!geoCodeCountry.getPostalCodeGeoCodeRequired() || postalCodeGeoCode != null) {
189                            var state = form.getState();
190                            
191                            if(!geoCodeCountry.getStateRequired() || state != null) {
192                                GeoCode stateGeoCode = null;
193                                var stateAlias = state == null? null: StringUtils.getInstance().cleanStringToName(state).toUpperCase(Locale.getDefault());
194                                
195                                if(stateAlias != null) {
196                                    stateGeoCode = geoControl.getStateByAlias(countryGeoCode, stateAlias);
197                                }
198                                
199                                if(!geoCodeCountry.getStateGeoCodeRequired() || stateGeoCode != null) {
200                                    var city = form.getCity();
201                                    
202                                    if(!geoCodeCountry.getCityRequired() || city != null) {
203                                        GeoCode cityGeoCode = null;
204                                        var cityAlias = city == null? null: StringUtils.getInstance().cleanStringToName(city).toUpperCase(Locale.getDefault());
205                                        
206                                        if(stateGeoCode != null && cityAlias != null) {
207                                            cityGeoCode = geoControl.getCityByAlias(stateGeoCode, cityAlias);
208                                        }
209
210                                        if(!geoCodeCountry.getCityGeoCodeRequired() || cityGeoCode != null) {
211                                            GeoCode countyGeoCode = null;
212                                            
213                                            var contactControl = Session.getModelController(ContactControl.class);
214                                            var entityInstanceControl = Session.getModelController(EntityInstanceControl.class);
215                                            var workflowControl = Session.getModelController(WorkflowControl.class);
216                                            var soundex = new Soundex();
217                                            BasePK createdBy = getPartyPK();
218                                            var personalTitleId = form.getPersonalTitleId();
219                                            var personalTitle = personalTitleId == null? null: partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY);
220                                            var firstName = form.getFirstName();
221                                            var middleName = form.getMiddleName();
222                                            var lastName = form.getLastName();
223                                            var nameSuffixId = form.getNameSuffixId();
224                                            var nameSuffix = nameSuffixId == null? null: partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY);
225                                            var companyName = form.getCompanyName();
226                                            var attention = form.getAttention();
227                                            var address1 = form.getAddress1();
228                                            var address2 = form.getAddress2();
229                                            var address3 = form.getAddress3();
230                                            var allowSolicitation = Boolean.valueOf(form.getAllowSolicitation());
231                                            var isCommercial = Boolean.valueOf(form.getIsCommercial());
232                                            var description = form.getDescription();
233                                            var contactMechanismName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(null, SequenceTypes.CONTACT_MECHANISM.name());
234                                            
235                                            String firstNameSdx;
236                                            try {
237                                                firstNameSdx = firstName == null? null: soundex.encode(firstName);
238                                            } catch (IllegalArgumentException iae) {
239                                                firstNameSdx = null;
240                                            }
241                                            
242                                            String middleNameSdx;
243                                            try {
244                                                middleNameSdx = middleName == null? null: soundex.encode(middleName);
245                                            } catch (IllegalArgumentException iae) {
246                                                middleNameSdx = null;
247                                            }
248                                            
249                                            String lastNameSdx;
250                                            try {
251                                                lastNameSdx = lastName == null? null: soundex.encode(lastName);
252                                            } catch (IllegalArgumentException iae) {
253                                                lastNameSdx = null;
254                                            }
255
256                                            var contactMechanismType = contactControl.getContactMechanismTypeByName(ContactMechanismTypes.POSTAL_ADDRESS.name());
257                                            var contactMechanism = contactControl.createContactMechanism(contactMechanismName, contactMechanismType,
258                                                    allowSolicitation, createdBy);
259                                            
260                                            contactControl.createContactPostalAddress(contactMechanism, personalTitle, firstName, firstNameSdx, middleName,
261                                                    middleNameSdx, lastName, lastNameSdx, nameSuffix, companyName, attention, address1, address2, address3, city,
262                                                    cityGeoCode, countyGeoCode, state, stateGeoCode, postalCode, postalCodeGeoCode, countryGeoCode, isCommercial,
263                                                    createdBy);
264                                            
265                                            contactControl.createPartyContactMechanism(party, contactMechanism, description,
266                                                    false, 1, createdBy);
267
268                                            var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(contactMechanism.getPrimaryKey());
269                                            workflowControl.addEntityToWorkflowUsingNames(null, PostalAddressStatusConstants.Workflow_POSTAL_ADDRESS_STATUS,
270                                                    PostalAddressStatusConstants.WorkflowEntrance_NEW_POSTAL_ADDRESS, entityInstance, null, null, createdBy);
271                                            
272                                            result.setContactMechanismName(contactMechanism.getLastDetail().getContactMechanismName());
273                                            result.setEntityRef(contactMechanism.getPrimaryKey().getEntityRef());
274                                        } else {
275                                            addExecutionError(ExecutionErrors.UnknownCity.name(), city, cityAlias);
276                                        }
277                                    } else {
278                                        addExecutionError(ExecutionErrors.MissingCity.name());
279                                    }
280                                } else {
281                                    addExecutionError(ExecutionErrors.UnknownState.name(), state, stateAlias);
282                                }
283                            } else {
284                                addExecutionError(ExecutionErrors.MissingState.name());
285                            }
286                        } else {
287                            addExecutionError(ExecutionErrors.UnknownPostalCode.name(), postalCode);
288                        }
289                    } else {
290                        addExecutionError(ExecutionErrors.InvalidPostalCode.name(), postalCode);
291                    }
292                } else {
293                    addExecutionError(ExecutionErrors.MissingPostalCode.name());
294                }
295            } else {
296                addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName, countryAlias);
297            }
298        } else {
299            addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName);
300        }
301        
302        return result;
303    }
304    
305}