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 static org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.ERROR_PATH.DOCUMENT_ERROR_PREFIX;
019    
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.kuali.kfs.fp.businessobject.CashDrawer;
029    import org.kuali.kfs.fp.businessobject.CashieringTransaction;
030    import org.kuali.kfs.fp.businessobject.CoinDetail;
031    import org.kuali.kfs.fp.businessobject.CurrencyDetail;
032    import org.kuali.kfs.fp.document.CashReceiptDocument;
033    import org.kuali.kfs.fp.document.CashReceiptFamilyBase;
034    import org.kuali.kfs.fp.document.dataaccess.CashManagementDao;
035    import org.kuali.kfs.fp.document.service.CashReceiptService;
036    import org.kuali.kfs.fp.service.CashDrawerService;
037    import org.kuali.kfs.sys.KFSConstants;
038    import org.kuali.kfs.sys.KFSKeyConstants;
039    import org.kuali.kfs.sys.KFSPropertyConstants;
040    import org.kuali.kfs.sys.KFSKeyConstants.CashReceipt;
041    import org.kuali.kfs.sys.context.SpringContext;
042    import org.kuali.rice.kew.exception.WorkflowException;
043    import org.kuali.rice.kim.bo.Person;
044    import org.kuali.rice.kns.bo.Campus;
045    import org.kuali.rice.kns.bo.CampusImpl;
046    import org.kuali.rice.kns.bo.DocumentHeader;
047    import org.kuali.rice.kns.exception.InfrastructureException;
048    import org.kuali.rice.kns.service.BusinessObjectService;
049    import org.kuali.rice.kns.service.DataDictionaryService;
050    import org.kuali.rice.kns.service.DictionaryValidationService;
051    import org.kuali.rice.kns.service.KualiModuleService;
052    import org.kuali.rice.kns.service.ParameterService;
053    import org.kuali.rice.kns.util.GlobalVariables;
054    import org.kuali.rice.kns.util.KualiDecimal;
055    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
056    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
057    import org.springframework.transaction.annotation.Transactional;
058    
059    /**
060     * 
061     * This is the default implementation of the CashReceiptService interface.
062     */
063    @Transactional
064    public class CashReceiptServiceImpl implements CashReceiptService {
065    
066        private BusinessObjectService businessObjectService;
067        private WorkflowDocumentService workflowDocumentService;
068        private CashManagementDao cashManagementDao;
069        private CashDrawerService cashDrawerService;
070        private ParameterService parameterService;
071        private DictionaryValidationService dictionaryValidationService;
072    
073        /**
074         * This method verifies the campus code provided exists.  This is done by retrieving all the available campuses from
075         * the BusinessObjectService and then looking for a matching campus code within the result set.
076         * 
077         * @param campusCode The campus code to be verified.
078         * @return True if the campus code provided is valid and exists, false otherwise.
079         */
080        protected boolean verifyCampus(String campusCode) {
081            List campusList = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(
082                    Campus.class).getExternalizableBusinessObjectsList(Campus.class, new HashMap<String, Object>());
083            Iterator campiiIter = campusList.iterator();
084            boolean foundCampus = false;
085            while (campiiIter.hasNext() && !foundCampus) {
086                Campus campus = (Campus)campiiIter.next();
087                if (campus.getCampusCode().equals(campusCode)) {
088                    foundCampus = true;
089                }
090            }
091            return foundCampus;
092            
093        }
094    
095    
096        /**
097         * This method retrieves the cash receipt verification unit based on the user provided.  This is done by retrieving the campus
098         * code associated with the user provided and then performing the lookup using this campus code.
099         * 
100         * @param user The user to be used to retrieve the verification unit.
101         * @return The cash receipt verification unit associated with the user provided.
102         * 
103         * @see org.kuali.kfs.fp.document.service.CashReceiptService#getCashReceiptVerificationUnit(org.kuali.rice.kns.bo.user.KualiUser)
104         */
105        public String getCashReceiptVerificationUnitForUser(Person user) {
106            String unitName = null;
107    
108            if (user == null) {
109                throw new IllegalArgumentException("invalid (null) user");
110            }
111    
112            return user.getCampusCode();
113        }
114    
115    
116        /**
117         * This method retrieves a collection of cash receipts using the verification unit and the status provided to 
118         * retrieve the cash receipts.  
119         * 
120         * @param verificationUnit The verification unit used to retrieve a collection of associated cash receipts.
121         * @param statusCode The status code of the cash receipts to be retrieved.  
122         * @return A collection of cash receipt documents which match the search criteria provided.
123         * 
124         * @see org.kuali.kfs.fp.document.service.CashReceiptService#getCashReceipts(java.lang.String, java.lang.String)
125         */
126        public List getCashReceipts(String verificationUnit, String statusCode) {
127            if (StringUtils.isBlank(statusCode)) {
128                throw new IllegalArgumentException("invalid (blank) statusCode");
129            }
130    
131            String[] statii = new String[] { statusCode };
132            return getCashReceipts(verificationUnit, statii);
133        }
134    
135        /**
136         * This method retrieves a collection of cash receipts using the verification unit and the statuses provided to 
137         * retrieve the cash receipts.  
138         * 
139         * @param verificationUnit The verification unit used to retrieve a collection of associated cash receipts.
140         * @param statii A collection of possible statuses that will be used in the lookup of cash receipts.
141         * @return A collection of cash receipt documents which match the search criteria provided.
142         * 
143         * @see org.kuali.kfs.fp.document.service.CashReceiptService#getCashReceipts(java.lang.String, java.lang.String[])
144         */
145        public List getCashReceipts(String verificationUnit, String[] statii) {
146            if (StringUtils.isBlank(verificationUnit)) {
147                throw new IllegalArgumentException("invalid (blank) verificationUnit");
148            }
149            if (statii == null) {
150                throw new IllegalArgumentException("invalid (null) statii");
151            }
152            else {
153                if (statii.length == 0) {
154                    throw new IllegalArgumentException("invalid (empty) statii");
155                }
156                else {
157                    for (int i = 0; i < statii.length; ++i) {
158                        if (StringUtils.isBlank(statii[i])) {
159                            throw new IllegalArgumentException("invalid (blank) status code " + i);
160                        }
161                    }
162                }
163            }
164    
165            return getPopulatedCashReceipts(verificationUnit, statii);
166        }
167    
168        /**
169         * This method retrieves a populated collection of cash receipts using the lookup parameters provided.  A populated 
170         * cash receipt document is a cash receipt document with fully populated workflow fields.
171         * 
172         * @param verificationUnit The verification unit used to retrieve a collection of associated cash receipts.
173         * @param statii A collection of possible statuses that will be used in the lookup of the cash receipts.
174         * @return List of CashReceiptDocument instances with their associated workflowDocuments populated.
175         */
176        public List getPopulatedCashReceipts(String verificationUnit, String[] statii) {
177            Map queryCriteria = buildCashReceiptCriteriaMap(verificationUnit, statii);
178    
179            List documents = new ArrayList(getBusinessObjectService().findMatchingOrderBy(CashReceiptDocument.class, queryCriteria, KFSPropertyConstants.DOCUMENT_NUMBER, true));
180    
181            populateWorkflowFields(documents);
182    
183            return documents;
184        }
185    
186    
187        /**
188         * This method builds out a map of search criteria for performing cash receipt lookups using the values provided.
189         * 
190         * @param campusCode The campus code to use as search criteria for looking up cash receipts.
191         * @param statii A collection of possible statuses to use as search criteria for looking up cash receipts.
192         * @return The search criteria provided in a map with CashReceiptConstants used as keys to the parameters given.
193         */
194        protected Map buildCashReceiptCriteriaMap(String campusCode, String[] statii) {
195            Map queryCriteria = new HashMap();
196    
197            if (statii.length == 1) {
198                queryCriteria.put(KFSConstants.CashReceiptConstants.CASH_RECEIPT_DOC_HEADER_STATUS_CODE_PROPERTY_NAME, statii[0]);
199            }
200            else if (statii.length > 0) {
201                List<String> statusList = Arrays.asList(statii);
202                queryCriteria.put(KFSConstants.CashReceiptConstants.CASH_RECEIPT_DOC_HEADER_STATUS_CODE_PROPERTY_NAME, statusList);
203            }
204    
205            queryCriteria.put(KFSConstants.CashReceiptConstants.CASH_RECEIPT_CAMPUS_LOCATION_CODE_PROPERTY_NAME, campusCode);
206    
207            return queryCriteria;
208        }
209    
210        /**
211         * This method populates the workflowDocument field of each CashReceiptDocument in the given List
212         * 
213         * @param documents A collection of CashReceiptDocuments to be populated with workflow document data.
214         */
215        protected void populateWorkflowFields(List documents) {
216            for (Iterator i = documents.iterator(); i.hasNext();) {
217                CashReceiptDocument cr = (CashReceiptDocument) i.next();
218    
219                KualiWorkflowDocument workflowDocument = null;
220                DocumentHeader docHeader = cr.getDocumentHeader();
221                try {
222                    Long documentHeaderId = Long.valueOf(docHeader.getDocumentNumber());
223                    Person user = GlobalVariables.getUserSession().getPerson();
224    
225                    workflowDocument = getWorkflowDocumentService().createWorkflowDocument(documentHeaderId, user);
226                }
227                catch (WorkflowException e) {
228                    throw new InfrastructureException("unable to retrieve workflow document for documentHeaderId '" + docHeader.getDocumentNumber() + "'", e);
229                }
230    
231                docHeader.setWorkflowDocument(workflowDocument);
232            }
233        }
234    
235        /**
236         * This method retrieves the cash details from the cash receipt document provided and adds those details to the 
237         * associated cash drawer.  After the details are added to the drawer, the drawer is persisted to the database.
238         * 
239         * @param crDoc The cash receipt document the cash details will be retrieved from.
240         * 
241         * @see org.kuali.kfs.fp.document.service.CashReceiptService#addCashDetailsToCashDrawer(org.kuali.kfs.fp.document.CashReceiptDocument)
242         */
243        public void addCashDetailsToCashDrawer(CashReceiptDocument crDoc) {
244            CashDrawer drawer = retrieveCashDrawer(crDoc);
245            // we need to to add the currency and coin to the cash management doc's cumulative CR as well
246            if (crDoc.getCurrencyDetail() != null && !crDoc.getCurrencyDetail().isEmpty()) {
247                CurrencyDetail cumulativeCurrencyDetail = cashManagementDao.findCurrencyDetailByCashieringRecordSource(drawer.getReferenceFinancialDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_RECEIPTS);
248                cumulativeCurrencyDetail.add(crDoc.getCurrencyDetail());
249                businessObjectService.save(cumulativeCurrencyDetail);
250                
251                drawer.addCurrency(crDoc.getCurrencyDetail());
252            }
253            if (crDoc.getCoinDetail() != null && !crDoc.getCoinDetail().isEmpty()) {
254                CoinDetail cumulativeCoinDetail = cashManagementDao.findCoinDetailByCashieringRecordSource(drawer.getReferenceFinancialDocumentNumber(), CashieringTransaction.DETAIL_DOCUMENT_TYPE, KFSConstants.CurrencyCoinSources.CASH_RECEIPTS);
255                cumulativeCoinDetail.add(crDoc.getCoinDetail());
256                businessObjectService.save(cumulativeCoinDetail);
257                
258                drawer.addCoin(crDoc.getCoinDetail());
259            }
260            SpringContext.getBean(BusinessObjectService.class).save(drawer);
261        }
262        
263        /**
264         * This method finds the appropriate cash drawer for this cash receipt document to add cash to.
265         * 
266         * @param crDoc The document the cash drawer will be retrieved from.
267         * @return An instance of a cash drawer associated with the cash receipt document provided.
268         */
269        protected CashDrawer retrieveCashDrawer(CashReceiptDocument crDoc) {
270            String campusCode = crDoc.getCampusLocationCode();
271            if (campusCode == null) {
272                throw new RuntimeException("Cannot find workgroup name for Cash Receipt document: "+crDoc.getDocumentNumber());
273            }
274            
275            CashDrawer drawer = cashDrawerService.getByCampusCode(campusCode);
276            if (drawer == null) {
277                throw new RuntimeException("There is no Cash Drawer for Workgroup "+campusCode);
278            }
279            return drawer;
280        }
281        
282        /**
283         * @see org.kuali.module.financial.service.CashReceiptTotalsVerificationService#areCashTotalsInvalid(org.kuali.kfs.fp.document.CashReceiptDocument)
284         */
285        public boolean areCashTotalsInvalid(CashReceiptDocument cashReceiptDocument) {
286            String documentEntryName = cashReceiptDocument.getDocumentHeader().getWorkflowDocument().getDocumentType();
287    
288            boolean isInvalid = isTotalInvalid(cashReceiptDocument, cashReceiptDocument.getTotalCheckAmount(), documentEntryName, KFSPropertyConstants.TOTAL_CHECK_AMOUNT);
289            isInvalid |= isTotalInvalid(cashReceiptDocument, cashReceiptDocument.getTotalCashAmount(), documentEntryName, KFSPropertyConstants.TOTAL_CASH_AMOUNT);
290            isInvalid |= isTotalInvalid(cashReceiptDocument, cashReceiptDocument.getTotalCoinAmount(), documentEntryName, KFSPropertyConstants.TOTAL_COIN_AMOUNT);
291    
292            isInvalid |= isTotalInvalid(cashReceiptDocument, cashReceiptDocument.getTotalDollarAmount(), documentEntryName, KFSPropertyConstants.SUM_TOTAL_AMOUNT);
293    
294            return isInvalid;
295        }
296    
297        /**
298         * Puts an error message in the error map for that property if the amount is negative.
299         * 
300         * @param cashReceiptDocument submitted cash receipt document
301         * @param totalAmount total amount (cash total, check total, etc)
302         * @param documentEntryName document type
303         * @param propertyName property type (i.e totalCashAmount, totalCheckAmount, etc)
304         * @return true if the totalAmount is an invalid value
305         */
306        protected boolean isTotalInvalid(CashReceiptFamilyBase cashReceiptDocument, KualiDecimal totalAmount, String documentEntryName, String propertyName) {
307            boolean isInvalid = false;
308            String errorProperty = DOCUMENT_ERROR_PREFIX + propertyName;
309    
310            if (totalAmount != null) {
311                DataDictionaryService dds = SpringContext.getBean(DataDictionaryService.class);
312                String errorLabel = dds.getAttributeLabel(documentEntryName, propertyName);
313    
314                if (totalAmount.isNegative()) {
315                    GlobalVariables.getMessageMap().putError(errorProperty, CashReceipt.ERROR_NEGATIVE_TOTAL, errorLabel);
316    
317                    isInvalid = true;
318                }
319                else {
320                    int precount = GlobalVariables.getMessageMap().size();
321    
322                    getDictionaryValidationService().validateDocumentAttribute(cashReceiptDocument, propertyName, DOCUMENT_ERROR_PREFIX);
323    
324                    // replace generic error message, if any, with something more readable
325                    GlobalVariables.getMessageMap().replaceError(errorProperty, KFSKeyConstants.ERROR_MAX_LENGTH, CashReceipt.ERROR_EXCESSIVE_TOTAL, errorLabel);
326    
327                    int postcount = GlobalVariables.getMessageMap().size();
328                    isInvalid = (postcount > precount);
329                }
330            }
331    
332            return isInvalid;
333        }
334    
335        // injection-molding
336        /**
337         * Gets the businessObjectService attribute. 
338         * @return current value of businessObjectService.
339         */
340        public BusinessObjectService getBusinessObjectService() {
341            return businessObjectService;
342        }
343    
344        /**
345         * Sets the businessObjectService attribute value.
346         * @param businessObjectService The businessObjectService to set.
347         */
348        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
349            this.businessObjectService = businessObjectService;
350        }
351    
352    
353        /**
354         * Gets the workflowDocumentService attribute. 
355         * @return current value of workflowDocumentService.
356         */
357        public WorkflowDocumentService getWorkflowDocumentService() {
358            return workflowDocumentService;
359        }
360    
361        /**
362         * Sets the workflowDocumentService attribute value.
363         * @param workflowDocumentService The workflowDocumentService to set.
364         */
365        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
366            this.workflowDocumentService = workflowDocumentService;
367        }
368    
369        /**
370         * Gets the cashManagementDao attribute. 
371         * 
372         * @return Returns the cashManagementDao.
373         */
374        public CashManagementDao getCashManagementDao() {
375            return cashManagementDao;
376        }
377    
378    
379        /**
380         * Sets the cashManagementDao attribute value.
381         * 
382         * @param cashManagementDao The cashManagementDao to set.
383         */
384        public void setCashManagementDao(CashManagementDao cashManagementDao) {
385            this.cashManagementDao = cashManagementDao;
386        }
387    
388        /**
389         * Gets the cashDrawerService attribute. 
390         * 
391         * @return Returns the cashDrawerService.
392         */
393        public CashDrawerService getCashDrawerService() {
394            return cashDrawerService;
395        }
396    
397    
398        /**
399         * Sets the cashDrawerService attribute value.
400         * 
401         * @param cashDrawerService The cashDrawerService to set.
402         */
403        public void setCashDrawerService(CashDrawerService cashDrawerService) {
404            this.cashDrawerService = cashDrawerService;
405        }
406    
407        /**
408         * Gets the parameterService attribute. 
409         * 
410         * @return Returns the parameterService.
411         */
412        public ParameterService getParameterService() {
413            return parameterService;
414        }
415    
416    
417        /**
418         * Sets the parameterService attribute value.
419         * 
420         * @param parameterService The parameterService to set.
421         */
422        public void setParameterService(ParameterService parameterService) {
423            this.parameterService = parameterService;
424        }
425        
426        /**
427         * Gets the dictionaryValidationService attribute. 
428         * @return Returns the dictionaryValidationService.
429         */
430        public DictionaryValidationService getDictionaryValidationService() {
431            return dictionaryValidationService;
432        }
433    
434        /**
435         * Sets the dictionaryValidationService attribute value.
436         * @param dictionaryValidationService The dictionaryValidationService to set.
437         */
438        public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
439            this.dictionaryValidationService = dictionaryValidationService;
440        }
441    }
442