001/*
002 * Copyright 2006 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 */
016package org.kuali.ole.fp.document.web.struts;
017
018import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
019import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
020
021import java.sql.Date;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025
026import javax.servlet.http.HttpServletRequest;
027
028import org.apache.commons.lang.StringUtils;
029import org.kuali.ole.coa.businessobject.AccountingPeriod;
030import org.kuali.ole.coa.businessobject.ObjectCode;
031import org.kuali.ole.coa.businessobject.SubObjectCode;
032import org.kuali.ole.coa.service.AccountingPeriodService;
033import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelper;
034import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelperBase;
035import org.kuali.ole.fp.document.VoucherDocument;
036import org.kuali.ole.sys.OLEConstants;
037import org.kuali.ole.sys.OLEKeyConstants;
038import org.kuali.ole.sys.OLEPropertyConstants;
039import org.kuali.ole.sys.businessobject.SourceAccountingLine;
040import org.kuali.ole.sys.context.SpringContext;
041import org.kuali.ole.sys.document.AmountTotaling;
042import org.kuali.ole.sys.web.struts.KualiAccountingDocumentFormBase;
043import org.kuali.rice.core.api.datetime.DateTimeService;
044import org.kuali.rice.core.api.util.type.KualiDecimal;
045import org.kuali.rice.core.web.format.CurrencyFormatter;
046import org.kuali.rice.krad.util.GlobalVariables;
047import org.kuali.rice.krad.util.ObjectUtils;
048
049/**
050 * This class is the Struts specific form object that works in conjunction with the pojo utilities to build the UI for Voucher
051 * Document instances. This class is unique in that it leverages a helper data structure called the
052 * <code>{@link VoucherAccountingLineHelper}</code> because Voucher documents, under some/none conditions, presents the user with
053 * a debit and credit column for amount entry. New accounting lines use specific credit and debit amount fields b/c the new line is
054 * explicitly known; however, already existing accounting lines need to exist within a list with ordering that matches the
055 * accounting lines source list.
056 */
057public class VoucherForm extends KualiAccountingDocumentFormBase {
058    protected List accountingPeriods;
059    protected KualiDecimal newSourceLineDebit;
060    protected KualiDecimal newSourceLineCredit;
061    protected List voucherLineHelpers;
062    protected String selectedAccountingPeriod;
063
064    /**
065     * Supplements a constructor for this voucher class
066     */
067    public VoucherForm() {
068        populateDefaultSelectedAccountingPeriod();
069        setNewSourceLineCredit(KualiDecimal.ZERO);
070        setNewSourceLineDebit(KualiDecimal.ZERO);
071        setVoucherLineHelpers(new ArrayList());
072    }
073
074    /**
075     * sets initial selected accounting period to current period
076     */
077    public void populateDefaultSelectedAccountingPeriod() {
078        Date date = SpringContext.getBean(DateTimeService.class).getCurrentSqlDate();
079        AccountingPeriod accountingPeriod = SpringContext.getBean(AccountingPeriodService.class).getByDate(date);
080
081        StringBuffer sb = new StringBuffer();
082        sb.append(accountingPeriod.getUniversityFiscalPeriodCode());
083        sb.append(accountingPeriod.getUniversityFiscalYear());
084
085        setSelectedAccountingPeriod(sb.toString());
086    }
087
088    /**
089     * Overrides the parent to call super.populate and then to call the two methods that are specific to loading the two select
090     * lists on the page. In addition, this also makes sure that the credit and debit amounts are filled in for situations where
091     * validation errors occur and the page reposts.
092     * 
093     * @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
094     */
095    @Override
096    public void populate(HttpServletRequest request) {
097        super.populate(request);
098
099        // populate the drop downs
100        if (OLEConstants.RETURN_METHOD_TO_CALL.equals(getMethodToCall())) {
101            String selectedPeriod = (StringUtils.defaultString(request.getParameter(OLEPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE)) + StringUtils.defaultString(request.getParameter(OLEPropertyConstants.UNIVERSITY_FISCAL_YEAR)));
102            if (StringUtils.isNotBlank(selectedPeriod)) {
103                setSelectedAccountingPeriod(selectedPeriod);
104            }
105        }
106        populateAccountingPeriodListForRendering();
107
108        // we don't want to do this if we are just reloading the document
109        if (StringUtils.isBlank(getMethodToCall()) || !getMethodToCall().equals(OLEConstants.RELOAD_METHOD_TO_CALL)) {
110            // make sure the amount fields are populated appropriately when in debit/credit amount mode
111            populateCreditAndDebitAmounts();
112        }
113    }
114
115    /**
116     * util method to get postingYear out of selectedAccountingPeriod
117     * 
118     * @return Integer
119     */
120
121    protected Integer getSelectedPostingYear() {
122        Integer postingYear = null;
123        if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
124            postingYear = new Integer(StringUtils.right(getSelectedAccountingPeriod(), 4));
125        }
126        return postingYear;
127    }
128
129    /**
130     * util method to get posting period code out of selectedAccountingPeriod
131     * 
132     * @return String
133     */
134    protected String getSelectedPostingPeriodCode() {
135        String periodCode = null;
136        String selectedPeriod = getSelectedAccountingPeriod();
137        if (StringUtils.isNotBlank(selectedPeriod)) {
138            periodCode = StringUtils.left(selectedPeriod, 2);
139        }
140        return periodCode;
141    }
142
143    /**
144     * Helper method to make casting easier
145     * 
146     * @return VoucherDocument
147     */
148    public VoucherDocument getVoucherDocument() {
149        return (VoucherDocument) getDocument();
150    }
151
152    /**
153     * Override the parent, to push the chosen accounting period and balance type down into the source accounting line object. In
154     * addition, check the balance type to see if it's the "External Encumbrance" balance and alter the encumbrance update code on
155     * the accounting line appropriately.
156     * 
157     * @see org.kuali.rice.kns.web.struts.form.KualiTransactionalDocumentFormBase#populateSourceAccountingLine(org.kuali.rice.krad.bo.SourceAccountingLine)
158     */
159    @Override
160    public void populateSourceAccountingLine(SourceAccountingLine sourceLine, String accountingLinePropertyName, Map parameterMap) {
161        super.populateSourceAccountingLine(sourceLine, accountingLinePropertyName, parameterMap);
162
163        // set the chosen accounting period into the line
164        String selectedAccountingPeriod = getSelectedAccountingPeriod();
165
166        if (StringUtils.isNotBlank(selectedAccountingPeriod)) {
167            Integer postingYear = getSelectedPostingYear();
168            sourceLine.setPostingYear(postingYear);
169
170            if (ObjectUtils.isNull(sourceLine.getObjectCode())) {
171                sourceLine.setObjectCode(new ObjectCode());
172            }
173            sourceLine.getObjectCode().setUniversityFiscalYear(postingYear);
174
175            if (ObjectUtils.isNull(sourceLine.getSubObjectCode())) {
176                sourceLine.setSubObjectCode(new SubObjectCode());
177            }
178            sourceLine.getSubObjectCode().setUniversityFiscalYear(postingYear);
179        }
180
181    }
182
183    /**
184     * This method retrieves the list of valid accounting periods to display.
185     * 
186     * @return List
187     */
188    public List getAccountingPeriods() {
189        return accountingPeriods;
190    }
191
192    /**
193     * This method sets the list of valid accounting periods to display.
194     * 
195     * @param accountingPeriods
196     */
197    public void setAccountingPeriods(List accountingPeriods) {
198        this.accountingPeriods = accountingPeriods;
199    }
200
201    /**
202     * This method returns the reversal date in the format MMM d, yyyy.
203     * 
204     * @return String
205     */
206    public String getFormattedReversalDate() {
207        return formatReversalDate(getVoucherDocument().getReversalDate());
208    }
209
210    /**
211     * This method retrieves the selectedAccountingPeriod.
212     * 
213     * @return String
214     */
215    public String getSelectedAccountingPeriod() {
216        return selectedAccountingPeriod;
217    }
218
219    /**
220     * @return AccountingPeriod associated with the currently selected period
221     */
222    public AccountingPeriod getAccountingPeriod() {
223        AccountingPeriod period = null;
224
225        if (!StringUtils.isBlank(getSelectedAccountingPeriod())) {
226            period = SpringContext.getBean(AccountingPeriodService.class).getByPeriod(getSelectedPostingPeriodCode(), getSelectedPostingYear());
227        }
228
229        return period;
230    }
231
232    /**
233     * This method sets the selectedAccountingPeriod.
234     * 
235     * @param selectedAccountingPeriod
236     */
237    public void setSelectedAccountingPeriod(String selectedAccountingPeriod) {
238        this.selectedAccountingPeriod = selectedAccountingPeriod;
239    }
240
241    /**
242     * Accessor to the list of <code>{@link VoucherAccountingLineHelper}</code> instances. This method retrieves the list of
243     * helper line objects for the form.
244     * 
245     * @return List
246     */
247    public List getVoucherLineHelpers() {
248        return voucherLineHelpers;
249    }
250
251    /**
252     * This method retrieves the proper voucher helper line data structure at the passed in list index so that it matches up with
253     * the correct accounting line at that index.
254     * 
255     * @param index
256     * @return VoucherAccountingLineHelper
257     */
258    public VoucherAccountingLineHelper getVoucherLineHelper(int index) {
259        while (getVoucherLineHelpers().size() <= index) {
260            getVoucherLineHelpers().add(new VoucherAccountingLineHelperBase());
261        }
262        return (VoucherAccountingLineHelper) getVoucherLineHelpers().get(index);
263    }
264
265    /**
266     * This method sets the list of helper lines for the form.
267     * 
268     * @param voucherLineHelpers
269     */
270    public void setVoucherLineHelpers(List voucherLineHelpers) {
271        this.voucherLineHelpers = voucherLineHelpers;
272    }
273
274    /**
275     * This method retrieves the credit amount of the new accounting line that was added.
276     * 
277     * @return KualiDecimal
278     */
279    public KualiDecimal getNewSourceLineCredit() {
280        return newSourceLineCredit;
281    }
282
283    /**
284     * This method sets the credit amount of the new accounting line that was added.
285     * 
286     * @param newSourceLineCredit
287     */
288    public void setNewSourceLineCredit(KualiDecimal newSourceLineCredit) {
289        this.newSourceLineCredit = newSourceLineCredit;
290    }
291
292    /**
293     * This method retrieves the debit amount of the new accounting line that was added.
294     * 
295     * @return KualiDecimal
296     */
297    public KualiDecimal getNewSourceLineDebit() {
298        return newSourceLineDebit;
299    }
300
301    /**
302     * This method sets the debit amount of the new accounting line that was added.
303     * 
304     * @param newSourceLineDebit
305     */
306    public void setNewSourceLineDebit(KualiDecimal newSourceLineDebit) {
307        this.newSourceLineDebit = newSourceLineDebit;
308    }
309
310    /**
311     * This method retrieves the voucher's debit total formatted as currency.
312     * 
313     * @return String
314     */
315    public String getCurrencyFormattedDebitTotal() {
316        return (String) new CurrencyFormatter().format(getVoucherDocument().getDebitTotal());
317    }
318
319    /**
320     * This method retrieves the voucher's credit total formatted as currency.
321     * 
322     * @return String
323     */
324    public String getCurrencyFormattedCreditTotal() {
325        return (String) new CurrencyFormatter().format(getVoucherDocument().getCreditTotal());
326    }
327
328    /**
329     * This method retrieves the voucher's total formatted as currency.
330     * 
331     * @return String
332     */
333    public String getCurrencyFormattedTotal() {
334        return (String) new CurrencyFormatter().format(((AmountTotaling) getVoucherDocument()).getTotalDollarAmount());
335    }
336
337    /**
338     * This method retrieves all of the "open for posting" accounting periods and prepares them to be rendered in a dropdown UI
339     * component.
340     */
341    public void populateAccountingPeriodListForRendering() {
342        // grab the list of valid accounting periods
343        ArrayList accountingPeriods = new ArrayList(SpringContext.getBean(AccountingPeriodService.class).getOpenAccountingPeriods());
344        // set into the form for rendering
345        setAccountingPeriods(accountingPeriods);
346        // set the chosen accounting period into the form
347        populateSelectedVoucherAccountingPeriod();
348    }
349
350
351    /**
352     * This method parses the accounting period value from the form and builds a basic AccountingPeriod object so that the voucher
353     * is properly persisted with the accounting period set for it.
354     */
355    protected void populateSelectedVoucherAccountingPeriod() {
356        if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
357            AccountingPeriod ap = new AccountingPeriod();
358            ap.setUniversityFiscalPeriodCode(getSelectedPostingPeriodCode());
359            ap.setUniversityFiscalYear(getSelectedPostingYear());
360            getFinancialDocument().setAccountingPeriod(ap);
361        }
362    }
363
364    /**
365     * If the balance type is an offset generation balance type, then the user is able to enter the amount as either a debit or a
366     * credit, otherwise, they only need to deal with the amount field in this case we always need to update the underlying bo so
367     * that the debit/credit code along with the amount, is properly set.
368     */
369    protected void populateCreditAndDebitAmounts() {
370        processDebitAndCreditForNewSourceLine();
371        processDebitAndCreditForAllSourceLines();
372    }
373
374    /**
375     * This method uses the newly entered debit and credit amounts to populate the new source line that is to be added to the
376     * voucher document.
377     * 
378     * @return boolean True if the processing was successful, false otherwise.
379     */
380    protected boolean processDebitAndCreditForNewSourceLine() {
381        // using debits and credits supplied, populate the new source accounting line's amount and debit/credit code appropriately
382        boolean passed = processDebitAndCreditForSourceLine(getNewSourceLine(), newSourceLineDebit, newSourceLineCredit, OLEConstants.NEGATIVE_ONE);
383
384        return passed;
385    }
386
387    /**
388     * This method iterates through all of the source accounting lines associated with the voucher doc and accounts for any changes
389     * to the credit and debit amounts, populate the source lines' amount and debit/credit code fields appropriately, so that they
390     * can be persisted accurately. This accounts for the fact that users may change the amounts and/or flip-flop the credit debit
391     * amounts on any accounting line after the initial add of the accounting line.
392     * 
393     * @return boolean
394     */
395    protected boolean processDebitAndCreditForAllSourceLines() {
396        VoucherDocument vDoc = getVoucherDocument();
397
398        // iterate through all of the source accounting lines
399        boolean validProcessing = true;
400        for (int i = 0; i < vDoc.getSourceAccountingLines().size(); i++) {
401            // retrieve the proper business objects from the form
402            SourceAccountingLine sourceLine = vDoc.getSourceAccountingLine(i);
403            VoucherAccountingLineHelper voucherLineHelper = getVoucherLineHelper(i);
404
405            // now process the amounts and the line
406            // we want to process all lines, some may be invalid b/c of dual amount values, but this method will handle
407            // only processing the valid ones, that way we are guaranteed that values in the valid lines carry over through the
408            // post and invalid ones do not alter the underlying business object
409            validProcessing &= processDebitAndCreditForSourceLine(sourceLine, voucherLineHelper.getDebit(), voucherLineHelper.getCredit(), i);
410        }
411        return validProcessing;
412    }
413
414    /**
415     * This method checks the debit and credit attributes passed in, figures out which one has a value, and sets the source
416     * accounting line's amount and debit/credit attribute appropriately. It assumes that if it finds something in the debit field,
417     * it's a debit entry, otherwise it's a credit entry. If a user enters a value into both fields, it will assume the debit value,
418     * then when the br eval framework applies the "add" rule, it will bomb out. If first checks to make sure that there isn't a
419     * value in both the credit and debit columns.
420     * 
421     * @param sourceLine
422     * @param debitAmount
423     * @param creditAmount
424     * @param index if -1, then its a new line, if not -1 then it's an existing line
425     * @return boolean True if the processing was successful, false otherwise.
426     */
427    protected boolean processDebitAndCreditForSourceLine(SourceAccountingLine sourceLine, KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
428        // check to make sure that the
429        if (!validateCreditAndDebitAmounts(debitAmount, creditAmount, index)) {
430            return false;
431        }
432
433        // check to see which amount field has a value - credit or debit field?
434        // and set the values of the appropriate fields
435        if (debitAmount != null && debitAmount.isNonZero()) { // a value entered into the debit field? if so it's a debit
436            // create a new instance w/out reference
437            KualiDecimal tmpDebitAmount = new KualiDecimal(debitAmount.toString());
438            sourceLine.setDebitCreditCode(OLEConstants.GL_DEBIT_CODE);
439            sourceLine.setAmount(tmpDebitAmount);
440        }
441        else if (creditAmount != null && creditAmount.isNonZero()) { // assume credit, if both are set the br eval framework will
442            // catch it
443            KualiDecimal tmpCreditAmount = new KualiDecimal(creditAmount.toString());
444            sourceLine.setDebitCreditCode(OLEConstants.GL_CREDIT_CODE);
445            sourceLine.setAmount(tmpCreditAmount);
446        }
447        else { // default to DEBIT, note the br eval framework will still pick it up
448            sourceLine.setDebitCreditCode(OLEConstants.GL_DEBIT_CODE);
449            sourceLine.setAmount(KualiDecimal.ZERO);
450        }
451
452        return true;
453    }
454
455    /**
456     * This method checks to make sure that there isn't a value in both the credit and debit columns for a given accounting line.
457     * 
458     * @param creditAmount
459     * @param debitAmount
460     * @param index if -1, it's a new line, if not -1, then its an existing line
461     * @return boolean False if both the credit and debit fields have a value, true otherwise.
462     */
463    protected boolean validateCreditAndDebitAmounts(KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
464        boolean valid = false;
465        if (null != creditAmount && null != debitAmount) {
466            if (creditAmount.isNonZero() && debitAmount.isNonZero()) {
467                // there's a value in both fields
468                if (OLEConstants.NEGATIVE_ONE == index) { // it's a new line
469                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(OLEConstants.DEBIT_AMOUNT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
470                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(OLEConstants.CREDIT_AMOUNT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
471                }
472                else {
473                    String errorKeyPath = OLEConstants.JOURNAL_LINE_HELPER_PROPERTY_NAME + OLEConstants.SQUARE_BRACKET_LEFT + Integer.toString(index) + OLEConstants.SQUARE_BRACKET_RIGHT;
474                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
475                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
476                }
477            }
478            else {
479                valid = true;
480            }
481        }
482        else {
483            valid = true;
484        }
485        return valid;
486    }
487}