001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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    package org.kuali.kfs.module.endow.document.validation.impl;
017    
018    import java.math.BigDecimal;
019    
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.log4j.Logger;
022    import org.kuali.kfs.module.endow.EndowConstants;
023    import org.kuali.kfs.module.endow.EndowKeyConstants;
024    import org.kuali.kfs.module.endow.EndowPropertyConstants;
025    import org.kuali.kfs.module.endow.businessobject.ClassCode;
026    import org.kuali.kfs.module.endow.businessobject.Security;
027    import org.kuali.kfs.module.endow.document.service.KEMService;
028    import org.kuali.kfs.module.endow.util.KEMCalculationRoundingHelper;
029    import org.kuali.kfs.sys.KFSKeyConstants;
030    import org.kuali.kfs.sys.context.SpringContext;
031    import org.kuali.rice.kns.document.MaintenanceDocument;
032    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
033    import org.kuali.rice.kns.service.DataDictionaryService;
034    import org.kuali.rice.kns.util.GlobalVariables;
035    import org.kuali.rice.kns.util.MessageMap;
036    import org.kuali.rice.kns.util.ObjectUtils;
037    
038    public class SecurityRule extends MaintenanceDocumentRuleBase {
039    
040        protected static Logger LOG = org.apache.log4j.Logger.getLogger(SecurityRule.class);
041        protected Security newSecurity;
042        protected Security oldSecurity;
043    
044        /**
045         * This method initializes the old and new security.
046         * 
047         * @param document
048         */
049        protected void initializeAttributes(MaintenanceDocument document) {
050            if (newSecurity == null) {
051                newSecurity = (Security) document.getNewMaintainableObject().getBusinessObject();
052            }
053            if (oldSecurity == null) {
054                oldSecurity = (Security) document.getOldMaintainableObject().getBusinessObject();
055            }
056        }
057    
058        /**
059         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
060         */
061        @Override
062        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
063    
064            boolean isValid = true;
065            isValid &= super.processCustomRouteDocumentBusinessRules(document);
066            MessageMap errorMap = GlobalVariables.getMessageMap();
067            isValid &= errorMap.hasNoErrors();
068    
069            if (isValid) {
070    
071                initializeAttributes(document);
072                isValid &= checkClassCode();
073                isValid &= checkCustomRequiredFields();
074                isValid &= checkUnitValue();
075                isValid &= checkValuesBasedOnValuationMethod();
076                isValid &= checkIncomeFrequencyCodeWhenPooledFundClassCodeUsed();
077                isValid &= checkSecurityDividendPayDate();
078                isValid &= checkDividendAmountChanged();
079            }
080    
081            return isValid;
082        }
083    
084        /**
085         * Checks the required fields based on user input data. If the Security Class Code for the new security record has a Security
086         * Accrual Method of 3,6,B,M or T the following fields are required input: 1. Issue Date 2. Maturity Date 3. Interest Rate or
087         * Amount 4. Income Pay Frequency. If the Class Code Type is an Alternative Investment, then the Commitment Amount field is
088         * required.
089         * 
090         * @param newSecurity
091         * @return true if required fields entered, false otherwise
092         */
093        protected boolean checkCustomRequiredFields() {
094    
095            boolean isValid = true;
096            ClassCode classCode = newSecurity.getClassCode();
097            DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class);
098    
099            if (ObjectUtils.isNotNull(classCode) && StringUtils.isNotEmpty(classCode.getSecurityAccrualMethod())) {
100    
101                String accrualMethod = classCode.getSecurityAccrualMethod();
102    
103                // if accrual method is 3,6,B,M,T
104                if (accrualMethod.equalsIgnoreCase(EndowConstants.AccrualMethod.MORTGAGE_30) || accrualMethod.equalsIgnoreCase(EndowConstants.AccrualMethod.MORTGAGE_60) || accrualMethod.equalsIgnoreCase(EndowConstants.AccrualMethod.DISCOUNT_BONDS) || accrualMethod.equalsIgnoreCase(EndowConstants.AccrualMethod.TIME_DEPOSITS) || accrualMethod.equalsIgnoreCase(EndowConstants.AccrualMethod.TREASURY_NOTES_AND_BONDS)) {
105    
106                    // maturity date required
107                    if (ObjectUtils.isNull(newSecurity.getMaturityDate())) {
108    
109                        String label = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Security.class.getName()).getAttributeDefinition(EndowPropertyConstants.SECURITY_MATURITY_DATE).getLabel();
110                        putFieldError(EndowPropertyConstants.SECURITY_MATURITY_DATE, KFSKeyConstants.ERROR_REQUIRED, label);
111                        isValid = false;
112                    }
113    
114                    // income pay frequency required
115                    if (StringUtils.isEmpty(newSecurity.getIncomePayFrequency())) {
116    
117                        String label = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Security.class.getName()).getAttributeDefinition(EndowPropertyConstants.SECURITY_INCOME_PAY_FREQUENCY).getLabel();
118                        putFieldError(EndowPropertyConstants.SECURITY_INCOME_PAY_FREQUENCY, KFSKeyConstants.ERROR_REQUIRED, label);
119                        isValid = false;
120                    }
121    
122                    // income rate required
123                    if (ObjectUtils.isNull(newSecurity.getIncomeRate())) {
124    
125                        String label = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Security.class.getName()).getAttributeDefinition(EndowPropertyConstants.SECURITY_INCOME_RATE).getLabel();
126                        putFieldError(EndowPropertyConstants.SECURITY_INCOME_RATE, KFSKeyConstants.ERROR_REQUIRED, label);
127                        isValid = false;
128                    }
129    
130                    // issue date required
131                    if (ObjectUtils.isNull(newSecurity.getIssueDate())) {
132    
133                        String label = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Security.class.getName()).getAttributeDefinition(EndowPropertyConstants.SECURITY_ISSUE_DATE).getLabel();
134                        putFieldError(EndowPropertyConstants.SECURITY_ISSUE_DATE, KFSKeyConstants.ERROR_REQUIRED, label);
135                        isValid = false;
136                    }
137    
138                }
139    
140                // if security class code type is Alternative Investment the Commitment Amount is required
141                if (ObjectUtils.isNotNull(classCode) && EndowConstants.ClassCodeTypes.ALTERNATIVE_INVESTMENT.equalsIgnoreCase(classCode.getClassCodeType())) {
142                    if (ObjectUtils.isNull(newSecurity.getCommitmentAmount()) || newSecurity.getCommitmentAmount().compareTo(BigDecimal.ZERO) == 0) {
143    
144                        String label = dataDictionaryService.getDataDictionary().getBusinessObjectEntry(Security.class.getName()).getAttributeDefinition(EndowPropertyConstants.SECURITY_COMMITMENT_AMOUNT).getLabel();
145                        putFieldError(EndowPropertyConstants.SECURITY_COMMITMENT_AMOUNT, KFSKeyConstants.ERROR_REQUIRED, label);
146                        isValid = false;
147                    }
148                }
149    
150            }
151            return isValid;
152        }
153    
154        /**
155         * This method checks if security unit value is valid. The unit value must always be greater than or equal to zero EXCEPT for
156         * liabilities which must always be less than or equal to zero.
157         * 
158         * @return true if valid, false otherwise
159         */
160        protected boolean checkUnitValue() {
161            boolean isValid = true;
162            ClassCode classCode = newSecurity.getClassCode();
163    
164            if (ObjectUtils.isNotNull(classCode)) {
165    
166                BigDecimal unitValue = newSecurity.getUnitValue();
167    
168                if (ObjectUtils.isNotNull(unitValue) && EndowConstants.ClassCodeTypes.LIABILITY.equalsIgnoreCase(classCode.getClassCodeType())) {
169    
170                    if (unitValue.compareTo(BigDecimal.ZERO) == 1) {
171                        putFieldError(EndowPropertyConstants.SECURITY_UNIT_VALUE, EndowKeyConstants.SecurityConstants.ERROR_SECURITY_UNIT_VALUE_LESS_THAN_OR_EQ_ZERO_FOR_LIABILITIES);
172                        isValid = false;
173                    }
174                }
175                else if (ObjectUtils.isNotNull(unitValue) && unitValue.compareTo(BigDecimal.ZERO) == -1) {
176                    putFieldError(EndowPropertyConstants.SECURITY_UNIT_VALUE, EndowKeyConstants.SecurityConstants.ERROR_SECURITY_UNIT_VALUE_LESS_THAN_OR_EQ_ZERO_FOR_NON_LIABILITIES);
177                    isValid = false;
178                }
179    
180            }
181            return isValid;
182        }
183    
184        /**
185         * This method checks that the old and new Class Codes belong to the same Class Code Type.
186         * 
187         * @return true if they belong to the same class code type, false otherwise.
188         */
189        protected boolean checkClassCode() {
190            boolean isValid = true;
191    
192            newSecurity.refreshReferenceObject(EndowPropertyConstants.SECURITY_CLASS_CODE_REF);
193    
194            if (ObjectUtils.isNotNull(oldSecurity) && ObjectUtils.isNotNull(oldSecurity.getClassCode()) && ObjectUtils.isNotNull(newSecurity.getClassCode())) {
195                String oldClassCodeType = oldSecurity.getClassCode().getClassCodeType();
196                if (!oldClassCodeType.equalsIgnoreCase(newSecurity.getClassCode().getClassCodeType())) {
197                    putFieldError(EndowPropertyConstants.SECURITY_CLASS_CODE, EndowKeyConstants.SecurityConstants.EROR_NEW_SECURITY_CLASS_CODE_TYPE_MUST_EQUAL_OLD_SEC_CLASS_CODE_TYPE);
198                    isValid = false;
199                }
200            }
201    
202            return isValid;
203        }
204    
205        /**
206         * Checks the following two rules: 5. If the class code for the security has a valuation method of U (Unit Value), the user can
207         * only enter a value in the SEC_UNIT_VAL. No entry is allowed in the SEC_VAL_BY_MKT field. 8. If the class code for the
208         * security has a valuation method of M (Market Value), the user can only enter a value in the SEC_ SEC_VAL_BY_MKT field. No
209         * entry is allowed in the SEC_UNIT_VAL field.
210         * 
211         * @return
212         */
213        protected boolean checkValuesBasedOnValuationMethod() {
214    
215            boolean isValid = true;
216    
217            newSecurity.refreshReferenceObject(EndowPropertyConstants.SECURITY_CLASS_CODE_REF);
218            ClassCode classCode = newSecurity.getClassCode();
219    
220            // If the class code for the security has a valuation method of U (Unit Value), the user can only enter a value in the
221            // SEC_UNIT_VAL. No entry is allowed in the SEC_VAL_BY_MKT field.
222            if (classCode != null && EndowConstants.ValuationMethod.UNITS.equalsIgnoreCase((classCode.getValuationMethod()))) {
223                if (newSecurity.getSecurityValueByMarket() != null) {
224                    putFieldError(EndowPropertyConstants.SECURITY_VALUE_BY_MARKET, EndowKeyConstants.SecurityConstants.ERROR_SECURITY_VAL_BY_MKT_MUST_BE_EMPTY_WHEN_VAL_MTHD_UNITS);
225                    isValid = false;
226                }
227            }
228            // If the class code for the security has a valuation method of M (Market Value), the user can only enter a value in the
229            // SEC_ SEC_VAL_BY_MKT field. No entry is allowed in the SEC_UNIT_VAL field.
230            if (classCode != null && EndowConstants.ValuationMethod.MARKET.equalsIgnoreCase((classCode.getValuationMethod()))) {
231                if (newSecurity.getUnitValue() != null) {
232                    putFieldError(EndowPropertyConstants.SECURITY_UNIT_VALUE, EndowKeyConstants.SecurityConstants.ERROR_SECURITY_UNIT_VAL_MUST_BE_EMPTY_WHEN_VAL_MTHD_MARKET);
233                    isValid = false;
234                }
235            }
236    
237            return isValid;
238        }
239    
240        protected boolean checkIncomeFrequencyCodeWhenPooledFundClassCodeUsed() {
241    
242            boolean isValid = true;
243    
244            newSecurity.refreshReferenceObject(EndowPropertyConstants.SECURITY_CLASS_CODE_REF);
245            ClassCode classCode = newSecurity.getClassCode();
246    
247            if (classCode.getClassCodeType() != null && classCode.getClassCodeType().equalsIgnoreCase(EndowConstants.ClassCodeTypes.POOLED_INVESTMENT)) {
248                String incomePayFrequencyCode = newSecurity.getIncomePayFrequency();
249    
250                if (StringUtils.isEmpty(incomePayFrequencyCode)) {
251                    putFieldError(EndowPropertyConstants.SECURITY_INCOME_PAY_FREQUENCY, EndowKeyConstants.SecurityConstants.ERROR_SECURITY_INCOME_PAY_FREQUENCY_CODE_NOT_ENTERED);
252                    isValid = false;
253                }
254            }
255    
256            return isValid;
257        }
258        
259        protected boolean checkSecurityDividendPayDate() {
260            newSecurity.refreshReferenceObject(EndowPropertyConstants.SECURITY_CLASS_CODE_REF);
261    
262            //KFSMI-6674
263            //If SEC_INC_PAY_FREQ entered then the SEC_INC_NEXT_PAY_DT is 
264            //automatically calculated.
265            //if class code type is stocks and SEC_DIV_PAY_DT is entered then 
266            //copy the date value to SEC_INC_NEXT_PAY_DT.
267            //We do not want to overwrite the date if it already exists.
268            
269            if (EndowConstants.ClassCodeTypes.STOCKS.equalsIgnoreCase(newSecurity.getClassCode().getClassCodeType())) {
270                if (newSecurity.getDividendPayDate() != null) {
271                    newSecurity.setIncomeNextPayDate(newSecurity.getDividendPayDate());
272                }
273            }
274            
275            return true;
276        }
277        
278        protected boolean checkDividendAmountChanged() {
279            //KFSMI-6706: rule #17 is implemented.
280            //If SEC_DVDND_AMT is changed then this value should be multiplied by 4 
281            //rounded to 5 decimal places and put the value in SEC_RT field.  Also
282            //SEC_INC_CHG_DT should be updated with PROC_DT value 
283            if (oldSecurity.getDividendAmount().compareTo(newSecurity.getDividendAmount()) != 0) {
284                newSecurity.setIncomeRate(KEMCalculationRoundingHelper.multiply(newSecurity.getDividendAmount(), new BigDecimal(4), EndowConstants.Scale.SECURITY_INCOME_RATE));
285                //setup process date into income change date...
286                newSecurity.setIncomeChangeDate(SpringContext.getBean(KEMService.class).getCurrentDate());
287            }
288            
289            return true;
290        }
291    }