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.validation.Validator;
049import java.util.ArrayList;
050import java.util.List;
051import java.util.Locale;
052import org.apache.commons.codec.language.Soundex;
053import javax.enterprise.context.Dependent;
054import javax.inject.Inject;
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    @Inject
121    ContactControl contactControl;
122
123    @Inject
124    EntityInstanceControl entityInstanceControl;
125
126    @Inject
127    GeoControl geoControl;
128
129    @Inject
130    PartyControl partyControl;
131
132    @Inject
133    WorkflowControl workflowControl;
134
135    @Inject
136    SequenceGeneratorLogic sequenceGeneratorLogic;
137
138    
139    /** Creates a new instance of CreateContactPostalAddressCommand */
140    public CreateContactPostalAddressCommand() {
141        super(COMMAND_SECURITY_DEFINITION, null, false);
142    }
143
144    @Override
145    protected SecurityResult security() {
146        var securityResult = super.security();
147
148        return securityResult != null ? securityResult : selfOnly(form);
149    }
150
151    @Override
152    protected ValidationResult validate() {
153        var partyTypeName = getPartyTypeName();
154        var FORM_FIELD_DEFINITIONS = partyTypeName.equals(PartyTypes.CUSTOMER.name())? customerFormFieldDefinitions: otherFormFieldDefinitions;
155        var validator = new Validator(this);
156        var validationResult = validator.validate(form, FORM_FIELD_DEFINITIONS);
157        
158        return validationResult;
159    }
160    
161    @Override
162    protected BaseResult execute() {
163        var result = ContactResultFactory.getCreateContactPostalAddressResult();
164        var partyName = form.getPartyName();
165        var party = partyName == null ? getParty() : partyControl.getPartyByName(partyName);
166        
167        if(party != null) {
168            var countryName = form.getCountryName();
169            var countryAlias = StringUtils.getInstance().cleanStringToName(countryName).toUpperCase(Locale.getDefault());
170            var countryGeoCode = geoControl.getCountryByAlias(countryAlias);
171            
172            if(countryGeoCode != null) {
173                var geoCodeCountry = geoControl.getGeoCodeCountry(countryGeoCode);
174                var postalCode = form.getPostalCode();
175
176                if(postalCode != null) {
177                    postalCode = postalCode.toUpperCase(Locale.getDefault());
178                }
179
180                if(!geoCodeCountry.getPostalCodeRequired() || postalCode != null) {
181                    var postalCodePattern = geoCodeCountry.getPostalCodePattern();
182                    var postalCodeLength = geoCodeCountry.getPostalCodeLength();
183                    
184                    if(postalCodeLength == null) {
185                        postalCodeLength = Integer.MAX_VALUE;
186                    }
187                    
188                    if(postalCode == null || ((postalCodePattern == null || postalCode.matches(postalCodePattern)) && (postalCode.length() <= postalCodeLength))) {
189                        GeoCode postalCodeGeoCode = null;
190                        var postalCodeAlias = postalCode == null? null: StringUtils.getInstance().cleanStringToLettersOrDigits(StringUtils.getInstance().cleanStringToName(postalCode));
191                        
192                        if(postalCodeAlias != null) {
193                            var postalCodeAliasLength = postalCodeAlias.length();
194                            var postalCodeGeoCodeLength = geoCodeCountry.getPostalCodeGeoCodeLength();
195                            
196                            if(postalCodeGeoCodeLength == null || postalCodeAliasLength >= postalCodeGeoCodeLength) {
197                                if(postalCodeGeoCodeLength != null && postalCodeAliasLength > postalCodeGeoCodeLength) {
198                                    postalCodeAlias = postalCodeAlias.substring(0, postalCodeGeoCodeLength);
199                                }
200
201                                postalCodeGeoCode = geoControl.getPostalCodeByAlias(countryGeoCode, postalCodeAlias);
202                            }
203                        }
204                        
205                        if(!geoCodeCountry.getPostalCodeGeoCodeRequired() || postalCodeGeoCode != null) {
206                            var state = form.getState();
207                            
208                            if(!geoCodeCountry.getStateRequired() || state != null) {
209                                GeoCode stateGeoCode = null;
210                                var stateAlias = state == null? null: StringUtils.getInstance().cleanStringToName(state).toUpperCase(Locale.getDefault());
211                                
212                                if(stateAlias != null) {
213                                    stateGeoCode = geoControl.getStateByAlias(countryGeoCode, stateAlias);
214                                }
215                                
216                                if(!geoCodeCountry.getStateGeoCodeRequired() || stateGeoCode != null) {
217                                    var city = form.getCity();
218                                    
219                                    if(!geoCodeCountry.getCityRequired() || city != null) {
220                                        GeoCode cityGeoCode = null;
221                                        var cityAlias = city == null? null: StringUtils.getInstance().cleanStringToName(city).toUpperCase(Locale.getDefault());
222                                        
223                                        if(stateGeoCode != null && cityAlias != null) {
224                                            cityGeoCode = geoControl.getCityByAlias(stateGeoCode, cityAlias);
225                                        }
226
227                                        if(!geoCodeCountry.getCityGeoCodeRequired() || cityGeoCode != null) {
228                                            GeoCode countyGeoCode = null;
229                                            var soundex = new Soundex();
230                                            BasePK createdBy = getPartyPK();
231                                            var personalTitleId = form.getPersonalTitleId();
232                                            var personalTitle = personalTitleId == null? null: partyControl.convertPersonalTitleIdToEntity(personalTitleId, EntityPermission.READ_ONLY);
233                                            var firstName = form.getFirstName();
234                                            var middleName = form.getMiddleName();
235                                            var lastName = form.getLastName();
236                                            var nameSuffixId = form.getNameSuffixId();
237                                            var nameSuffix = nameSuffixId == null? null: partyControl.convertNameSuffixIdToEntity(nameSuffixId, EntityPermission.READ_ONLY);
238                                            var companyName = form.getCompanyName();
239                                            var attention = form.getAttention();
240                                            var address1 = form.getAddress1();
241                                            var address2 = form.getAddress2();
242                                            var address3 = form.getAddress3();
243                                            var allowSolicitation = Boolean.valueOf(form.getAllowSolicitation());
244                                            var isCommercial = Boolean.valueOf(form.getIsCommercial());
245                                            var description = form.getDescription();
246                                            var contactMechanismName = sequenceGeneratorLogic.getNextSequenceValue(null, SequenceTypes.CONTACT_MECHANISM.name());
247                                            
248                                            String firstNameSdx;
249                                            try {
250                                                firstNameSdx = firstName == null? null: soundex.encode(firstName);
251                                            } catch (IllegalArgumentException iae) {
252                                                firstNameSdx = null;
253                                            }
254                                            
255                                            String middleNameSdx;
256                                            try {
257                                                middleNameSdx = middleName == null? null: soundex.encode(middleName);
258                                            } catch (IllegalArgumentException iae) {
259                                                middleNameSdx = null;
260                                            }
261                                            
262                                            String lastNameSdx;
263                                            try {
264                                                lastNameSdx = lastName == null? null: soundex.encode(lastName);
265                                            } catch (IllegalArgumentException iae) {
266                                                lastNameSdx = null;
267                                            }
268
269                                            var contactMechanismType = contactControl.getContactMechanismTypeByName(ContactMechanismTypes.POSTAL_ADDRESS.name());
270                                            var contactMechanism = contactControl.createContactMechanism(contactMechanismName, contactMechanismType,
271                                                    allowSolicitation, createdBy);
272                                            
273                                            contactControl.createContactPostalAddress(contactMechanism, personalTitle, firstName, firstNameSdx, middleName,
274                                                    middleNameSdx, lastName, lastNameSdx, nameSuffix, companyName, attention, address1, address2, address3, city,
275                                                    cityGeoCode, countyGeoCode, state, stateGeoCode, postalCode, postalCodeGeoCode, countryGeoCode, isCommercial,
276                                                    createdBy);
277                                            
278                                            contactControl.createPartyContactMechanism(party, contactMechanism, description,
279                                                    false, 1, createdBy);
280
281                                            var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(contactMechanism.getPrimaryKey());
282                                            workflowControl.addEntityToWorkflowUsingNames(null, PostalAddressStatusConstants.Workflow_POSTAL_ADDRESS_STATUS,
283                                                    PostalAddressStatusConstants.WorkflowEntrance_NEW_POSTAL_ADDRESS, entityInstance, null, null, createdBy);
284                                            
285                                            result.setContactMechanismName(contactMechanism.getLastDetail().getContactMechanismName());
286                                            result.setEntityRef(contactMechanism.getPrimaryKey().getEntityRef());
287                                        } else {
288                                            addExecutionError(ExecutionErrors.UnknownCity.name(), city, cityAlias);
289                                        }
290                                    } else {
291                                        addExecutionError(ExecutionErrors.MissingCity.name());
292                                    }
293                                } else {
294                                    addExecutionError(ExecutionErrors.UnknownState.name(), state, stateAlias);
295                                }
296                            } else {
297                                addExecutionError(ExecutionErrors.MissingState.name());
298                            }
299                        } else {
300                            addExecutionError(ExecutionErrors.UnknownPostalCode.name(), postalCode);
301                        }
302                    } else {
303                        addExecutionError(ExecutionErrors.InvalidPostalCode.name(), postalCode);
304                    }
305                } else {
306                    addExecutionError(ExecutionErrors.MissingPostalCode.name());
307                }
308            } else {
309                addExecutionError(ExecutionErrors.UnknownCountryName.name(), countryName, countryAlias);
310            }
311        } else {
312            addExecutionError(ExecutionErrors.UnknownPartyName.name(), partyName);
313        }
314        
315        return result;
316    }
317    
318}