001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.ar.document.web.struts;
017    
018    import java.util.List;
019    
020    import javax.servlet.http.HttpServletRequest;
021    import javax.servlet.http.HttpServletResponse;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.apache.struts.action.ActionForm;
025    import org.apache.struts.action.ActionForward;
026    import org.apache.struts.action.ActionMapping;
027    import org.kuali.kfs.module.ar.ArConstants;
028    import org.kuali.kfs.module.ar.ArKeyConstants;
029    import org.kuali.kfs.module.ar.ArPropertyConstants;
030    import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader;
031    import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
032    import org.kuali.kfs.module.ar.document.CashControlDocument;
033    import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
034    import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService;
035    import org.kuali.kfs.module.ar.document.service.CashControlDocumentService;
036    import org.kuali.kfs.module.ar.document.validation.event.AddCashControlDetailEvent;
037    import org.kuali.kfs.sys.KFSConstants;
038    import org.kuali.kfs.sys.context.SpringContext;
039    import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase;
040    import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
041    import org.kuali.rice.kew.exception.WorkflowException;
042    import org.kuali.rice.kns.exception.UnknownDocumentIdException;
043    import org.kuali.rice.kns.rule.event.SaveDocumentEvent;
044    import org.kuali.rice.kns.service.BusinessObjectService;
045    import org.kuali.rice.kns.service.DocumentService;
046    import org.kuali.rice.kns.service.KualiConfigurationService;
047    import org.kuali.rice.kns.service.KualiRuleService;
048    import org.kuali.rice.kns.util.GlobalVariables;
049    import org.kuali.rice.kns.util.KualiDecimal;
050    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
051    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
052    
053    public class CashControlDocumentAction extends FinancialSystemTransactionalDocumentActionBase {
054    
055        /**
056         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
057         */
058        @Override
059        protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
060    
061            super.loadDocument(kualiDocumentFormBase);
062            CashControlDocumentForm ccForm = (CashControlDocumentForm) kualiDocumentFormBase;
063            CashControlDocument cashControlDocument = ccForm.getCashControlDocument();
064    
065            // now that the form has been originally loaded, we need to set a few Form variables that are used by
066            // JSP JSTL expressions because they are used directly and immediately upon initial form display
067            if (cashControlDocument != null && cashControlDocument.getCustomerPaymentMediumCode() != null) {
068                ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()));
069            }
070    
071            // get the PaymentApplicationDocuments by reference number
072            for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {
073                String docId = cashControlDetail.getReferenceFinancialDocumentNumber();
074                PaymentApplicationDocument doc = null;
075                doc = (PaymentApplicationDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(docId);
076                if (doc == null) {
077                    throw new UnknownDocumentIdException("Document no longer exists.  It may have been cancelled before being saved.");
078                }
079    
080                cashControlDetail.setReferenceFinancialDocument(doc);
081                KualiWorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
082                // KualiDocumentFormBase.populate() needs this updated in the session
083                GlobalVariables.getUserSession().setWorkflowDocument(workflowDoc);
084            }
085    
086        }
087    
088        /**
089         * Adds handling for cash control detail amount updates.
090         * 
091         * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
092         *      javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
093         */
094        @Override
095        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
096    
097            CashControlDocumentForm ccForm = (CashControlDocumentForm) form;
098            CashControlDocument ccDoc = ccForm.getCashControlDocument();
099    
100            if (ccDoc != null) {
101                ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(ccDoc.getCustomerPaymentMediumCode()));
102            }
103    
104            if (ccForm.hasDocumentId()) {
105                ccDoc = ccForm.getCashControlDocument();
106                ccDoc.refreshReferenceObject("customerPaymentMedium");
107                // recalc b/c changes to the amounts could have happened
108                ccDoc.recalculateTotals();
109            }
110    
111            return super.execute(mapping, form, request, response);
112        }
113    
114        /**
115         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
116         */
117        @Override
118        protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
119            super.createDocument(kualiDocumentFormBase);
120            CashControlDocumentForm form = (CashControlDocumentForm) kualiDocumentFormBase;
121            CashControlDocument document = form.getCashControlDocument();
122    
123            // set up the default values for the AR DOC Header (SHOULD PROBABLY MAKE THIS A SERVICE)
124            AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService = SpringContext.getBean(AccountsReceivableDocumentHeaderService.class);
125            AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = accountsReceivableDocumentHeaderService.getNewAccountsReceivableDocumentHeaderForCurrentUser();
126            accountsReceivableDocumentHeader.setDocumentNumber(document.getDocumentNumber());
127            document.setAccountsReceivableDocumentHeader(accountsReceivableDocumentHeader);
128    
129        }
130    
131        /**
132         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping,
133         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
134         */
135        @Override
136        public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
137    
138            CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
139            CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
140    
141            // If the cancel works, proceed to canceling the cash control doc
142            if (cancelLinkedPaymentApplicationDocuments(cashControlDocument)) {
143                return super.cancel(mapping, form, request, response);
144            }
145    
146            return mapping.findForward(KFSConstants.MAPPING_BASIC);
147        }
148    
149        /**
150         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#disapprove(org.apache.struts.action.ActionMapping,
151         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
152         */
153        @Override
154        public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
155            boolean success = true;
156            CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
157            CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
158    
159            success = cancelLinkedPaymentApplicationDocuments(cashControlDocument);
160    
161            if (!success) {
162                return mapping.findForward(KFSConstants.MAPPING_BASIC);
163            }
164    
165            return super.disapprove(mapping, form, request, response);
166        }
167    
168        /**
169         * This method cancels all linked Payment Application documents that are not already in approved status.
170         * 
171         * @param cashControlDocument
172         * @throws WorkflowException
173         */
174        protected boolean cancelLinkedPaymentApplicationDocuments(CashControlDocument cashControlDocument) throws WorkflowException {
175            boolean success = true;
176            List<CashControlDetail> details = cashControlDocument.getCashControlDetails();
177    
178            for (CashControlDetail cashControlDetail : details) {
179                DocumentService documentService = SpringContext.getBean(DocumentService.class);
180    
181                PaymentApplicationDocument applicationDocument = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());
182                if (KFSConstants.DocumentStatusCodes.CANCELLED.equals(applicationDocument.getDocumentHeader().getFinancialDocumentStatusCode())) {
183                    // Ignore this case, as it should not impact the ability to cancel a cash control doc.
184                }
185                else if (!KFSConstants.DocumentStatusCodes.APPROVED.equals(applicationDocument.getDocumentHeader().getFinancialDocumentStatusCode())) {
186                    documentService.cancelDocument(applicationDocument, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
187                }
188                else {
189                    GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDetailFields.CASH_CONTROL_DETAILS_TAB, ArKeyConstants.ERROR_CANT_CANCEL_CASH_CONTROL_DOC_WITH_ASSOCIATED_APPROVED_PAYMENT_APPLICATION);
190                    success = false;
191                    ;
192                }
193            }
194            return success;
195        }
196    
197        /**
198         * This method adds a new cash control detail
199         * 
200         * @param mapping action mapping
201         * @param form action form
202         * @param request
203         * @param response
204         * @return forward action
205         * @throws Exception
206         */
207        public ActionForward addCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
208    
209            CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
210            CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
211            KualiConfigurationService kualiConfiguration = SpringContext.getBean(KualiConfigurationService.class);
212    
213            CashControlDetail newCashControlDetail = cashControlDocForm.getNewCashControlDetail();
214            newCashControlDetail.setDocumentNumber(cashControlDocument.getDocumentNumber());
215    
216            String customerNumber = newCashControlDetail.getCustomerNumber();
217            if (StringUtils.isNotEmpty(customerNumber)) {
218                // force customer numbers to upper case, since its a primary key
219                customerNumber = customerNumber.toUpperCase();
220            }
221            newCashControlDetail.setCustomerNumber(customerNumber);
222    
223            // save the document, which will run business rules and make sure the doc is ready for lines
224            KualiRuleService ruleService = SpringContext.getBean(KualiRuleService.class);
225            boolean rulePassed = true;
226    
227            // apply save rules for the doc
228            rulePassed &= ruleService.applyRules(new SaveDocumentEvent(KFSConstants.DOCUMENT_HEADER_ERRORS, cashControlDocument));
229    
230            // apply rules for the new cash control detail
231            rulePassed &= ruleService.applyRules(new AddCashControlDetailEvent(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX, cashControlDocument, newCashControlDetail));
232    
233            // add the new detail if rules passed
234            if (rulePassed) {
235    
236                CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);
237    
238                // add cash control detail. implicitly saves the cash control document
239                cashControlDocumentService.addNewCashControlDetail(kualiConfiguration.getPropertyString(ArKeyConstants.CREATED_BY_CASH_CTRL_DOC), cashControlDocument, newCashControlDetail);
240    
241                // set a new blank cash control detail
242                cashControlDocForm.setNewCashControlDetail(new CashControlDetail());
243    
244            }
245    
246            // recalc totals, including the docHeader total
247            cashControlDocument.recalculateTotals();
248    
249            return mapping.findForward(KFSConstants.MAPPING_BASIC);
250    
251        }
252    
253        /**
254         * This method deletes a cash control detail
255         * 
256         * @param mapping action mapping
257         * @param form action form
258         * @param request
259         * @param response
260         * @return action forward
261         * @throws Exception
262         */
263        public ActionForward deleteCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
264            CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
265            CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
266    
267            int indexOfLineToDelete = getLineToDelete(request);
268            CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(indexOfLineToDelete);
269            DocumentService documentService = SpringContext.getBean(DocumentService.class);
270    
271            PaymentApplicationDocument payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());
272    
273            // this if statement is to catch the situation where a person deletes the line, but doesnt save
274            // and then reloads. This will bring the deleted line back on the screen. If they then click
275            // the delete line, and this test isnt here, it will try to cancel the already cancelled
276            // document, which will throw a workflow error and barf.
277            if (!payAppDoc.getDocumentHeader().getWorkflowDocument().stateIsCanceled()) {
278                documentService.cancelDocument(payAppDoc, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
279            }
280            cashControlDocument.deleteCashControlDetail(indexOfLineToDelete);
281    
282            // recalc totals, including the docHeader total
283            cashControlDocument.recalculateTotals();
284    
285            return mapping.findForward(KFSConstants.MAPPING_BASIC);
286        }
287    
288        /**
289         * This method generates the GLPEs.
290         * 
291         * @param mapping action mapping
292         * @param form action form
293         * @param request
294         * @param response
295         * @return action forward
296         * @throws Exception
297         */
298        public ActionForward generateGLPEs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
299    
300            CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
301            CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
302            String paymentMediumCode = cashControlDocument.getCustomerPaymentMediumCode();
303    
304            // refresh reference objects
305            cashControlDocument.refreshReferenceObject("customerPaymentMedium");
306            cashControlDocument.refreshReferenceObject("generalLedgerPendingEntries");
307    
308            // payment medium might have been changed meanwhile so we save first the document
309            BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
310            businessObjectService.save(cashControlDocument);
311    
312            // generate the GLPEs
313            GeneralLedgerPendingEntryService glpeService = SpringContext.getBean(GeneralLedgerPendingEntryService.class);
314            boolean success = glpeService.generateGeneralLedgerPendingEntries(cashControlDocument);
315    
316            if (!success) {
317                GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED);
318            }
319            // approve the GLPEs
320            cashControlDocument.changeGeneralLedgerPendingEntriesApprovedStatusCode();
321    
322            // save the GLPEs in the database
323            CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);
324            cashControlDocumentService.saveGLPEs(cashControlDocument);
325    
326            // approve the document when the GLPEs are generated
327            // DocumentService docService = SpringContext.getBean(DocumentService.class);
328            // docService.approveDocument(cashControlDocument, "Automatically approved document with GLPE generation.", null);
329    
330            return mapping.findForward(KFSConstants.MAPPING_BASIC);
331    
332        }
333    
334        /**
335         * Recalculates the cash control total since user could have changed it during their update.
336         * 
337         * @param cashControlDocument
338         */
339        protected KualiDecimal calculateCashControlTotal(CashControlDocument cashControlDocument) {
340            KualiDecimal total = KualiDecimal.ZERO;
341            for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {
342    
343                total = total.add(cashControlDetail.getFinancialDocumentLineAmount());
344            }
345            return total;
346        }
347    
348    }