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.fp.document.service.impl;
017
018 import java.util.ArrayList;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.kuali.kfs.fp.businessobject.CashDrawer;
026 import org.kuali.kfs.fp.businessobject.CashieringItemInProcess;
027 import org.kuali.kfs.fp.businessobject.CashieringTransaction;
028 import org.kuali.kfs.fp.businessobject.Check;
029 import org.kuali.kfs.fp.businessobject.CoinDetail;
030 import org.kuali.kfs.fp.businessobject.CurrencyDetail;
031 import org.kuali.kfs.fp.businessobject.Deposit;
032 import org.kuali.kfs.fp.businessobject.DepositCashReceiptControl;
033 import org.kuali.kfs.fp.businessobject.format.CashDrawerStatusCodeFormatter;
034 import org.kuali.kfs.fp.document.CashManagementDocument;
035 import org.kuali.kfs.fp.document.CashReceiptDocument;
036 import org.kuali.kfs.fp.document.dataaccess.CashManagementDao;
037 import org.kuali.kfs.fp.document.service.CashManagementService;
038 import org.kuali.kfs.fp.document.service.CashReceiptService;
039 import org.kuali.kfs.fp.exception.CashDrawerStateException;
040 import org.kuali.kfs.fp.exception.InvalidCashReceiptState;
041 import org.kuali.kfs.fp.service.CashDrawerService;
042 import org.kuali.kfs.sys.KFSConstants;
043 import org.kuali.kfs.sys.KFSKeyConstants;
044 import org.kuali.kfs.sys.KFSPropertyConstants;
045 import org.kuali.kfs.sys.KFSConstants.CashDrawerConstants;
046 import org.kuali.kfs.sys.KFSConstants.CurrencyCoinSources;
047 import org.kuali.kfs.sys.KFSConstants.DepositConstants;
048 import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes;
049 import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes.CashReceipt;
050 import org.kuali.kfs.sys.businessobject.Bank;
051 import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
052 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
053 import org.kuali.kfs.sys.context.SpringContext;
054 import org.kuali.rice.kew.exception.WorkflowException;
055 import org.kuali.rice.kim.bo.Person;
056 import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
057 import org.kuali.rice.kns.exception.InfrastructureException;
058 import org.kuali.rice.kns.service.BusinessObjectService;
059 import org.kuali.rice.kns.service.DataDictionaryService;
060 import org.kuali.rice.kns.service.DateTimeService;
061 import org.kuali.rice.kns.service.DocumentHelperService;
062 import org.kuali.rice.kns.service.DocumentService;
063 import org.kuali.rice.kns.util.GlobalVariables;
064 import org.kuali.rice.kns.util.KualiDecimal;
065 import org.kuali.rice.kns.util.ObjectUtils;
066 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
067 import org.springframework.transaction.annotation.Transactional;
068
069 /**
070 * This is the default implementation of the CashManagementService interface.
071 */
072 @Transactional
073 public class CashManagementServiceImpl implements CashManagementService {
074 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashManagementServiceImpl.class);
075
076 private BusinessObjectService businessObjectService;
077 private CashDrawerService cashDrawerService;
078 private DateTimeService dateTimeService;
079 private DocumentService documentService;
080 private CashManagementDao cashManagementDao;
081 private DataDictionaryService dataDictionaryService;
082
083 /**
084 * If a CMD is found that is associated with the CR document, then that CMD is returned; otherwise null is returned.
085 * Currently the relationships are:
086 * <ul>
087 * <li>(CashReceipt to CashReceiptHeader) is (1 to 1)</li>
088 * <li>(CashReceiptHeader to DepositCashReceiptControl) is (1 to 1)</li>
089 * <li>(DepositCashReceiptControl to Deposit) is (many to 1)</li>
090 * <li>(Deposit to CashManagementDocument) is (many to 1)</li>
091 * </ul>
092 *
093 * @param documentId The id of the cash receipt document linked to the cash management document.
094 * @return An instance of a CashManagementDocument matching the provided search criteria, or null if no value is found.
095 *
096 * @see org.kuali.kfs.fp.document.service.CashManagementService#getCashManagementDocumentForCashReceiptId(java.lang.String)
097 */
098 public CashManagementDocument getCashManagementDocumentForCashReceiptId(String documentId) {
099 CashManagementDocument cmdoc = null;
100
101 // get CashReceiptHeader for the CashReceipt, if any
102 HashMap primaryKeys = new HashMap();
103 primaryKeys.put(KFSPropertyConstants.DOCUMENT_NUMBER, documentId);
104 CashReceiptDocument crDoc = (CashReceiptDocument) businessObjectService.findByPrimaryKey(getDataDictionaryService().getDocumentClassByTypeName(KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT), primaryKeys);
105
106 // get the DepositCashReceiptControl for the CashReceiptHeader
107 if (crDoc != null) {
108 List crcList = crDoc.getDepositCashReceiptControl();
109 if (!crcList.isEmpty()) {
110 DepositCashReceiptControl dpcrc = (DepositCashReceiptControl) crcList.get(0);
111
112 // get the Deposit and follow it to the CashManagementDocument
113 Deposit d = (Deposit) dpcrc.getDeposit();
114 cmdoc = d.getCashManagementDocument();
115 }
116 }
117
118 return cmdoc;
119 }
120
121
122 /**
123 * This method creates a new cash management document and sets the provided values as attributes to the document.
124 * The steps followed to create a new cash management document are as follows:
125 * <ul>
126 * <li>Find the drawer for the campus code given.</li>
127 * <li>Make sure the drawer is closed, force the drawer closed if it is not already closed.</li>
128 * <li>Create the cash management document, set the provided values to the document and link it to the cash drawer</li>
129 * </ul>
130 *
131 * If the campusCode or docDescription values are null, an IllegalArgumentException will be thrown.
132 *
133 * TODO - annotation is not used or set at all in this method, remove it if appropriate.
134 *
135 * @param campusCode The campus code of the cash drawer.
136 * @param docDescription The document description to be set on the new cash management document.
137 * @param annotation
138 * @return A new instance of a CashManagementDocument (not persisted).
139 *
140 * @see org.kuali.kfs.fp.document.service.CashManagementService#createCashManagementDocument(java.lang.String,
141 * java.lang.String, java.lang.String)
142 */
143 public CashManagementDocument createCashManagementDocument(String campusCode, String docDescription, String annotation) {
144 if (StringUtils.isBlank(campusCode)) {
145 throw new IllegalArgumentException("invalid (blank) campus code");
146 }
147 if (StringUtils.isBlank(docDescription)) {
148 throw new IllegalArgumentException("invalid (blank) docDescription");
149 }
150
151 // check user authorization
152 Person user = GlobalVariables.getUserSession().getPerson();
153 DocumentAuthorizer documentAuthorizer = SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(KFSConstants.FinancialDocumentTypeCodes.CASH_MANAGEMENT);
154 documentAuthorizer.canInitiate(KFSConstants.FinancialDocumentTypeCodes.CASH_MANAGEMENT, user);
155
156 // check cash drawer
157 CashDrawer cd = cashDrawerService.getByCampusCode(campusCode);
158 if (cd == null) {
159 throw new RuntimeException("No cash drawer exists for campus code "+campusCode+"; please create on via the Cash Drawer Maintenance Document before attemping to create a CashManagementDocument for campus "+campusCode);
160 }
161 String controllingDocId = cd.getReferenceFinancialDocumentNumber();
162
163 // KULEDOCS-1475: adding handling for two things which should never happen:
164 // 1. CashDrawer is open or locked by document 'null'
165 // 2. CashDrawer is open or locked by a document which doesn't exist
166 if (!cd.isClosed() || cd.getStatusCode() == null) {
167 boolean forceDrawerClosed = false;
168
169 if (cd.getStatusCode() == null) {
170 forceDrawerClosed = true;
171 }
172
173 if (StringUtils.isBlank(controllingDocId)) {
174 forceDrawerClosed = true;
175 }
176 else if (!documentService.documentExists(controllingDocId)) {
177 forceDrawerClosed = true;
178 }
179
180 if (forceDrawerClosed) {
181 cashDrawerService.closeCashDrawer(cd);
182 cd = cashDrawerService.getByCampusCode(campusCode);
183 if (cd == null) {
184 throw new RuntimeException("No cash drawer exists for campus code "+campusCode+"; please create on via the Cash Drawer Maintenance Document before attemping to create a CashManagementDocument for campus "+campusCode);
185 }
186 }
187 }
188
189
190 CashManagementDocument cmDoc = null;
191 if (cd.isClosed()) {
192 // create the document
193 try {
194 cmDoc = (CashManagementDocument) documentService.getNewDocument(CashManagementDocument.class);
195 cmDoc.getDocumentHeader().setDocumentDescription(docDescription);
196 cmDoc.setCampusCode(campusCode);
197 cmDoc.setCashDrawer(cd);
198 cmDoc.getCurrentTransaction().setCampusCode(cmDoc.getCampusCode());
199 cmDoc.getCurrentTransaction().setReferenceFinancialDocumentNumber(cmDoc.getDocumentNumber());
200 cmDoc.getCurrentTransaction().setOpenItemsInProcess(getOpenItemsInProcess(cmDoc));
201 }
202 catch (WorkflowException e) {
203 throw new InfrastructureException("unable to create CashManagementDocument", e);
204 }
205 }
206 else {
207 CashDrawerStatusCodeFormatter f = new CashDrawerStatusCodeFormatter();
208
209 throw new CashDrawerStateException(campusCode, controllingDocId, (String) f.format(CashDrawerConstants.STATUS_CLOSED), (String) f.format(cd.getStatusCode()));
210 }
211
212 return cmDoc;
213 }
214
215 /**
216 * This method creates new cumulative currency and coin details for the document given.
217 *
218 * @param cmDoc The cash management document the cumulative details will be associated with.
219 * @param cashieringSource The cashiering record source for the new details.
220 */
221 public void createNewCashDetails(CashManagementDocument cmDoc, String cashieringSource) {
222 CoinDetail coinDetail = new CoinDetail();
223 coinDetail.setDocumentNumber(cmDoc.getDocumentNumber());
224 coinDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
225 coinDetail.setCashieringRecordSource(cashieringSource);
226 businessObjectService.save(coinDetail);
227
228 CurrencyDetail currencyDetail = new CurrencyDetail();
229 currencyDetail.setDocumentNumber(cmDoc.getDocumentNumber());
230 currencyDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
231 currencyDetail.setCashieringRecordSource(cashieringSource);
232 businessObjectService.save(currencyDetail);
233 }
234
235 /**
236 * This method adds a new deposit to a the given CashManagementDocument.
237 * <br/>
238 * The following steps go into adding a deposit to a cash management document.
239 * <ul>
240 * <li>The given deposit parameters are validated.
241 * <li>The corresponding cash drawer is locked
242 * <li>The given cashiering check records are turned into check records
243 * <li>The new deposit is created
244 * <li>The deposit is added to the cash management document and persisted
245 * <li>The list of cash receipts are associated with the new deposit
246 * <li>The deposit is saved again to ensure all links and attributes of the deposit are set appropriately and persisted
247 * <li>The drawer is unlocked
248 * <ul>
249 *
250 * @param cashManagementDoc The document to have the deposit added to.
251 * @param depositTicketNumber The ticket number of the deposit being added.
252 * @param bankAccount The bank account on the deposit.
253 * @param selectedCashReceipts The collection of cash receipts associated with the new deposit.
254 * @param selectedCashieringChecks The collection of checks associated with the new deposit.
255 * @param isFinalDeposit A flag used to identify if a deposit is the final deposit to be added to a cash management document.
256 *
257 * @see org.kuali.kfs.fp.document.service.CashManagementService#addInterimDeposit(org.kuali.kfs.fp.document.CashManagementDocument,
258 * java.lang.String, org.kuali.kfs.fp.businessobject.BankAccount, java.util.List)
259 */
260 @SuppressWarnings("deprecation")
261 public void addDeposit(CashManagementDocument cashManagementDoc, String depositTicketNumber, Bank bank, List selectedCashReceipts, List selectedCashieringChecks, boolean isFinalDeposit) {
262 validateDepositParams(cashManagementDoc, bank, selectedCashReceipts);
263
264 String depositTypeCode = DepositConstants.DEPOSIT_TYPE_INTERIM;
265 if (isFinalDeposit) {
266 depositTypeCode = DepositConstants.DEPOSIT_TYPE_FINAL;
267 }
268
269 // lock the cashDrawer
270 cashDrawerService.lockCashDrawer(cashManagementDoc.getCashDrawer(), cashManagementDoc.getDocumentNumber());
271
272 // turn the list of selected check sequence ids into a list of actual check records
273 Map<Integer, Check> checks = getUndepositedChecksAsMap(cashManagementDoc);
274 List<Check> checksToSave = new ArrayList<Check>();
275 if (selectedCashieringChecks != null) {
276 for (Object o: selectedCashieringChecks) {
277 Integer sequenceId = (Integer)o;
278 Check check = checks.get(sequenceId);
279 checksToSave.add(check);
280 }
281 }
282
283 // create the Deposit
284 Deposit deposit = buildDeposit(cashManagementDoc, depositTypeCode, depositTicketNumber, bank, selectedCashReceipts, checksToSave);
285
286 // attach the deposit to the document
287 List deposits = cashManagementDoc.getDeposits();
288 deposits.add(deposit);
289 documentService.updateDocument(cashManagementDoc);
290
291 // associate the CashReceipts with the deposit
292 List dccList = new ArrayList();
293 for (Iterator i = selectedCashReceipts.iterator(); i.hasNext();) {
294 CashReceiptDocument crDoc = (CashReceiptDocument) i.next();
295 FinancialSystemDocumentHeader dh = crDoc.getDocumentHeader();
296
297 String statusCode = isFinalDeposit ? DocumentStatusCodes.CashReceipt.FINAL : DocumentStatusCodes.CashReceipt.INTERIM;
298 dh.setFinancialDocumentStatusCode(statusCode);
299 documentService.updateDocument(crDoc);
300
301 DepositCashReceiptControl dcc = new DepositCashReceiptControl();
302 dcc.setFinancialDocumentCashReceiptNumber(crDoc.getDocumentNumber());
303 dcc.setFinancialDocumentDepositNumber(deposit.getDocumentNumber());
304 dcc.setFinancialDocumentDepositLineNumber(deposit.getFinancialDocumentDepositLineNumber());
305
306 dcc.setCashReceiptDocument(crDoc);
307 dcc.setDeposit(deposit);
308
309 dccList.add(dcc);
310 }
311 // crHeaders get saved as side-effect of saving dccs
312 businessObjectService.save(dccList);
313
314 // make sure all checks have the right deposit line number
315 for (Check check: checksToSave) {
316 check.setFinancialDocumentDepositLineNumber(deposit.getFinancialDocumentDepositLineNumber());
317 }
318 businessObjectService.save(checksToSave);
319
320 // unlock the cashDrawer, if needed
321 if (!isFinalDeposit) {
322 cashDrawerService.unlockCashDrawer(cashManagementDoc.getCashDrawer(), cashManagementDoc.getDocumentNumber());
323 }
324 }
325
326 /**
327 * Validates the given Deposit parameters, throwing various (runtime) exceptions if errors exist.
328 *
329 * @param cashManagementDoc The document the deposit will be added to.
330 * @param bank The bank account of the deposit being added.
331 * @param selectedCashReceipts The collection of cash receipts associated with the new deposit.
332 */
333 protected void validateDepositParams(CashManagementDocument cashManagementDoc, Bank bank, List<CashReceiptDocument> selectedCashReceipts) {
334 if (cashManagementDoc == null) {
335 throw new IllegalArgumentException("invalid (null) cashManagementDoc");
336 }
337 else if (!cashManagementDoc.getDocumentHeader().getWorkflowDocument().stateIsSaved()) {
338 throw new IllegalStateException("cashManagementDoc '" + cashManagementDoc.getDocumentNumber() + "' is not in 'saved' state");
339 }
340 else if (cashManagementDoc.hasFinalDeposit()) {
341 throw new IllegalStateException("cashManagementDoc '" + cashManagementDoc.getDocumentNumber() + "' hasFinalDeposit");
342 }
343 if (bank == null) {
344 throw new IllegalArgumentException("invalid (null) bank");
345 }
346
347 if (selectedCashReceipts == null) {
348 throw new IllegalArgumentException("invalid (null) cashReceipts list");
349 }
350 else {
351 for (CashReceiptDocument cashReceipt : selectedCashReceipts) {
352 String statusCode = cashReceipt.getDocumentHeader().getFinancialDocumentStatusCode();
353 if (!StringUtils.equals(statusCode, DocumentStatusCodes.CashReceipt.VERIFIED)) {
354 throw new InvalidCashReceiptState("cash receipt document " + cashReceipt.getDocumentNumber() + " has a status other than 'verified' ");
355 }
356 }
357 }
358 }
359
360 /**
361 *
362 * This method builds a new deposit object from the parameters provided.
363 *
364 * @param cashManagementDoc The cash management document the deposit will be added to.
365 * @param depositTypeCode The type code associated with the deposit.
366 * @param depositTicketNumber The deposit ticket number to be set on the deposit object.
367 * @param bank The bank account of the deposit.
368 * @param selectedCashReceipts The cash receipts that make up the deposit.
369 * @param selectedCashieringChecks The cashiering checks that make up the deposit.
370 * @return A new instance of a deposit generated from all the parameters provided.
371 */
372 protected Deposit buildDeposit(CashManagementDocument cashManagementDoc, String depositTypeCode, String depositTicketNumber, Bank bank, List<CashReceiptDocument> selectedCashReceipts, List selectedCashieringChecks) {
373 Deposit deposit = new Deposit();
374 deposit.setDocumentNumber(cashManagementDoc.getDocumentNumber());
375 deposit.setCashManagementDocument(cashManagementDoc);
376
377 deposit.setDepositTypeCode(depositTypeCode);
378
379 deposit.setDepositDate(dateTimeService.getCurrentSqlDate());
380
381 deposit.setBank(bank);
382 deposit.setDepositBankCode(bank.getBankCode());
383
384 // derive the line number
385 int lineNumber = cashManagementDoc.getNextDepositLineNumber();
386 deposit.setFinancialDocumentDepositLineNumber(new Integer(lineNumber));
387
388 // trim depositTicketNumber to empty, because the field is optional
389 deposit.setDepositTicketNumber(StringUtils.trimToEmpty(depositTicketNumber));
390
391 // total up the cash receipts
392 KualiDecimal total = KualiDecimal.ZERO;
393 for (Iterator i = selectedCashReceipts.iterator(); i.hasNext();) {
394 CashReceiptDocument crDoc = (CashReceiptDocument) i.next();
395 total = total.add(crDoc.getTotalCheckAmount());
396 }
397 Check currCheck;
398 for (Object checkObj: selectedCashieringChecks) {
399 currCheck = (Check)checkObj;
400 total = total.add(currCheck.getAmount());
401 }
402 deposit.setDepositAmount(total);
403
404 return deposit;
405 }
406
407 /**
408 * This method returns all undeposited checks as a map with the key of each value in the map equal to the sequence id
409 * of the corresponding check.
410 *
411 * @param cmDoc The cash management doc to find undeposited checks for.
412 * @return A map of checks keyed on sequence id.
413 */
414 protected Map<Integer, Check> getUndepositedChecksAsMap(CashManagementDocument cmDoc) {
415 Map<Integer, Check> checks = new HashMap<Integer, Check>();
416 List<Check> checkList = cashManagementDao.selectUndepositedCashieringChecks(cmDoc.getDocumentNumber());
417 if (checkList != null && checkList.size() > 0) {
418 for (Check check: checkList) {
419 checks.put(check.getSequenceId(), check);
420 }
421 }
422 return checks;
423 }
424
425 /**
426 * This method cancels a cash management document, effectively nullifying all values and attributes associated with
427 * the document. Canceling a CashManagementDocument results in the following:
428 * <ul>
429 * <li>Cancels (deletes) all deposits associated with the document.</li>
430 * <li>Recloses the drawer</li>
431 * <li>Remove all currency and coin records generated by the document.</li>
432 * </ul>
433 * <br>
434 * NOTE: Method should only be called after the appropriate CashManagementDocumentRule has been successfully passed.
435 *
436 * @param cmDoc The CashManagementDocument to be canceled.
437 *
438 * @see org.kuali.kfs.fp.document.service.CashManagementService#cancelCashManagementDocument(org.kuali.kfs.fp.document.CashManagementDocument)
439 */
440 public void cancelCashManagementDocument(CashManagementDocument cmDoc) {
441 if (cmDoc == null) {
442 throw new IllegalArgumentException("invalid (null) CashManagementDocument");
443 }
444
445 // cancel each deposit (which also deletes the records connecting the Deposit to a CashManagementDoc
446 List deposits = cmDoc.getDeposits();
447 for (Iterator i = deposits.iterator(); i.hasNext();) {
448 Deposit deposit = (Deposit) i.next();
449
450 cancelDeposit(deposit);
451 }
452
453 // reclose the cashDrawer
454 String unitName = cmDoc.getCampusCode();
455 cashDrawerService.closeCashDrawer(cmDoc.getCashDrawer());
456
457 // cleanup the CMDoc, but let the postprocessor itself save it
458 cmDoc.setDeposits(new ArrayList());
459 cmDoc.getDocumentHeader().setFinancialDocumentStatusCode(DocumentStatusCodes.CANCELLED);
460
461 // kill off cumulative currency/coin detail records for this document (canceling the deposits kills the deposit records)
462 String[] cashieringSourcesToDelete = { KFSConstants.CurrencyCoinSources.CASH_RECEIPTS, CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_OUT };
463 for (String cashieringSourceToDelete : cashieringSourcesToDelete) {
464 CurrencyDetail currencyDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, cashieringSourceToDelete);
465 if (currencyDetail != null) {
466 businessObjectService.delete(currencyDetail);
467 }
468 CoinDetail coinDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, cashieringSourceToDelete);
469 if (coinDetail != null) {
470 businessObjectService.delete(coinDetail);
471 }
472 }
473 }
474
475
476 /**
477 * This method cancels a given deposit. This equates to the following:
478 * <ul>
479 * <li>Resetting all associated CashReceipts to a state of VERIFIED.</li>
480 * <li>Update all associated cashiering checks to a be un-deposited.</li>
481 * <li>Unlock the cash drawer if needed.</li>
482 * <li>Delete the deposit.</li>
483 * </ul>
484 *
485 * @see org.kuali.kfs.fp.document.service.CashManagementService#cancelDeposit(org.kuali.kfs.fp.businessobject.Deposit)
486 */
487 public void cancelDeposit(Deposit deposit) {
488 if (deposit == null) {
489 throw new IllegalArgumentException("invalid (null) deposit");
490 }
491
492 // reload it, to forestall OptimisticLockExceptions
493 deposit.refresh();
494
495 // save campus name, for possible later use
496 String depositCampus = deposit.getCashManagementDocument().getCampusCode();
497
498 // update every CashReceipt associated with this Deposit
499 List depositCashReceiptControls = deposit.getDepositCashReceiptControl();
500 for (Iterator j = depositCashReceiptControls.iterator(); j.hasNext();) {
501 DepositCashReceiptControl dcc = (DepositCashReceiptControl) j.next();
502 if (!ObjectUtils.isNull(dcc)) {
503 dcc.refreshReferenceObject("cashReceiptDocument");
504 CashReceiptDocument crDoc = dcc.getCashReceiptDocument();
505 if (!ObjectUtils.isNull(crDoc)) {
506 crDoc.refreshReferenceObject("documentHeader");
507 FinancialSystemDocumentHeader crdh = crDoc.getDocumentHeader();
508 if (!ObjectUtils.isNull(crdh)) {
509 crdh.setFinancialDocumentStatusCode(DocumentStatusCodes.CashReceipt.VERIFIED);
510 documentService.updateDocument(crDoc);
511 }
512 }
513 }
514 }
515
516 // un-deposit all cashiering checks associated with the deposit
517 List<Check> depositedChecks = selectCashieringChecksForDeposit(deposit.getDocumentNumber(), deposit.getFinancialDocumentDepositLineNumber());
518 for (Check check: depositedChecks) {
519 check.setFinancialDocumentDepositLineNumber(null);
520 }
521 businessObjectService.save(depositedChecks);
522
523 // unlock the cashDrawer, if needed
524 if (LOG.isDebugEnabled()) {
525 LOG.debug("deposit deposit type = "+deposit.getDepositTypeCode()+"; constant = "+KFSConstants.DepositConstants.DEPOSIT_TYPE_FINAL+"; are they equal? "+deposit.getDepositTypeCode().equals(KFSConstants.DepositConstants.DEPOSIT_TYPE_FINAL));
526 }
527 if (deposit.getDepositTypeCode().equals(KFSConstants.DepositConstants.DEPOSIT_TYPE_FINAL)) {
528 CashDrawer drawer = cashDrawerService.getByCampusCode(deposit.getCashManagementDocument().getCampusCode());
529 CurrencyDetail currencyDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(deposit.getCashManagementDocument().getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
530 if (currencyDetail != null) {
531 drawer.addCurrency(currencyDetail);
532 businessObjectService.delete(currencyDetail);
533 }
534 CoinDetail coinDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(deposit.getCashManagementDocument().getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
535 if (coinDetail != null) {
536 drawer.addCoin(coinDetail);
537 businessObjectService.delete(coinDetail);
538 }
539 businessObjectService.save(drawer);
540 cashDrawerService.unlockCashDrawer(drawer, deposit.getDocumentNumber());
541 }
542
543 // delete the Deposit from the database
544 businessObjectService.delete(deposit);
545 }
546
547 /**
548 * This method performs the necessary steps to finalize a cash management document. These steps include:
549 * <ul>
550 * <li>Finalize all associated cash receipts.
551 * <li>Generate the master currency and coin details and persist them.
552 * <li>Update the CashManagementDocument status to APPROVED.
553 * </ul>
554 *
555 * <br>
556 * NOTE: Method should only be called after the appropriate CashManagementDocumentRule has been successfully passed
557 *
558 * @param cmDoc The CashManagementDocument to be finalized.
559 *
560 * @see org.kuali.kfs.fp.document.service.CashManagementService#finalizeCashManagementDocument(org.kuali.kfs.fp.document.CashManagementDocument)
561 */
562 public void finalizeCashManagementDocument(CashManagementDocument cmDoc) {
563 if (cmDoc == null) {
564 throw new IllegalArgumentException("invalid (null) CashManagementDocument");
565 }
566 if (!cmDoc.hasFinalDeposit()) {
567 throw new IllegalStateException("cmDoc " + cmDoc.getDocumentNumber() + " is missing a FinalDeposit");
568 }
569
570 String campusCode = cmDoc.getCampusCode();
571 cashDrawerService.closeCashDrawer(campusCode);
572 CashDrawer cd = cashDrawerService.getByCampusCode(campusCode);
573
574
575 // finalize the CashReceipts
576 List<Deposit> deposits = cmDoc.getDeposits();
577 for (Deposit deposit : deposits) {
578 List<CashReceiptDocument> receipts = retrieveCashReceipts(deposit);
579 for (CashReceiptDocument receipt : receipts) {
580 // marks GLPEs of CRs as APPROVED
581 for (GeneralLedgerPendingEntry glpe : receipt.getGeneralLedgerPendingEntries()) {
582 glpe.setFinancialDocumentApprovedCode(DocumentStatusCodes.APPROVED);
583 }
584
585 // mark CRs themselves as APPROVED
586 receipt.getDocumentHeader().setFinancialDocumentStatusCode(DocumentStatusCodes.APPROVED);
587
588 // persist
589 documentService.updateDocument(receipt);
590 }
591 }
592
593 // generate the master currency and coin details; save those
594 CurrencyDetail masterCurrencyDetail = this.generateMasterCurrencyDetail(cmDoc);
595 businessObjectService.save(masterCurrencyDetail);
596 CoinDetail masterCoinDetail = this.generateMasterCoinDetail(cmDoc);
597 businessObjectService.save(masterCoinDetail);
598
599 // finalize the CMDoc, but let the postprocessor save it
600 cmDoc.getDocumentHeader().setFinancialDocumentStatusCode(DocumentStatusCodes.APPROVED);
601 }
602
603 /**
604 * This method verifies that all cash receipts for the document are deposited.
605 *
606 * @param cmDoc The cash management document to verify.
607 * @return True if all CashReceipts are deposited, false otherwise.
608 */
609 public boolean allVerifiedCashReceiptsAreDeposited(CashManagementDocument cmDoc) {
610 boolean result = true;
611 List verifiedReceipts = SpringContext.getBean(CashReceiptService.class).getCashReceipts(cmDoc.getCampusCode(), KFSConstants.DocumentStatusCodes.CashReceipt.VERIFIED);
612 for (Object o: verifiedReceipts) {
613 if (!verifyCashReceiptIsDeposited(cmDoc, (CashReceiptDocument)o)) {
614 result = false;
615 break;
616 }
617 }
618 return result;
619 }
620
621 /**
622 * This method returns a collection of cash receipts associated with the deposit given.
623 *
624 * @param deposit The deposit to retrieve all the cash receipts for.
625 * @return A collection of cash receipts associated with the deposit given.
626 *
627 * @see org.kuali.kfs.fp.document.service.CashManagementService#retrieveCashReceipts(org.kuali.kfs.fp.businessobject.Deposit)
628 */
629 public List<CashReceiptDocument> retrieveCashReceipts(Deposit deposit) {
630 List<CashReceiptDocument> cashReceiptDocuments = new ArrayList<CashReceiptDocument>();
631
632 if (ObjectUtils.isNull(deposit.getDepositCashReceiptControl())) {
633 deposit.refreshReferenceObject("depositCashReceiptControl");
634 }
635
636 Map pkMap = new HashMap();
637 for (Object dcrcAsObject : deposit.getDepositCashReceiptControl()) {
638 final DepositCashReceiptControl dcrc = (DepositCashReceiptControl)dcrcAsObject;
639 try {
640 CashReceiptDocument crDoc = (CashReceiptDocument)documentService.getByDocumentHeaderId(dcrc.getFinancialDocumentCashReceiptNumber());
641 final KualiWorkflowDocument headerWorkflowDoc = crDoc.getDocumentHeader().getWorkflowDocument();
642 crDoc.refreshReferenceObject("documentHeader");
643 crDoc.getDocumentHeader().setWorkflowDocument(headerWorkflowDoc);
644 cashReceiptDocuments.add(crDoc);
645 }
646 catch (WorkflowException we) {
647 throw new RuntimeException("Could not retrieve related Cash Receipts", we);
648 }
649 }
650
651 return cashReceiptDocuments;
652 }
653
654 /**
655 * Verifies if a given cash receipt is deposited as part of the given cash management document.
656 *
657 * @param cmDoc The cash management document to search through.
658 * @param crDoc The cash receipt to check the deposited status of.
659 * @return True if the given cash receipt document is deposited as part of the given cash management document, false otherwise.
660 */
661 public boolean verifyCashReceiptIsDeposited(CashManagementDocument cmDoc, CashReceiptDocument crDoc) {
662 boolean thisCRDeposited = false;
663 for (Deposit deposit: cmDoc.getDeposits()) {
664 if (deposit.containsCashReceipt(crDoc)) {
665 thisCRDeposited = true;
666 break;
667 }
668 }
669 return thisCRDeposited;
670 }
671
672 /**
673 * This method turns the last interim deposit into the final deposit and locks the cash drawer. A deposit is turned into
674 * a final deposit by updating the deposit type code.
675 * <br>
676 * NOTE: This method throws an IllegalStateException if a final deposit already exists for this document or if there
677 * are any undeposited cash receipts.
678 *
679 * @param cmDoc The cash management document to take deposits from for finalization.
680 */
681 public void finalizeLastInterimDeposit(CashManagementDocument cmDoc) {
682 // if there's already a final deposit, throw an IllegalStateException
683 if (cmDoc.hasFinalDeposit()) {
684 throw new IllegalStateException("CashManagementDocument #"+cmDoc.getDocumentNumber()+" already has a final deposit");
685 }
686 // if there are still verified un-deposited cash receipts, throw an IllegalStateException
687 List verifiedReceipts = SpringContext.getBean(CashReceiptService.class).getCashReceipts(cmDoc.getCampusCode(), KFSConstants.DocumentStatusCodes.CashReceipt.VERIFIED);
688 for (Object o: verifiedReceipts) {
689 CashReceiptDocument crDoc = (CashReceiptDocument)o;
690 if (!verifyCashReceiptIsDeposited(cmDoc, crDoc)) {
691 throw new IllegalStateException("Verified Cash Receipt Document #"+crDoc.getDocumentNumber()+" must be deposited for this to be a final deposit");
692 }
693 }
694 // lock the cash drawer
695 cashDrawerService.lockCashDrawer(cmDoc.getCashDrawer(), cmDoc.getDocumentNumber());
696
697 // change the deposit type code for the last deposit
698 List<Deposit> allDeposits = cmDoc.getDeposits();
699 Deposit lastInterim = allDeposits.get(allDeposits.size() - 1);
700 lastInterim.setDepositTypeCode(DepositConstants.DEPOSIT_TYPE_FINAL);
701 finalizeCashReceiptsForDeposit(lastInterim);
702 documentService.updateDocument(cmDoc);
703 }
704
705 /**
706 * This method switches cash receipts to "final" status as opposed to "interim" status.
707 *
708 * @param deposit The deposit the cash receipts are associated with.
709 */
710 protected void finalizeCashReceiptsForDeposit(Deposit deposit) {
711 List cashReceipts = this.retrieveCashReceipts(deposit);
712 for (Object o: cashReceipts) {
713 CashReceiptDocument crDoc = (CashReceiptDocument)o;
714 crDoc.getDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.CashReceipt.FINAL);
715 documentService.updateDocument(crDoc);
716 }
717 }
718
719 /**
720 * This method applies the cashiering transaction to the given CashManagementDocument. This is accomplished by
721 * retrieving a CashieringTransactionRule object and running the appropriate methods to process the cashiering
722 * application rules.
723 *
724 * @see org.kuali.kfs.fp.document.service.CashManagementService#applyCashieringTransaction(org.kuali.kfs.fp.document.CashManagementDocument, org.kuali.kfs.fp.businessobject.CashieringTransaction)
725 * @see org.kuali.kfs.fp.document.validation.impl.CashieringTransactionRule#processCashieringTransactionApplicationRules(CashManagementDocument)
726 */
727 public void applyCashieringTransaction(CashManagementDocument cmDoc) {
728 if (cmDoc.getCashDrawer() == null) {
729 cmDoc.setCashDrawer(cashDrawerService.getByCampusCode(cmDoc.getCampusCode()));
730 }
731
732 this.transferChecksToCashManagementDocument(cmDoc, cmDoc.getCurrentTransaction());
733 this.saveChecks(cmDoc);
734 this.completeNewItemInProcess(cmDoc.getCurrentTransaction());
735 if (cmDoc.getCurrentTransaction().getNewItemInProcess() != null) {
736 this.saveNewItemInProcess(cmDoc, cmDoc.getCurrentTransaction());
737 }
738 this.saveExisingItemsInProcess(cmDoc, cmDoc.getCurrentTransaction());
739 this.saveMoneyInCash(cmDoc, cmDoc.getCurrentTransaction());
740 this.saveMoneyOutCash(cmDoc, cmDoc.getCurrentTransaction());
741 this.updateCashDrawer(cmDoc.getCashDrawer(), cmDoc.getCurrentTransaction());
742 cmDoc.resetCurrentTransaction();
743 }
744
745 /**
746 * This method puts money from the "money in" portion of the transaction into the cash drawer, and takes money from the
747 * "money out" portion of the cash drawer out.
748 *
749 * @param drawer The cash drawer to operate on.
750 * @param trans The transaction that is the operation.
751 */
752 protected void updateCashDrawer(CashDrawer drawer, CashieringTransaction trans) {
753 // add money in to cash drawer
754 if (!trans.getMoneyInCurrency().isEmpty()) {
755 drawer.addCurrency(trans.getMoneyInCurrency());
756 }
757 if (!trans.getMoneyInCoin().isEmpty()) {
758 drawer.addCoin(trans.getMoneyInCoin());
759 }
760
761 // subtract money out from cash drawer
762 if (!trans.getMoneyOutCurrency().isEmpty()) {
763 drawer.removeCurrency(trans.getMoneyOutCurrency());
764 }
765 if (!trans.getMoneyOutCoin().isEmpty()) {
766 drawer.removeCoin(trans.getMoneyOutCoin());
767 }
768
769 businessObjectService.save(drawer);
770 }
771
772 /**
773 *
774 * This method completes the new item in process by setting the item remaining amount equal to the item amount.
775 *
776 * @param trans The transaction being performed.
777 */
778 protected void completeNewItemInProcess(CashieringTransaction trans) {
779 if (trans.getNewItemInProcess().isPopulated()) {
780 trans.getNewItemInProcess().setItemRemainingAmount(trans.getNewItemInProcess().getItemAmount());
781 }
782 else {
783 trans.setNewItemInProcess(null); // we don't want to save it or deal with it
784 }
785 }
786
787 /**
788 *
789 * This method retrieves all the checks for the given document and persists them.
790 *
791 * @param cmDoc The cash management document the checks will be saved against.
792 */
793 protected void saveChecks(CashManagementDocument cmDoc) {
794 if (cmDoc.getChecks() != null) {
795 for (Check check: cmDoc.getChecks()) {
796 check.setDocumentNumber(cmDoc.getDocumentNumber());
797 check.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
798 check.setCashieringRecordSource(KFSConstants.CheckSources.CASH_MANAGEMENT);
799 businessObjectService.save(check);
800 }
801 }
802 }
803
804 /**
805 *
806 * This method retrieves the checks from the transaction and adds them to the cash management document.
807 *
808 * @param cmDoc The document the checks will be transferred to.
809 * @param trans The transaction the checks are associated with.
810 */
811 protected void transferChecksToCashManagementDocument(CashManagementDocument cmDoc, CashieringTransaction trans) {
812 for (Check check: trans.getMoneyInChecks()) {
813 check.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
814 check.setCashieringRecordSource(KFSConstants.CheckSources.CASH_MANAGEMENT);
815 check.setDocumentNumber(cmDoc.getDocumentNumber());
816 cmDoc.addCheck(check);
817 }
818 }
819
820 /**
821 * This methods checks if data was actually entered for the new item in process; if so, it saves that item in process.
822 *
823 * @param cmDoc The cash management doc that the new item in process will be associated with.
824 * @param trans The cashiering transaction that created the new item in process.
825 */
826 protected void saveNewItemInProcess(CashManagementDocument cmDoc, CashieringTransaction trans) {
827 if (trans.getNewItemInProcess().isPopulated()) {
828 trans.getNewItemInProcess().setItemRemainingAmount(trans.getNewItemInProcess().getItemAmount());
829 trans.getNewItemInProcess().setItemReducedAmount(KualiDecimal.ZERO);
830 trans.getNewItemInProcess().setCampusCode(cmDoc.getCampusCode());
831 businessObjectService.save(trans.getNewItemInProcess());
832
833 // put it in the list of open items in process
834 trans.getOpenItemsInProcess().add(trans.getNewItemInProcess());
835
836 CashDrawer drawer = cmDoc.getCashDrawer();
837 if (drawer.getFinancialDocumentMiscellaneousAdvanceAmount() == null) {
838 drawer.setFinancialDocumentMiscellaneousAdvanceAmount(trans.getNewItemInProcess().getItemAmount());
839 }
840 else {
841 drawer.setFinancialDocumentMiscellaneousAdvanceAmount(drawer.getFinancialDocumentMiscellaneousAdvanceAmount().add(trans.getNewItemInProcess().getItemAmount()));
842 }
843 }
844 }
845
846 /**
847 * This method checks the cashiering transaction to see if any open items in process were at least partially paid back;
848 * it then saves the changes.
849 *
850 * @param cmDoc The cash management document that the items in process will be associated with
851 * @param trans The cashiering transaction the items in process are associated with.
852 */
853 protected void saveExisingItemsInProcess(CashManagementDocument cmDoc, CashieringTransaction trans) {
854 if (trans.getOpenItemsInProcess() != null) {
855 CashDrawer drawer = cmDoc.getCashDrawer();
856
857 for (CashieringItemInProcess itemInProc: trans.getOpenItemsInProcess()) {
858 if (itemInProc.getCurrentPayment() != null && !itemInProc.getCurrentPayment().equals(KualiDecimal.ZERO)) {
859 itemInProc.setItemRemainingAmount(itemInProc.getItemRemainingAmount().subtract(itemInProc.getCurrentPayment()));
860 itemInProc.setItemReducedAmount(itemInProc.getItemReducedAmount().add(itemInProc.getCurrentPayment()));
861 if (drawer.getFinancialDocumentMiscellaneousAdvanceAmount() != null) {
862 drawer.setFinancialDocumentMiscellaneousAdvanceAmount(drawer.getFinancialDocumentMiscellaneousAdvanceAmount().subtract(itemInProc.getCurrentPayment()));
863 }
864 itemInProc.setCurrentPayment(KualiDecimal.ZERO);
865 if (itemInProc.getItemRemainingAmount().equals(KualiDecimal.ZERO)) {
866 itemInProc.setItemClosedDate(new java.sql.Date(SpringContext.getBean(DateTimeService.class).getCurrentDate().getTime()));
867 }
868 businessObjectService.save(itemInProc);
869 }
870 }
871 }
872 }
873
874 /**
875 *
876 * This method retrieves the amount of cash in the "money in" portion of the transaction and saves it to the
877 * cash management document.
878 *
879 * @param cmDoc The cash management document that the cash will be saved to.
880 * @param trans The cashiering transaction the cash is currently associated with.
881 */
882 protected void saveMoneyInCash(CashManagementDocument cmDoc, CashieringTransaction trans) {
883 // get the cumulative money in coin for this doc
884 CoinDetail cumulativeMoneyInCoin = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN);
885 // add the new money in coin
886 cumulativeMoneyInCoin.add(trans.getMoneyInCoin());
887 // save the cumulative
888 businessObjectService.save(cumulativeMoneyInCoin);
889
890 CurrencyDetail cumulativeMoneyInCurrency = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN);
891 cumulativeMoneyInCurrency.add(trans.getMoneyInCurrency());
892 businessObjectService.save(cumulativeMoneyInCurrency);
893 }
894
895 /**
896 *
897 * This method retrieves the amount of cash in the "money out" portion of the transaction and saves it to the
898 * cash management document.
899 *
900 * @param cmDoc The cash management document that the cash will be saved to.
901 * @param trans The cashiering transaction the cash is currently associated with.
902 */
903 protected void saveMoneyOutCash(CashManagementDocument cmDoc, CashieringTransaction trans) {
904 CoinDetail cumulativeMoneyOutCoin = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_OUT);
905 cumulativeMoneyOutCoin.add(trans.getMoneyOutCoin());
906 businessObjectService.save(cumulativeMoneyOutCoin);
907
908 CurrencyDetail cumulativeMoneyOutCurrency = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_OUT);
909 cumulativeMoneyOutCurrency.add(trans.getMoneyOutCurrency());
910 businessObjectService.save(cumulativeMoneyOutCurrency);
911 }
912
913 /**
914 * This method retrieves a collection of open CashieringItemInProcess objects from the cash management document given
915 * and returns that collection.
916 *
917 * @param cmDoc The document the open items in process will be retrieved from.
918 * @return The collection of open items.
919 *
920 * @see org.kuali.kfs.fp.document.service.CashManagementService#getOpenItemsInProcess(org.kuali.kfs.fp.document.CashManagementDocument)
921 */
922 public List<CashieringItemInProcess> getOpenItemsInProcess(CashManagementDocument cmDoc) {
923 List<CashieringItemInProcess> itemsInProcess = cashManagementDao.findOpenItemsInProcessByCampusCode(cmDoc.getCampusCode());
924 return (itemsInProcess == null) ? new ArrayList<CashieringItemInProcess>() : itemsInProcess;
925 }
926
927 /**
928 * This method retrieves a collection of recently closed CashieringItemInProcess objects from the cash management
929 * document given and returns the collection.
930 *
931 * @param cmDoc The cash management document the recently closed items will be retrieved from.
932 * @return The collection of recently closed items.
933 *
934 * @see org.kuali.kfs.fp.document.service.CashManagementService#getRecentlyClosedItemsInProcess(org.kuali.kfs.fp.document.CashManagementDocument)
935 */
936 public List<CashieringItemInProcess> getRecentlyClosedItemsInProcess(CashManagementDocument cmDoc) {
937 return cashManagementDao.findRecentlyClosedItemsInProcess(cmDoc.getCampusCode());
938 }
939
940 /**
941 * This method generates a master coin detail for the cash management document given. A master coin detail is a CoinDetail
942 * that represents the result of all the money in and out of the cash drawer via the given cash management document. The
943 * following formula is used to perform this calculation:
944 * <ul>
945 * <li>
946 * "coin detail for cash receipt - coin detail for deposits + coin detail for money in - coin detail for money out"
947 * </li>
948 * </ul>
949 *
950 * @param cmDoc The document the master coin detail will be generated from.
951 * @return The resulting coin detail.
952 *
953 * @see org.kuali.kfs.fp.document.service.CashManagementService#generateMasterCoinDetail(org.kuali.kfs.fp.document.CashManagementDocument)
954 */
955 public CoinDetail generateMasterCoinDetail(CashManagementDocument cmDoc) {
956 CoinDetail masterDetail = new CoinDetail();
957 masterDetail.setDocumentNumber(cmDoc.getDocumentNumber());
958 masterDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
959 masterDetail.setCashieringRecordSource(KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_MASTER);
960
961 masterDetail.zeroOutAmounts();
962
963 CoinDetail cashReceiptDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_RECEIPTS);
964 if (cashReceiptDetail != null) {
965 masterDetail.add(cashReceiptDetail);
966 }
967
968 CoinDetail depositDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
969 if (depositDetail != null) {
970 masterDetail.subtract(depositDetail);
971 }
972
973 CoinDetail moneyInDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN);
974 if (moneyInDetail != null) {
975 masterDetail.add(moneyInDetail);
976 }
977
978 CoinDetail moneyOutDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_OUT);
979 if (moneyOutDetail != null) {
980 masterDetail.subtract(moneyOutDetail);
981 }
982
983 return masterDetail;
984 }
985
986
987 /**
988 * This method generates a master currency detail for the cash management document given. A master currency detail is a currencyDetail
989 * that represents the result of all the money in and out of the cash drawer via the given cash management document. The
990 * following formula is used to perform this calculation:
991 * <ul>
992 * <li>
993 * "currency detail for cash receipt - currency detail for deposits + currency detail for money in - currency detail for money out"
994 * </li>
995 * </ul>
996 *
997 * @param cmDoc The document the master currency detail will be generated from.
998 * @return The resulting currency detail.
999 *
1000 * @see org.kuali.kfs.fp.document.service.CashManagementService#generateMasterCurrencyDetail(org.kuali.kfs.fp.document.CashManagementDocument)
1001 */
1002 public CurrencyDetail generateMasterCurrencyDetail(CashManagementDocument cmDoc) {
1003 CurrencyDetail masterDetail = new CurrencyDetail();
1004 masterDetail.setDocumentNumber(cmDoc.getDocumentNumber());
1005 masterDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
1006 masterDetail.setCashieringRecordSource(KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_MASTER);
1007
1008 masterDetail.zeroOutAmounts();
1009
1010 CurrencyDetail cashReceiptDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_RECEIPTS);
1011 if (cashReceiptDetail != null) {
1012 masterDetail.add(cashReceiptDetail);
1013 }
1014
1015 CurrencyDetail depositDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
1016 if (depositDetail != null) {
1017 masterDetail.subtract(depositDetail);
1018 }
1019
1020 CurrencyDetail moneyInDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN);
1021 if (moneyInDetail != null) {
1022 masterDetail.add(moneyInDetail);
1023 }
1024
1025 CurrencyDetail moneyOutDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_OUT);
1026 if (moneyOutDetail != null) {
1027 masterDetail.subtract(moneyOutDetail);
1028 }
1029
1030 return masterDetail;
1031 }
1032
1033 /**
1034 * Populates the currency and coin detail for final deposits by setting the deposited currency or coin amount equal to the
1035 * associated cashiering record currency or coin amount.
1036 *
1037 * @param cmDoc The cash management document which has deposits to populate.
1038 */
1039 public void populateCashDetailsForDeposit(CashManagementDocument cmDoc) {
1040 // if this ever gets changed so that each deposit has currency/coin lines, then
1041 // we can just do this with the ORM, which would be *much* easier
1042 for (Deposit d: cmDoc.getDeposits()) {
1043 if (d.getDepositTypeCode().equals(DepositConstants.DEPOSIT_TYPE_FINAL)) {
1044 if (d.getDepositedCurrency() == null) {
1045 d.setDepositedCurrency(cashManagementDao.findCurrencyDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, CurrencyCoinSources.DEPOSITS));
1046 }
1047 if (d.getDepositedCoin() == null) {
1048 d.setDepositedCoin(cashManagementDao.findCoinDetailByCashieringRecordSource(cmDoc.getDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, CurrencyCoinSources.DEPOSITS));
1049 }
1050 }
1051 }
1052 }
1053
1054 /**
1055 * This method retrieves the collection of cashiering checks associated with a given deposit.
1056 *
1057 * @param documentNumber The id of the document to search for the deposit within.
1058 * @param depositLineNumber The line number of the deposit to be found.
1059 * @return A collection of checks for the deposit and document given.
1060 *
1061 * @see org.kuali.kfs.fp.document.service.CashManagementService#selectCashieringChecksForDeposit(java.lang.String, java.lang.Integer)
1062 */
1063 public List<Check> selectCashieringChecksForDeposit(String documentNumber, Integer depositLineNumber) {
1064 return cashManagementDao.selectCashieringChecksForDeposit(documentNumber, depositLineNumber);
1065 }
1066
1067 /**
1068 * This method retrieves the collection of undeposited cashiering checks associated with the document given.
1069 *
1070 * @param documentNumber The id of the document to search for the undeposited checks within.
1071 * @return A collection of any undeposited checks for the document given.
1072 *
1073 * @see org.kuali.kfs.fp.document.service.CashManagementService#selectUndepositedCashieringChecks(java.lang.String)
1074 */
1075 public List<Check> selectUndepositedCashieringChecks(String documentNumber) {
1076 return cashManagementDao.selectUndepositedCashieringChecks(documentNumber);
1077 }
1078
1079 /**
1080 * This method retrieves a collection of all deposited checks associated with the given document.
1081 *
1082 * @param documentNumber The document to retrieve the deposited checks from.
1083 * @return A collection of all deposited checks for the document given.
1084 *
1085 * @see org.kuali.kfs.fp.document.service.CashManagementService#selectDepositedCashieringChecks(java.lang.String)
1086 */
1087 public List<Check> selectDepositedCashieringChecks(String documentNumber) {
1088 return cashManagementDao.selectDepositedCashieringChecks(documentNumber);
1089 }
1090
1091
1092 /**
1093 * Total up the amounts of all checks so far deposited as part of the given cash management document.
1094 *
1095 * @param documentNumber The id of a cash management document.
1096 * @return The total amount of cashiering checks deposited so far as part of that document.
1097 */
1098 public KualiDecimal calculateDepositedCheckTotal(String documentNumber) {
1099 KualiDecimal total = KualiDecimal.ZERO;
1100 for (Check check: cashManagementDao.selectDepositedCashieringChecks(documentNumber)) {
1101 if (check != null && check.getAmount() != null && check.getAmount().isGreaterThan(KualiDecimal.ZERO)) {
1102 total = total.add(check.getAmount());
1103 }
1104 }
1105 return total;
1106 }
1107
1108 /**
1109 * Calculates the total amount of all the undeposited checks for a cash management document.
1110 *
1111 * @param documentNumber The id of the cash management document to pull the undeposited checks from.
1112 * @return The total amount of all undeposited checks for the document given.
1113 *
1114 * @see org.kuali.kfs.fp.document.service.CashManagementService#calculateUndepositedCheckTotal(java.lang.String)
1115 */
1116 public KualiDecimal calculateUndepositedCheckTotal(String documentNumber) {
1117 KualiDecimal total = KualiDecimal.ZERO;
1118 for (Check check: cashManagementDao.selectUndepositedCashieringChecks(documentNumber)) {
1119 if (check != null && check.getAmount() != null && check.getAmount().isGreaterThan(KualiDecimal.ZERO)) {
1120 total = total.add(check.getAmount());
1121 }
1122 }
1123 return total;
1124 }
1125
1126
1127 /**
1128 * This method determines if a document can be cancelled, by reviewing a set of criteria:
1129 * - do any cash receipts exist in this document?
1130 * - do any cashiering checks exist in this document?
1131 * - do any cash details exist in this document?
1132 * If any of these questions comes back as true, then the document cannot be canceled.
1133 *
1134 * @param cmDoc The document that would be canceled.
1135 * @return True if the document can be canceled, false otherwise.
1136 *
1137 * @see org.kuali.kfs.fp.document.service.CashManagementService#allowDocumentCancellation(org.kuali.kfs.fp.document.CashManagementDocument)
1138 */
1139 public boolean allowDocumentCancellation(CashManagementDocument cmDoc) {
1140 return !existCashReceipts(cmDoc) && !existCashieringChecks(cmDoc) && !existCashDetails(cmDoc);
1141 }
1142
1143 /**
1144 * This method determines if any verified, interim, or final cash receipts currently exist.
1145 *
1146 * @param cmDoc The cash management document to find cash receipts associated with the campus of.
1147 * @return True if there's some cash receipts that verified, interim, or final in this campus; false if otherwise.
1148 */
1149 protected boolean existCashReceipts(CashManagementDocument cmDoc) {
1150 List<CashReceiptDocument> cashReceipts = SpringContext.getBean(CashReceiptService.class).getCashReceipts(cmDoc.getCampusCode(), new String[] {KFSConstants.DocumentStatusCodes.CashReceipt.VERIFIED, KFSConstants.DocumentStatusCodes.CashReceipt.INTERIM, KFSConstants.DocumentStatusCodes.CashReceipt.FINAL} );
1151 return cashReceipts != null && cashReceipts.size() > 0;
1152 }
1153
1154 /**
1155 * This method determines if any populated currency or coin details exist for the given document.
1156 *
1157 * @param cmDoc A cash management document to find details.
1158 * @return True if it finds populated currency or coin details, false if otherwise.
1159 */
1160 protected boolean existCashDetails(CashManagementDocument cmDoc) {
1161 boolean result = false;
1162 List<CurrencyDetail> currencyDetails = cashManagementDao.getAllCurrencyDetails(cmDoc.getDocumentNumber());
1163 if (currencyDetails != null && currencyDetails.size() > 0) {
1164 for (CurrencyDetail detail: currencyDetails) {
1165 result |= !detail.isEmpty();
1166 }
1167 }
1168 if (!result) {
1169 List<CoinDetail> coinDetails = cashManagementDao.getAllCoinDetails(cmDoc.getDocumentNumber());
1170 if (coinDetails != null && coinDetails.size() > 0) {
1171 for (CoinDetail detail: coinDetails) {
1172 result |= !detail.isEmpty();
1173 }
1174 }
1175 }
1176 return result;
1177 }
1178
1179 /**
1180 * This method determines if cashiering checks exist for the cash management document.
1181 *
1182 * @param cmDoc The cash management document to test.
1183 * @return True if it finds some checks, false if otherwise.
1184 */
1185 protected boolean existCashieringChecks(CashManagementDocument cmDoc) {
1186 List<Check> undepositedChecks = this.selectUndepositedCashieringChecks(cmDoc.getDocumentNumber());
1187 List<Check> depositedChecks = cashManagementDao.selectDepositedCashieringChecks(cmDoc.getDocumentNumber());
1188 return (undepositedChecks != null && undepositedChecks.size() > 0) || (depositedChecks != null && depositedChecks.size() > 0);
1189 }
1190
1191 /**
1192 * This method retrieves the next available check line number from the document provided.
1193 *
1194 * @param documentNumber The document to get the next check line number from.
1195 * @return The next available check line number.
1196 *
1197 * @see org.kuali.kfs.fp.document.service.CashManagementService#selectNextAvailableCheckLineNumber(java.lang.String)
1198 */
1199 public Integer selectNextAvailableCheckLineNumber(String documentNumber) {
1200 return cashManagementDao.selectNextAvailableCheckLineNumber(documentNumber);
1201 }
1202
1203 /**
1204 * This method retrieves the cash details for the final deposit object. The resulting map contains a CurrencyDetail and a
1205 * CoinDetail object, both keyed by the class of detail they represent (ie. CurrencyDetail.class is the map key for the
1206 * CurrencyDetail of the document).
1207 *
1208 * @param documentNumber The document the details will be generated from.
1209 * @return A map of the resulting cash details. This map is keyed by the detail class object.
1210 *
1211 * @see org.kuali.kfs.fp.document.service.CashManagementService#getCashDetailsForFinalDeposit(java.lang.String)
1212 */
1213 public Map<Class, Object> getCashDetailsForFinalDeposit(String documentNumber) {
1214 CurrencyDetail finalDepositCurrencyDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(documentNumber, CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
1215 CoinDetail finalDepositCoinDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(documentNumber, CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.DEPOSITS);
1216 Map<Class, Object> result = new HashMap<Class, Object>();
1217 if (finalDepositCurrencyDetail != null) {
1218 result.put(CurrencyDetail.class, finalDepositCurrencyDetail);
1219 }
1220 if (finalDepositCoinDetail != null) {
1221 result.put(CoinDetail.class, finalDepositCoinDetail);
1222 }
1223 return result;
1224 }
1225
1226 // injected dependencies
1227 /**
1228 * Getter for retrieving an instance of the BusinessObjectService attribute.
1229 *
1230 * @return Current value of businessObjectService.
1231 */
1232 public BusinessObjectService getBusinessObjectService() {
1233 return businessObjectService;
1234 }
1235
1236 /**
1237 * Sets the businessObjectService attribute value.
1238 *
1239 * @param businessObjectService The businessObjectService to set.
1240 */
1241 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1242 this.businessObjectService = businessObjectService;
1243 }
1244
1245 /**
1246 * Getter for retrieving an instance of the CashDrawerService attribute.
1247 *
1248 * @return Current value of cashDrawerService.
1249 */
1250 public CashDrawerService getCashDrawerService() {
1251 return cashDrawerService;
1252 }
1253
1254 /**
1255 * Sets the cashDrawerService attribute value.
1256 *
1257 * @param cashDrawerService The cashDrawerService to set.
1258 */
1259 public void setCashDrawerService(CashDrawerService cashDrawerService) {
1260 this.cashDrawerService = cashDrawerService;
1261 }
1262
1263 /**
1264 * Gets the documentService attribute.
1265 *
1266 * @return Current value of documentService.
1267 */
1268 public DocumentService getDocumentService() {
1269 return documentService;
1270 }
1271
1272 /**
1273 * Sets the documentService attribute value.
1274 *
1275 * @param documentService
1276 */
1277 public void setDocumentService(DocumentService documentService) {
1278 this.documentService = documentService;
1279 }
1280
1281 /**
1282 * Gets the dateTimeService attribute.
1283 *
1284 * @return Current value of dateTimeService.
1285 */
1286 public DateTimeService getDateTimeService() {
1287 return dateTimeService;
1288 }
1289
1290 /**
1291 * Sets the dateTimeService attribute value.
1292 *
1293 * @param dateTimeService The dateTimeService to set.
1294 */
1295 public void setDateTimeService(DateTimeService dateTimeService) {
1296 this.dateTimeService = dateTimeService;
1297 }
1298
1299 /**
1300 * Gets the cashManagementDao attribute.
1301 *
1302 * @return Returns the cashManagementDao.
1303 */
1304 public CashManagementDao getCashManagementDao() {
1305 return cashManagementDao;
1306 }
1307
1308 /**
1309 * Sets the cashManagementDao attribute value.
1310 *
1311 * @param cashManagementDao The cashManagementDao to set.
1312 */
1313 public void setCashManagementDao(CashManagementDao cashManagementDao) {
1314 this.cashManagementDao = cashManagementDao;
1315 }
1316
1317
1318 /**
1319 * @return an implementation of the DataDictionaryService
1320 */
1321 public DataDictionaryService getDataDictionaryService() {
1322 return dataDictionaryService;
1323 }
1324
1325 /**
1326 * Sets the data dictionary service implementation
1327 * @param dataDictionaryService the implementation of the data dictionary service to use
1328 */
1329 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1330 this.dataDictionaryService = dataDictionaryService;
1331 }
1332
1333 }
1334