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 }