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