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.batch.service.impl;
017
018 import static org.kuali.kfs.module.endow.EndowConstants.NEW_SOURCE_TRAN_LINE_PROPERTY_NAME;
019 import static org.kuali.kfs.module.endow.EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME;
020
021 import java.math.BigDecimal;
022 import java.util.ArrayList;
023 import java.util.Collection;
024 import java.util.HashMap;
025 import java.util.List;
026 import java.util.Map;
027
028 import org.kuali.kfs.module.endow.EndowConstants;
029 import org.kuali.kfs.module.endow.EndowParameterKeyConstants;
030 import org.kuali.kfs.module.endow.EndowPropertyConstants;
031 import org.kuali.kfs.module.endow.batch.CreateCashSweepTransactionsStep;
032 import org.kuali.kfs.module.endow.batch.reporter.ReportDocumentStatistics;
033 import org.kuali.kfs.module.endow.batch.service.CreateCashSweepTransactionsService;
034 import org.kuali.kfs.module.endow.businessobject.CashSweepModel;
035 import org.kuali.kfs.module.endow.businessobject.EndowmentExceptionReportHeader;
036 import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionLine;
037 import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionSecurity;
038 import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine;
039 import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionSecurity;
040 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine;
041 import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
042 import org.kuali.kfs.module.endow.businessobject.KEMID;
043 import org.kuali.kfs.module.endow.businessobject.KemidCurrentCash;
044 import org.kuali.kfs.module.endow.businessobject.PooledFundControl;
045 import org.kuali.kfs.module.endow.businessobject.TransactionDocumentExceptionReportLine;
046 import org.kuali.kfs.module.endow.businessobject.TransactionDocumentTotalReportLine;
047 import org.kuali.kfs.module.endow.dataaccess.CashSweepModelDao;
048 import org.kuali.kfs.module.endow.document.AssetDecreaseDocument;
049 import org.kuali.kfs.module.endow.document.AssetIncreaseDocument;
050 import org.kuali.kfs.module.endow.document.EndowmentTaxLotLinesDocumentBase;
051 import org.kuali.kfs.module.endow.document.service.KEMIDService;
052 import org.kuali.kfs.module.endow.document.service.KEMService;
053 import org.kuali.kfs.module.endow.document.service.PooledFundControlService;
054 import org.kuali.kfs.module.endow.document.service.UpdateAssetDecreaseDocumentTaxLotsService;
055 import org.kuali.kfs.module.endow.document.service.UpdateAssetIncreaseDocumentTaxLotsService;
056 import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent;
057 import org.kuali.kfs.module.endow.util.GloabalVariablesExtractHelper;
058 import org.kuali.kfs.sys.service.ReportWriterService;
059 import org.kuali.rice.kew.exception.WorkflowException;
060 import org.kuali.rice.kns.bo.DocumentHeader;
061 import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
062 import org.kuali.rice.kns.service.BusinessObjectService;
063 import org.kuali.rice.kns.service.DataDictionaryService;
064 import org.kuali.rice.kns.service.DocumentService;
065 import org.kuali.rice.kns.service.KualiConfigurationService;
066 import org.kuali.rice.kns.service.KualiRuleService;
067 import org.kuali.rice.kns.service.ParameterService;
068 import org.kuali.rice.kns.util.KualiDecimal;
069 import org.springframework.transaction.annotation.Transactional;
070
071 @Transactional
072 public class CreateCashSweepTransactionsServiceImpl implements CreateCashSweepTransactionsService {
073
074 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreateCashSweepTransactionsServiceImpl.class);
075 private static final String SUBMIT_DOCUMENT_DESCRIPTION = "Created by Create Cash Sweep Batch Process.";
076
077 private Map<String, ReportDocumentStatistics> statistics = new HashMap<String, ReportDocumentStatistics>();
078 private UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService;
079 private UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService;
080 private ReportWriterService createCashSweepExceptionReportWriterService;
081 private ReportWriterService createCashSweepProcessedReportWriterService;
082 private PooledFundControlService pooledFundControlService;
083 private BusinessObjectService businessObjectService;
084 private DataDictionaryService dataDictionaryService;
085 private KualiConfigurationService configService;
086 private CashSweepModelDao cashSweepModelDao;
087 private KualiRuleService kualiRuleService;
088 private ParameterService parameterService;
089 private DocumentService documentService;
090 private KEMIDService kemidService;
091 private KEMService kemService;
092
093 /**
094 * @see org.kuali.kfs.module.endow.batch.service.CreateCashSweepTransactionsService#createCashSweepTransactions()
095 */
096 public boolean createCashSweepTransactions() {
097
098 LOG.info("Starting \"Create Cash Sweep Transactions\" batch job...");
099 writeHeaders();
100
101 Collection<CashSweepModel> cashSweepModels = getCashSweepModelMatchingCurrentDate();
102 for (CashSweepModel cashSweepModel : cashSweepModels) {
103 processIncomeSweepPurchases(cashSweepModel);
104 processIncomeSweepSales(cashSweepModel);
105 processPrincipalSweepPurchases(cashSweepModel);
106 processPrincipalSweepSale(cashSweepModel);
107 }
108
109 writeStatistics();
110 LOG.info("Finished \"Create Cash Sweep Transactions\" batch job!");
111
112 return true;
113 }
114
115 /**
116 * Process all the principle cash sweep models for sales.
117 *
118 * @param cashSweepModel
119 */
120 protected boolean processPrincipalSweepSale(CashSweepModel cashSweepModel) {
121 LOG.info("Entering \"processPrincipalSweepSale\".");
122
123 boolean success = false;
124
125 // Step 2. Get security and registration code from cash sweep.
126 String sweepRegistraionCode = cashSweepModel.getPrincipleSweepRegistrationCode();
127 String sweepSecurityId = cashSweepModel.getPrincipleSweepInvestment();
128
129 // Steps 4 - 12.
130 success = processAssetDecreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepPrincipleCashLimit(), false);
131
132 LOG.info("Leaving \"processPrincipalSweepSale\".");
133
134 return success;
135 }
136
137 /**
138 * Process all the principle cash sweep models for purchases.
139 *
140 * @param cashSweepModel
141 */
142 protected boolean processPrincipalSweepPurchases(CashSweepModel cashSweepModel) {
143 LOG.info("Entering \"processPrincipalSweepPurchases\".");
144
145 boolean success = false;
146
147 // Step 2. Get security and registration code from cash sweep.
148 String sweepRegistraionCode = cashSweepModel.getPrincipleSweepRegistrationCode();
149 String sweepSecurityId = cashSweepModel.getPrincipleSweepInvestment();
150
151 // Steps 4 - 12.
152 success = processAssetIncreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepPrincipleCashLimit(), false);
153
154 LOG.info("Leaving \"processPrincipalSweepPurchases\".");
155
156 return success;
157 }
158
159 /**
160 * Process all the income cash sweep models for sales.
161 *
162 * @param cashSweepModel
163 */
164 protected boolean processIncomeSweepSales(CashSweepModel cashSweepModel) {
165 LOG.info("Entering \"processIncomeSweepSales\".");
166
167 boolean success = false;
168
169 // Step 2. Get security and registration code from cash sweep.
170 String sweepRegistraionCode = cashSweepModel.getIncomeSweepRegistrationCode();
171 String sweepSecurityId = cashSweepModel.getIncomeSweepInvestment();
172
173 // Steps 4 - 12.
174 success = processAssetDecreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepIncomeCashLimit(), true);
175
176 LOG.info("Leaving \"processIncomeSweepSales\".");
177
178 return success;
179 }
180
181 /**
182 * Process all the income cash sweep models for purchases.
183 *
184 * @param cashSweepModel
185 */
186 protected boolean processIncomeSweepPurchases(CashSweepModel cashSweepModel) {
187 LOG.info("Entering \"processIncomeSweepPurchases\".");
188
189 boolean success = false;
190
191 // Step 2. Get security and registration code from cash sweep.
192 String sweepRegistraionCode = cashSweepModel.getIncomeSweepRegistrationCode();
193 String sweepSecurityId = cashSweepModel.getIncomeSweepInvestment();
194
195 // Steps 4 - 12.
196 success = processAssetIncreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepIncomeCashLimit(), true);
197
198 LOG.info("Leaving \"processIncomeSweepPurchases\".");
199
200 return success;
201 }
202
203 /**
204 * Creates asset decrease documents with transaction lines and routes it.
205 *
206 * @param cashSweepModelId
207 * @param sweepRegistraionCode
208 * @param sweepSecurityId
209 * @param cashLimit
210 * @param isIncome
211 */
212 protected boolean processAssetDecreaseDocuments(Integer cashSweepModelId, String sweepRegistraionCode, String sweepSecurityId, BigDecimal cashLimit, boolean isIncome) {
213
214 boolean success = true;
215 AssetDecreaseDocument assetDecreaseDoc = null;
216
217 // Get the maximum number of allowed transaction lines per eDoc.
218 int maxNumberOfTransactionLines = getMaxNumberOfTransactionLines();
219
220 // Iterate through all the KEMIDs for processing.
221 List<KEMID> kemids = new ArrayList<KEMID>(kemidService.getByCashSweepId(cashSweepModelId));
222 for (int i = 0; i < kemids.size(); i++) {
223
224 // Get the current KEMID.
225 KEMID kemid = kemids.get(i);
226
227 // Get the current income/principle cash for this KEMID and compare it to the cash limit.
228 BigDecimal currentCash = getKemidCurrentCash(kemid, isIncome);
229 if (currentCash != null && currentCash.compareTo(BigDecimal.ZERO) < 0) {
230
231 // If this is null that means we need to create a new eDoc for
232 // the first time.
233 if (assetDecreaseDoc == null) {
234 assetDecreaseDoc = createAssetDecrease(sweepSecurityId, sweepRegistraionCode);
235 }
236
237 // Create, validate, and add the transaction line to the eDoc.
238 addTransactionLineForAssetDecrease(assetDecreaseDoc, kemid, cashLimit, currentCash, sweepSecurityId, isIncome);
239
240 // Check to see if we've reached our max number of transaction lines
241 // per eDoc. If so, validate, and submit the current eDoc and start
242 // another eDoc.
243 if (i != 0 && i % maxNumberOfTransactionLines == 0) {
244 // Validate and route the document. Then create a new doc.
245 success = routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
246 assetDecreaseDoc = createAssetDecrease(sweepSecurityId, sweepRegistraionCode);
247 }
248 }
249 }
250
251 // Verify that we don't need to do any clean-up. There could still be
252 // some let over transaction lines, less than the max amount that need
253 // to still be processed on the current eDoc. This also prevents from
254 // routing an asset decrease document with no transaction lines.
255 if (assetDecreaseDoc != null && !assetDecreaseDoc.getSourceTransactionLines().isEmpty()) {
256 // Validate and route the document.
257 success = routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
258 }
259
260 return success;
261 }
262
263 /**
264 * Creates asset increase documents with transaction lines and routes it.
265 *
266 * @param cashSweepModelId
267 * @param sweepRegistraionCode
268 * @param sweepSecurityId
269 * @param cashLimit
270 * @param isIncome
271 */
272 protected boolean processAssetIncreaseDocuments(Integer cashSweepModelId, String sweepRegistraionCode, String sweepSecurityId, BigDecimal cashLimit, boolean isIncome) {
273
274 boolean success = true;
275 AssetIncreaseDocument assetIncreaseDoc = null;
276
277 // Get the maximum number of allowed transaction lines per eDoc.
278 int maxNumberOfTransactionLines = getMaxNumberOfTransactionLines();
279
280 // Iterate through all the KEMIDs for processing.
281 List<KEMID> kemids = new ArrayList<KEMID>(kemidService.getByCashSweepId(cashSweepModelId));
282 for (int i = 0; i < kemids.size(); i++) {
283
284 // Get the current KEMID.
285 KEMID kemid = kemids.get(i);
286
287 // Get the current income/principle cash for this KEMID and compare it to the cash limit.
288 BigDecimal currentCash = getKemidCurrentCash(kemid, isIncome);
289 if (currentCash != null && ((currentCash.compareTo(cashLimit) > 0 || currentCash.compareTo(cashLimit) == 0))) {
290
291 // If this is null that means we need to create a new eDoc for
292 // the first time.
293 if (assetIncreaseDoc == null) {
294 assetIncreaseDoc = createAssetIncrease(sweepSecurityId, sweepRegistraionCode);
295 }
296
297 // Create, validate, and add the transaction line to the eDoc.
298 addTransactionLineForAssetIncrease(assetIncreaseDoc, kemid, cashLimit, currentCash, sweepSecurityId, isIncome);
299
300 // Check to see if we've reached our max number of transaction lines
301 // per eDoc. If so, validate, and submit the current eDoc and start
302 // another eDoc.
303 if (i != 0 && i % maxNumberOfTransactionLines == 0) {
304 // Validate and route the document. Then create a new doc.
305 success = routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
306 assetIncreaseDoc = createAssetIncrease(sweepSecurityId, sweepRegistraionCode);
307 }
308 }
309 }
310
311 // Verify that we don't need to do any clean-up. There could still be
312 // some let over transaction lines, less than the max amount that need
313 // to still be processed on the current eDoc. This also prevents from
314 // routing an asset decrease document with no transaction lines.
315 if (assetIncreaseDoc != null && !assetIncreaseDoc.getTargetTransactionLines().isEmpty()) {
316 // Validate and route the document.
317 success = routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
318 }
319
320 return success;
321 }
322
323 /**
324 *
325 * Calculates the number of units available.
326 *
327 * @param kemid
328 * @param sweepSecurityId
329 * @return
330 */
331 private KualiDecimal calculateAvailableKemidSecurities(KEMID kemid, String sweepSecurityId) {
332
333 BigDecimal unitsAvailable = BigDecimal.ZERO;
334
335 Map<String, String> criteria = new HashMap<String, String>();
336 criteria.put(EndowPropertyConstants.HOLDING_TAX_LOT_KEMID, kemid.getKemid());
337
338 List<HoldingTaxLot> holdingTaxLots = (List<HoldingTaxLot>)businessObjectService.findMatching(HoldingTaxLot.class, criteria);
339 for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
340 if(holdingTaxLot.getSecurityId().equalsIgnoreCase(sweepSecurityId)) {
341 unitsAvailable = unitsAvailable.add(holdingTaxLot.getUnits());
342 }
343 }
344
345 return (new KualiDecimal(unitsAvailable));
346 }
347
348 /**
349 * This method...
350 *
351 * @param assetDecreaseDoc
352 * @param kemid
353 * @param cashLimit
354 * @param currentCash
355 * @param isIncome
356 */
357 private void addTransactionLineForAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, KEMID kemid, BigDecimal cashLimit, BigDecimal currentCash, String sweepSecurityId, boolean isIncome) {
358 // Calculate the amount.
359 KualiDecimal amount = calculateCashAvailable(cashLimit, currentCash, false);
360
361 // Calculate available units and determine the transaction amount.
362 // If the units needed are equal to or less than the units held then
363 // use the calculated units. If the units needed are greater than the
364 // units held, then use the units held as the transaction amount.
365 KualiDecimal available = calculateAvailableKemidSecurities(kemid, sweepSecurityId);
366 if (amount.compareTo(available) > 0) { amount = available; }
367
368 // Create the correct transaction line based on if it's a source or target type.
369 EndowmentTransactionLine transactionLine = createTransactionLine(assetDecreaseDoc.getDocumentNumber(), kemid.getKemid(), amount, true, isIncome);
370
371 // Validate the transaction line.
372 boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_SOURCE_TRAN_LINE_PROPERTY_NAME, assetDecreaseDoc, transactionLine));
373
374 // If the validation passes, add the transaction line to the document; otherwise,
375 // write the error messages to the error report.
376 if (rulesPassed) {
377 assetDecreaseDoc.addSourceTransactionLine((EndowmentSourceTransactionLine) transactionLine);
378 updateEadTaxLotService.updateTransactionLineTaxLots(assetDecreaseDoc, transactionLine);
379 }
380 else {
381 writeExceptionTableRowAssetDecrease(assetDecreaseDoc, transactionLine, isIncome);
382 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
383 for (String errorMessage : errorMessages) {
384 writeExceptionTableReason(errorMessage);
385 }
386 }
387 }
388
389 /**
390 * This method...
391 *
392 * @param assetIncreaseDoc
393 * @param kemid
394 * @param cashLimit
395 * @param currentCash
396 * @param isIncome
397 */
398 private void addTransactionLineForAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, KEMID kemid, BigDecimal cashLimit, BigDecimal currentCash, String sweepSecurityId, boolean isIncome) {
399 // Calculate the amount.
400 KualiDecimal amount = calculateCashAvailable(cashLimit, currentCash, true);
401
402 // Create the correct transaction line based on if it's a source or target type.
403 EndowmentTransactionLine transactionLine = createTransactionLine(assetIncreaseDoc.getDocumentNumber(), kemid.getKemid(), amount, false, isIncome);
404
405 // Validate the transaction line.
406 boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_TARGET_TRAN_LINE_PROPERTY_NAME, assetIncreaseDoc, transactionLine));
407
408 // If the validation passes, add the transaction line to the document; otherwise,
409 // write the error messages to the error report.
410 if (rulesPassed) {
411 assetIncreaseDoc.addTargetTransactionLine((EndowmentTargetTransactionLine) transactionLine);
412 updateEaiTaxLotService.updateTransactionLineTaxLots(assetIncreaseDoc, transactionLine);
413 }
414 else {
415 writeExceptionTableRowAssetIncrease(assetIncreaseDoc, transactionLine, isIncome);
416 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
417 for (String errorMessage : errorMessages) {
418 writeExceptionTableReason(errorMessage);
419 }
420 }
421 }
422
423 /**
424 * Validates and routes the asset decrease document.
425 *
426 * @param assetDecreaseDoc
427 * @param balh
428 */
429 protected boolean routeAssetDecreaseDocument(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
430
431 boolean success = false;
432
433 // Perform validation on the document.
434 boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetDecreaseDoc));
435
436 // If the document passed validations, route it accordingly.
437 if (rulesPassed) {
438 boolean noRouteIndicator = getNoRouteIndicator(true);
439 try {
440 assetDecreaseDoc.setNoRouteIndicator(noRouteIndicator);
441 documentService.routeDocument(assetDecreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
442 writeProcessedTableRowAssetDecrease(assetDecreaseDoc, isIncome);
443 success = true;
444 }
445 catch (WorkflowException we) {
446 writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
447 LOG.error(we.getLocalizedMessage());
448 }
449 }
450 else {
451 // Write the errors to the exception file.
452 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
453 writeExceptionTableRowAssetDecrease(assetDecreaseDoc, null, isIncome);
454 for (String errorMessage : errorMessages) {
455 writeExceptionTableReason(errorMessage);
456 }
457 // Try to save the document if it fails validation.
458 try {
459 documentService.saveDocument(assetDecreaseDoc);
460 }
461 catch (WorkflowException we) {
462 writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
463 LOG.error(we.getLocalizedMessage());
464 }
465 }
466
467 return success;
468 }
469
470 /**
471 * Validates and routes the asset increase document.
472 *
473 * @param assetIncreaseDoc
474 */
475 protected boolean routeAssetIncreaseDocument(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
476
477 boolean success = false;
478
479 // Perform validation on the document.
480 boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetIncreaseDoc));
481
482 // If the document passed validations, route it accordingly.
483 if (rulesPassed) {
484 boolean noRouteIndicator = getNoRouteIndicator(false);
485 try {
486 assetIncreaseDoc.setNoRouteIndicator(noRouteIndicator);
487 documentService.routeDocument(assetIncreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
488 writeProcessedTableRowAssetIncrease(assetIncreaseDoc, isIncome);
489 success = true;
490 }
491 catch (WorkflowException we) {
492 writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
493 LOG.error(we.getLocalizedMessage());
494 }
495 }
496 else {
497 // Write the errors to the exception file.
498 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
499 writeExceptionTableRowAssetIncrease(assetIncreaseDoc, null, isIncome);
500 for (String errorMessage : errorMessages) {
501 writeExceptionTableReason(errorMessage);
502 }
503 // Try to save the document if it fails validation.
504 try {
505 documentService.saveDocument(assetIncreaseDoc);
506 }
507 catch (WorkflowException we) {
508 writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
509 LOG.error(we.getLocalizedMessage());
510 }
511 }
512
513 return success;
514 }
515
516 /**
517 * Creates and returns a new principle or income transaction line.
518 *
519 * @param docNumber
520 * @param kemid
521 * @param amount
522 * @param isSource
523 * @return
524 */
525 private EndowmentTransactionLine createTransactionLine(String docNumber, String kemid, KualiDecimal amount, boolean isSource, boolean isIncome) {
526
527 EndowmentTransactionLine transactionLine = null;
528 if (isSource) {
529 transactionLine = new EndowmentSourceTransactionLine();
530 }
531 else {
532 transactionLine = new EndowmentTargetTransactionLine();
533 }
534
535 // Set values on the transaction line.
536 transactionLine.setDocumentNumber(docNumber);
537 transactionLine.setKemid(kemid);
538 if (!isIncome) {
539 transactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.PRINCIPAL);
540 }
541 else {
542 transactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME);
543 }
544 // These should be whole numbers.
545 transactionLine.setTransactionAmount(amount);
546 transactionLine.setTransactionUnits(amount);
547
548 return transactionLine;
549 }
550
551 /**
552 * Creates and returns a new asset increase document.
553 *
554 * @param securityId
555 * @param registrationCode
556 * @return
557 */
558 private AssetIncreaseDocument createAssetIncrease(String securityId, String registrationCode) {
559
560 AssetIncreaseDocument assetIncrease = null;
561 try {
562 assetIncrease = (AssetIncreaseDocument) documentService.getNewDocument(AssetIncreaseDocument.class);
563
564 // Set the document description.
565 DocumentHeader docHeader = assetIncrease.getDocumentHeader();
566 docHeader.setDocumentDescription(getPurchaseDescription());
567 assetIncrease.setDocumentHeader(docHeader);
568
569 // Set the sub type code to cash.
570 assetIncrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
571
572 // Create and set the target security transaction line.
573 EndowmentTargetTransactionSecurity targetTransactionSecurity = new EndowmentTargetTransactionSecurity();
574 targetTransactionSecurity.setRegistrationCode(registrationCode);
575 targetTransactionSecurity.setSecurityID(securityId);
576
577 assetIncrease.setTargetTransactionSecurity(targetTransactionSecurity);
578 }
579 catch (WorkflowException ex) {
580 writeExceptionTableReason("Workflow error while trying to create EAI document: " + ex.getLocalizedMessage());
581 LOG.error(ex.getLocalizedMessage());
582 }
583
584 return assetIncrease;
585 }
586
587 /**
588 * Creates and returns a new asset decrease document.
589 *
590 * @param securityId
591 * @param registrationCode
592 * @return
593 */
594 private AssetDecreaseDocument createAssetDecrease(String securityId, String registrationCode) {
595
596 AssetDecreaseDocument assetDecrease = null;
597 try {
598 assetDecrease = (AssetDecreaseDocument) documentService.getNewDocument(AssetDecreaseDocument.class);
599
600 // Set the document description.
601 DocumentHeader docHeader = assetDecrease.getDocumentHeader();
602 docHeader.setDocumentDescription(getSaleDescription());
603 assetDecrease.setDocumentHeader(docHeader);
604
605 // Set the sub type code to cash.
606 assetDecrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
607
608 // Create and set the source security transaction line.
609 EndowmentSourceTransactionSecurity sourceTransactionSecurity = new EndowmentSourceTransactionSecurity();
610 sourceTransactionSecurity.setRegistrationCode(registrationCode);
611 sourceTransactionSecurity.setSecurityID(securityId);
612
613 assetDecrease.setSourceTransactionSecurity(sourceTransactionSecurity);
614
615 }
616 catch (WorkflowException ex) {
617 writeExceptionTableReason("Workflow error while trying to create EAD document: " + ex.getLocalizedMessage());
618 LOG.error(ex.getLocalizedMessage());
619 }
620
621 return assetDecrease;
622 }
623
624 /**
625 * Gets the current principle or income cash for the specified KEMID.
626 *
627 * @param kemid
628 * @return
629 */
630 private BigDecimal getKemidCurrentCash(KEMID kemid, boolean isIncome) {
631 KemidCurrentCash kemidCurrentCash = businessObjectService.findBySinglePrimaryKey(KemidCurrentCash.class, kemid.getKemid());
632
633 if (kemidCurrentCash == null) {
634 writeExceptionTableReason("Unable to calculate current cash amount: END_CRNT_CSH_T is \'null\' for KEMID \'" + kemid.getKemid() + "\'");
635 return null;
636 }
637
638 return !isIncome ? kemidCurrentCash.getCurrentPrincipalCash().bigDecimalValue() : kemidCurrentCash.getCurrentIncomeCash().bigDecimalValue();
639 }
640
641 /**
642 * Determines the cash available.
643 *
644 * @param cashLimit
645 * @param currentCash
646 * @return
647 */
648 private KualiDecimal calculateCashAvailable(BigDecimal cashLimit, BigDecimal currentCash, boolean isIncrease) {
649
650 BigDecimal amount = null;
651
652 // Subtract the current amount from the limit amount, and round up.
653 if (isIncrease) {
654 amount = currentCash.subtract(cashLimit);
655 amount = amount.setScale(0, BigDecimal.ROUND_UP);
656 }
657 // Take the absolute value of the current cash, add limit amount,
658 // and round down.
659 else {
660 amount = (currentCash.abs()).add(cashLimit);
661 amount = amount.setScale(0, BigDecimal.ROUND_DOWN);
662 }
663
664 return new KualiDecimal(amount);
665 }
666
667 /**
668 * This method retrieves all the cash sweep models whose frequency code matches the current date.
669 *
670 * @return Collection of CashSweepModel business objects
671 */
672 private Collection<CashSweepModel> getCashSweepModelMatchingCurrentDate() {
673 return cashSweepModelDao.getCashSweepModelWithNextPayDateEqualToCurrentDate(kemService.getCurrentDate());
674 }
675
676 /**
677 * Gets the appropriate approval indicator based on if it's for a sale or purchase type.
678 *
679 * @param isSale
680 * @return
681 */
682 private boolean getNoRouteIndicator(boolean isSale) {
683 boolean approvalIndicator = isSale ? getSaleNoRouteIndicator() : getPurchaseNoRouteIndicator();
684 return approvalIndicator;
685 }
686
687 /**
688 * This method returns the true or false value of the purchase no route indicator.
689 *
690 * @return
691 */
692 private boolean getPurchaseNoRouteIndicator() {
693 String noRouteIndicator = parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_NO_ROUTE_IND);
694 return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
695 }
696
697 /**
698 * This method returns the true or false value of the sale no route indicator.
699 *
700 * @return
701 */
702 private boolean getSaleNoRouteIndicator() {
703 String noRouteIndicator = parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.SALE_NO_ROUTE_IND);
704 return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
705 }
706
707 /**
708 * Gets the purchase description parameter.
709 *
710 * @return
711 */
712 private String getPurchaseDescription() {
713 return parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_DESCRIPTION);
714 }
715
716 /**
717 * Gets the sale description parameter.
718 *
719 * @return
720 */
721 private String getSaleDescription() {
722 return parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.SALE_DESCRIPTION);
723 }
724
725 /**
726 * This method...
727 *
728 * @return
729 */
730 private int getMaxNumberOfTransactionLines() {
731 return kemService.getMaxNumberOfTransactionLinesPerDocument();
732 }
733
734 /**
735 * This method...
736 *
737 * @param assetIncreaseDoc
738 * @param isIncome
739 */
740 private void writeProcessedTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
741
742 TransactionDocumentTotalReportLine createCashSweepProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
743
744 List<EndowmentTransactionLine> transLines = assetIncreaseDoc.getTargetTransactionLines();
745 for (EndowmentTransactionLine tranLine : transLines) {
746 if (isIncome) {
747 createCashSweepProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
748 createCashSweepProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
749 }
750 else {
751 createCashSweepProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
752 createCashSweepProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
753 }
754 }
755 updatePostingStats(assetIncreaseDoc);
756
757 createCashSweepProcessedReportWriterService.writeTableRow(createCashSweepProcessedReportValues);
758 createCashSweepProcessedReportWriterService.writeNewLines(1);
759 }
760
761 /**
762 * This method...
763 *
764 * @param assetDecreaseDoc
765 * @param isIncome
766 */
767 private void writeProcessedTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
768
769 TransactionDocumentTotalReportLine createCashSweepProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
770
771 List<EndowmentTransactionLine> transLines = assetDecreaseDoc.getSourceTransactionLines();
772 for (EndowmentTransactionLine tranLine : transLines) {
773 if (isIncome) {
774 createCashSweepProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
775 createCashSweepProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
776 }
777 else {
778 createCashSweepProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
779 createCashSweepProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
780 }
781 }
782 updatePostingStats(assetDecreaseDoc);
783
784 createCashSweepProcessedReportWriterService.writeTableRow(createCashSweepProcessedReportValues);
785 createCashSweepProcessedReportWriterService.writeNewLines(1);
786 }
787
788 /**
789 * This method...
790 *
791 * @param assetIncreaseDoc
792 * @param tranLine
793 * @param isIncome
794 */
795 private void writeExceptionTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
796
797 TransactionDocumentExceptionReportLine createCashSweepExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
798
799 if (tranLine != null) {
800 createCashSweepExceptionReportValues.setKemid(tranLine.getKemid());
801 if (isIncome) {
802 createCashSweepExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
803 createCashSweepExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
804 }
805 else {
806 createCashSweepExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
807 createCashSweepExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
808 }
809 }
810 updateErrorStats(assetIncreaseDoc);
811
812 createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportValues);
813 createCashSweepExceptionReportWriterService.writeNewLines(1);
814 }
815
816 /**
817 * This method...
818 *
819 * @param assetDecreaseDoc
820 * @param tranLine
821 * @param isIncome
822 */
823 private void writeExceptionTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
824
825 TransactionDocumentExceptionReportLine createCashSweepExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
826
827 if (tranLine != null) {
828 createCashSweepExceptionReportValues.setKemid(tranLine.getKemid());
829 if (isIncome) {
830 createCashSweepExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
831 createCashSweepExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
832 }
833 else {
834 createCashSweepExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
835 createCashSweepExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
836 }
837 }
838 updateErrorStats(assetDecreaseDoc);
839
840 createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportValues);
841 createCashSweepExceptionReportWriterService.writeNewLines(1);
842 }
843
844 /**
845 * Writes the reason row and inserts a blank line.
846 *
847 * @param reasonMessage
848 */
849 private void writeExceptionTableReason(String reasonMessage) {
850 EndowmentExceptionReportHeader createCashSweepExceptionReportReason = new EndowmentExceptionReportHeader();
851 createCashSweepExceptionReportReason.setColumnHeading1("Reason: ");
852 createCashSweepExceptionReportReason.setColumnHeading2(reasonMessage);
853
854 createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportReason);
855 createCashSweepExceptionReportWriterService.writeNewLines(1);
856 }
857
858 /**
859 * Initialize the report document headers.
860 */
861 private void writeHeaders() {
862 createCashSweepExceptionReportWriterService.writeNewLines(1);
863 createCashSweepProcessedReportWriterService.writeNewLines(1);
864 createCashSweepExceptionReportWriterService.writeTableHeader(new TransactionDocumentExceptionReportLine());
865 createCashSweepProcessedReportWriterService.writeTableHeader(new TransactionDocumentTotalReportLine());
866 }
867
868 /**
869 * This method...
870 *
871 * @param assetDocument
872 */
873 private void updatePostingStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
874
875 String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
876 ReportDocumentStatistics stats = statistics.get(documentTypeName);
877
878 // If null that means there isn't one in the map, so create it and add
879 // it to the map.
880 if (stats == null) {
881 stats = new ReportDocumentStatistics(documentTypeName);
882 statistics.put(documentTypeName, stats);
883 }
884 stats.addNumberOfSourceTransactionLines(assetDocument.getSourceTransactionLines().size());
885 stats.addNumberOfTargetTransactionLines(assetDocument.getTargetTransactionLines().size());
886
887 stats.incrementNumberOfDocuments();
888 }
889
890 /**
891 * This method...
892 *
893 * @param assetDocument
894 */
895 private void updateErrorStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
896
897 String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
898 ReportDocumentStatistics stats = statistics.get(documentTypeName);
899
900 // If null that means there isn't one in the map, so create it and add
901 // it to the map.
902 if (stats == null) {
903 stats = new ReportDocumentStatistics(documentTypeName);
904 statistics.put(documentTypeName, stats);
905 }
906
907 stats.incrementNumberOfErrors();
908 }
909
910 /**
911 * Write out the statistics.
912 */
913 private void writeStatistics() {
914
915 for (Map.Entry<String, ReportDocumentStatistics> entry : statistics.entrySet()) {
916
917 ReportDocumentStatistics stats = entry.getValue();
918
919 createCashSweepProcessedReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
920 createCashSweepProcessedReportWriterService.writeStatisticLine(" Number of Documents Generated: %d", stats.getNumberOfDocuments());
921 createCashSweepProcessedReportWriterService.writeStatisticLine(" Number of Transaction Lines Generated: %d", stats.getTotalNumberOfTransactionLines());
922 createCashSweepProcessedReportWriterService.writeStatisticLine(" Number of Error Records Written: %d", stats.getNumberOfErrors());
923 createCashSweepProcessedReportWriterService.writeStatisticLine("", "");
924
925 createCashSweepExceptionReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
926 createCashSweepExceptionReportWriterService.writeStatisticLine(" Number of Documents Generated: %d", stats.getNumberOfDocuments());
927 createCashSweepExceptionReportWriterService.writeStatisticLine(" Number of Transaction Lines Generated: %d", stats.getTotalNumberOfTransactionLines());
928 createCashSweepExceptionReportWriterService.writeStatisticLine(" Number of Error Records Written: %d", stats.getNumberOfErrors());
929 createCashSweepExceptionReportWriterService.writeStatisticLine("", "");
930 }
931
932 }
933
934 /**
935 * Sets the businessObjectService attribute value.
936 *
937 * @param businessObjectService The businessObjectService to set.
938 */
939 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
940 this.businessObjectService = businessObjectService;
941 }
942
943 /**
944 * Sets the kemService attribute value.
945 *
946 * @param kemService The kemService to set.
947 */
948 public void setKemService(KEMService kemService) {
949 this.kemService = kemService;
950 }
951
952 /**
953 * Sets the kemidService attribute value.
954 *
955 * @param kemidService The kemidService to set.
956 */
957 public void setKemidService(KEMIDService kemidService) {
958 this.kemidService = kemidService;
959 }
960
961 /**
962 * Sets the documentService attribute value.
963 *
964 * @param documentService The documentService to set.
965 */
966 public void setDocumentService(DocumentService documentService) {
967 this.documentService = documentService;
968 }
969
970 /**
971 * Sets the kualiRuleService attribute value.
972 *
973 * @param kualiRuleService The kualiRuleService to set.
974 */
975 public void setKualiRuleService(KualiRuleService kualiRuleService) {
976 this.kualiRuleService = kualiRuleService;
977 }
978
979 /**
980 * Sets the parameterService attribute value.
981 *
982 * @param parameterService The parameterService to set.
983 */
984 public void setParameterService(ParameterService parameterService) {
985 this.parameterService = parameterService;
986 }
987
988 /**
989 * Sets the createCashSweepExceptionReportWriterService attribute value.
990 *
991 * @param createCashSweepExceptionReportWriterService The createCashSweepExceptionReportWriterService to set.
992 */
993 public void setCreateCashSweepExceptionReportWriterService(ReportWriterService createCashSweepExceptionReportWriterService) {
994 this.createCashSweepExceptionReportWriterService = createCashSweepExceptionReportWriterService;
995 }
996
997 /**
998 * Sets the createCashSweepProcessedReportWriterService attribute value.
999 *
1000 * @param createCashSweepProcessedReportWriterService The createCashSweepProcessedReportWriterService to set.
1001 */
1002 public void setCreateCashSweepProcessedReportWriterService(ReportWriterService createCashSweepProcessedReportWriterService) {
1003 this.createCashSweepProcessedReportWriterService = createCashSweepProcessedReportWriterService;
1004 }
1005
1006 /**
1007 * Sets the configService attribute value.
1008 *
1009 * @param configService The configService to set.
1010 */
1011 public void setConfigService(KualiConfigurationService configService) {
1012 this.configService = configService;
1013 }
1014
1015 /**
1016 * Sets the cashSweepModelDao attribute value.
1017 *
1018 * @param cashSweepModelDao The cashSweepModelDao to set.
1019 */
1020 public void setCashSweepModelDao(CashSweepModelDao cashSweepModelDao) {
1021 this.cashSweepModelDao = cashSweepModelDao;
1022 }
1023
1024 /**
1025 * Sets the dataDictionaryService attribute value.
1026 *
1027 * @param dataDictionaryService The dataDictionaryService to set.
1028 */
1029 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1030 this.dataDictionaryService = dataDictionaryService;
1031 }
1032
1033 /**
1034 * Sets the updateEaiTaxLotService attribute value.
1035 *
1036 * @param updateEaiTaxLotService The updateEaiTaxLotService to set.
1037 */
1038 public void setUpdateEaiTaxLotService(UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService) {
1039 this.updateEaiTaxLotService = updateEaiTaxLotService;
1040 }
1041
1042 /**
1043 * Sets the updateEadTaxLotService attribute value.
1044 *
1045 * @param updateEadTaxLotService The updateEadTaxLotService to set.
1046 */
1047 public void setUpdateEadTaxLotService(UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService) {
1048 this.updateEadTaxLotService = updateEadTaxLotService;
1049 }
1050
1051 }