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 }