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 }