001/*
002 * Copyright 2007 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.sys.web.struts;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024import javax.servlet.http.HttpServletRequest;
025
026import org.apache.commons.lang.StringUtils;
027import org.apache.struts.upload.FormFile;
028import org.kuali.ole.coa.businessobject.Account;
029import org.kuali.ole.coa.businessobject.ObjectCode;
030import org.kuali.ole.coa.businessobject.SubAccount;
031import org.kuali.ole.coa.businessobject.SubObjectCode;
032import org.kuali.ole.sys.OLEConstants;
033import org.kuali.ole.sys.OLEPropertyConstants;
034import org.kuali.ole.sys.businessobject.AccountingLine;
035import org.kuali.ole.sys.businessobject.AccountingLineOverride;
036import org.kuali.ole.sys.businessobject.SourceAccountingLine;
037import org.kuali.ole.sys.businessobject.TargetAccountingLine;
038import org.kuali.ole.sys.context.SpringContext;
039import org.kuali.ole.sys.document.AccountingDocument;
040import org.kuali.ole.sys.document.web.struts.FinancialSystemTransactionalDocumentFormBase;
041import org.kuali.ole.sys.service.impl.OleParameterConstants;
042import org.kuali.rice.core.api.config.property.ConfigurationService;
043import org.kuali.rice.core.web.format.CurrencyFormatter;
044import org.kuali.rice.coreservice.framework.parameter.ParameterService;
045import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
046import org.kuali.rice.krad.util.KRADConstants;
047import org.kuali.rice.krad.util.ObjectUtils;
048
049/**
050 * This class is the base action form for all financial documents.
051 */
052public class KualiAccountingDocumentFormBase extends FinancialSystemTransactionalDocumentFormBase {
053    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiAccountingDocumentFormBase.class);
054
055    protected SourceAccountingLine newSourceLine;
056    protected TargetAccountingLine newTargetLine;
057
058    protected Map editableAccounts;
059    protected Map forcedLookupOptionalFields;
060
061    // TODO: FormFile isn't Serializable, so mark these fields need as transient or create a Serializable subclass of FormFile
062    protected FormFile sourceFile;
063    protected FormFile targetFile;
064    protected boolean hideDetails = false;
065
066    /**
067     * This constructor sets up empty instances for the dependent objects...
068     */
069    public KualiAccountingDocumentFormBase() {
070        super();
071
072        // create an empty editableAccounts map, for safety's sake
073        editableAccounts = new HashMap();
074        forcedReadOnlyFields = new HashMap();
075        forcedLookupOptionalFields = new HashMap();
076    }
077
078
079    /**
080     * Overrides the parent to call super.populate and then to call the accounting lines populate method that is specific to loading
081     * the two select lists on the page.
082     * 
083     * @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
084     */
085    @Override
086    public void populate(HttpServletRequest request) {
087        super.populate(request);
088        final String methodToCall = this.getMethodToCall();
089        final Map parameterMap = request.getParameterMap();
090
091        populateAccountingLinesForResponse(methodToCall, parameterMap);
092
093        setDocTypeName(discoverDocumentTypeName());
094    }
095    
096    /**
097     * Populates the accounting lines which need to be updated to successfully complete a response to the request
098     * @param methodToCall the method to call in the action to complete this request transaction
099     * @param parameterMap the map of parameters which came in with the transaction
100     */
101    protected void populateAccountingLinesForResponse(String methodToCall, Map parameterMap) {
102        populateSourceAccountingLine(getNewSourceLine(), OLEPropertyConstants.NEW_SOURCE_LINE, parameterMap);
103        populateTargetAccountingLine(getNewTargetLine(), OLEPropertyConstants.NEW_TARGET_LINE, parameterMap);
104
105        // don't call populateAccountingLines if you are copying or errorCorrecting a document,
106        // since you want the accountingLines in the copy to be "identical" to those in the original
107        if (!StringUtils.equals(methodToCall, OLEConstants.COPY_METHOD) && !StringUtils.equals(methodToCall, OLEConstants.ERRORCORRECT_METHOD)) {
108            populateAccountingLines(parameterMap);
109        }
110    }
111
112    /**
113     * This method iterates over all of the source lines and all of the target lines in a transactional document, and calls
114     * prepareAccountingLineForValidationAndPersistence on each one. This is called because a user could have updated already
115     * existing accounting lines that had blank values in composite key fields.
116     * 
117     * @param parameterMap the map of parameters that were sent in with the request
118     */
119    protected void populateAccountingLines(Map parameterMap) {
120        Iterator sourceLines = getFinancialDocument().getSourceAccountingLines().iterator();
121        int count = 0;
122        while (sourceLines.hasNext()) {
123            SourceAccountingLine sourceLine = (SourceAccountingLine) sourceLines.next();
124            populateSourceAccountingLine(sourceLine, OLEPropertyConstants.DOCUMENT+"."+OLEPropertyConstants.SOURCE_ACCOUNTING_LINE+"["+count+"]", parameterMap);
125            count += 1;
126        }
127
128        Iterator targetLines = getFinancialDocument().getTargetAccountingLines().iterator();
129        count = 0;
130        while (targetLines.hasNext()) {
131            TargetAccountingLine targetLine = (TargetAccountingLine) targetLines.next();
132            populateTargetAccountingLine(targetLine, OLEPropertyConstants.DOCUMENT+"."+OLEPropertyConstants.TARGET_ACCOUNTING_LINE+"["+count+"]", parameterMap);
133            count += 1;
134        }
135    }
136
137    /**
138     * Populates a source accounting line bo using values from the struts form. This is in place to make sure that all of the
139     * composite key objects have the correct values in them. This should be overridden by children forms in the situation where
140     * document level attributes need to be pushed down into the accounting lines.
141     * 
142     * @param sourceLine
143     * @param accountingLinePropertyName the property path from the form to the accounting line
144     * @param parameterMap the map of parameters that were sent in with the request
145     */
146    public void populateSourceAccountingLine(SourceAccountingLine sourceLine, String accountingLinePropertyName, Map parameterMap) {
147        populateAccountingLine(sourceLine, accountingLinePropertyName, parameterMap);
148    }
149
150    /**
151     * Populates a target accounting line bo using values from the struts form. This is in place to make sure that all of the
152     * composite key objects have the correct values in them. This should be overridden by children forms in the situation where
153     * document level attributes need to be pushed down into the accounting lines.
154     * 
155     * @param targetLine
156     * @param accountingLinePropertyName the property path from the form to the accounting line
157     * @param parameterMap the map of parameters that were sent in with the request
158     */
159    public void populateTargetAccountingLine(TargetAccountingLine targetLine, String accountingLinePropertyName, Map parameterMap) {
160        populateAccountingLine(targetLine, accountingLinePropertyName, parameterMap);
161    }
162
163    /**
164     * Populates the dependent fields of objects contained within the given accountingLine
165     * 
166     * @param line
167     * @param accountingLinePropertyName the property path from the form to the accounting line
168     * @param parameterMap the map of parameters that were sent in with the request
169     */
170    @SuppressWarnings("deprecation")
171    protected void populateAccountingLine(AccountingLine line, String accountingLinePropertyName, Map parameterMap) {
172        SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(line);
173
174        line.setDocumentNumber(getDocument().getDocumentNumber());
175
176        if (ObjectUtils.isNull(line.getAccount())) {
177            line.setAccount(new Account());
178        }
179        line.getAccount().setChartOfAccountsCode(line.getChartOfAccountsCode());
180
181        if (ObjectUtils.isNull(line.getObjectCode())) {
182            line.setObjectCode(new ObjectCode());
183        }
184        line.getObjectCode().setUniversityFiscalYear(getFinancialDocument().getPostingYear());
185        line.getObjectCode().setChartOfAccountsCode(line.getChartOfAccountsCode());
186
187        if (ObjectUtils.isNull(line.getSubAccount())) {
188            line.setSubAccount(new SubAccount());
189        }
190        line.getSubAccount().setChartOfAccountsCode(line.getChartOfAccountsCode());
191        line.getSubAccount().setAccountNumber(line.getAccountNumber());
192
193        if (ObjectUtils.isNull(line.getSubObjectCode())) {
194            line.setSubObjectCode(new SubObjectCode());
195        }
196        line.getSubObjectCode().setChartOfAccountsCode(line.getChartOfAccountsCode());
197        line.getSubObjectCode().setAccountNumber(line.getAccountNumber());
198        line.getSubObjectCode().setFinancialObjectCode(line.getFinancialObjectCode());
199        line.getSubObjectCode().setUniversityFiscalYear(getFinancialDocument().getPostingYear());
200        
201        repopulateOverrides(line, accountingLinePropertyName, parameterMap);
202
203        AccountingLineOverride.populateFromInput(line);
204    }
205    
206    /**
207     * This repopulates the override values from the request
208     * @param line the line to repopulate override values for
209     * @param accountingLinePropertyName the property path from the form to the accounting line
210     * @param parameterMap the map of parameters that were sent in with the request
211     */
212    protected void repopulateOverrides(AccountingLine line, String accountingLinePropertyName, Map parameterMap) {
213        AccountingLineOverride.determineNeededOverrides(getFinancialDocument() , line);
214        if (line.getAccountExpiredOverrideNeeded()) {
215            if (LOG.isDebugEnabled()) {
216                StringUtils.join(parameterMap.keySet(), "\n");
217            }
218            if (parameterMap.containsKey(accountingLinePropertyName+".accountExpiredOverride.present")) {
219                line.setAccountExpiredOverride(parameterMap.containsKey(accountingLinePropertyName+".accountExpiredOverride"));
220            }
221        } else {
222            line.setAccountExpiredOverride(false);
223        }
224        if (line.isObjectBudgetOverrideNeeded()) {
225            if (parameterMap.containsKey(accountingLinePropertyName+".objectBudgetOverride.present")) {
226                line.setObjectBudgetOverride(parameterMap.containsKey(accountingLinePropertyName+".objectBudgetOverride"));
227            }
228        } else {
229            line.setObjectBudgetOverride(false);
230        }
231    }
232
233    /**
234     * This method retrieves an instance of the form.
235     * 
236     * @return
237     */
238    public AccountingDocument getFinancialDocument() {
239        return (AccountingDocument) getDocument();
240    }
241
242    /**
243     * @return Returns the newTargetLine.
244     */
245    public TargetAccountingLine getNewTargetLine() {
246        if (newTargetLine == null) {
247            newTargetLine = createNewTargetAccountingLine(getFinancialDocument());
248        }
249        return newTargetLine;
250    }
251
252    /**
253     * @param newExpenseLine The newTargetLine to set.
254     */
255    public void setNewTargetLine(TargetAccountingLine newExpenseLine) {
256        this.newTargetLine = newExpenseLine;
257    }
258
259    /**
260     * @return Returns the newSourceLine.
261     */
262    public SourceAccountingLine getNewSourceLine() {
263        if (newSourceLine == null) {
264            newSourceLine = createNewSourceAccountingLine(getFinancialDocument());
265        }
266        return newSourceLine;
267    }
268
269    /**
270     * @param newIncomeLine The newSourceLine to set.
271     */
272    public void setNewSourceLine(SourceAccountingLine newIncomeLine) {
273        this.newSourceLine = newIncomeLine;
274    }
275
276    /**
277     * @return Returns the sourceFile.
278     */
279    public FormFile getSourceFile() {
280        return sourceFile;
281    }
282
283    /**
284     * @param sourceFile The sourceFile to set.
285     */
286    public void setSourceFile(FormFile sourceFile) {
287        this.sourceFile = sourceFile;
288    }
289
290    /**
291     * @return Returns the targetFile.
292     */
293    public FormFile getTargetFile() {
294        return targetFile;
295    }
296
297    /**
298     * @param targetFile The targetFile to set.
299     */
300    public void setTargetFile(FormFile targetFile) {
301        this.targetFile = targetFile;
302    }
303
304
305    /**
306     * @return current Map of editableAccounts
307     */
308    public Map getEditableAccounts() {
309        return editableAccounts;
310    }
311
312    /**
313     * @param editableAccounts the account Map to set
314     */
315    public void setEditableAccounts(Map editableAccounts) {
316        this.editableAccounts = editableAccounts;
317    }
318
319    /**
320     * @return hideDetails attribute
321     */
322    public boolean isHideDetails() {
323        return hideDetails;
324    }
325
326    /**
327     * @return hideDetails attribute
328     * @see #isHideDetails()
329     */
330    public boolean getHideDetails() {
331        return isHideDetails();
332    }
333
334    /**
335     * @param hideDetails
336     */
337    public void setHideDetails(boolean hideDetails) {
338        this.hideDetails = hideDetails;
339    }
340
341    /**
342     * Retrieves the source accounting lines total in a currency format with commas.
343     * 
344     * @return String
345     */
346    public String getCurrencyFormattedSourceTotal() {
347        return (String) new CurrencyFormatter().format(getFinancialDocument().getSourceTotal());
348    }
349
350    /**
351     * Retrieves the source accounting lines total in a currency format with commas.
352     * 
353     * @return String
354     */
355    public String getCurrencyFormattedTargetTotal() {
356        return (String) new CurrencyFormatter().format(getFinancialDocument().getTargetTotal());
357    }
358
359    /**
360     * @return the URL to the accounting line import instructions
361     */
362    public String getAccountingLineImportInstructionsUrl() {
363        return SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(OLEConstants.EXTERNALIZABLE_HELP_URL_KEY) + SpringContext.getBean(ParameterService.class).getParameterValueAsString(OleParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, OLEConstants.FinancialApcParms.ACCOUNTING_LINE_IMPORT_HELP);
364    }
365
366    /**
367     * @param financialDocument
368     * @return a new source accounting line for the document
369     */
370    protected SourceAccountingLine createNewSourceAccountingLine(AccountingDocument financialDocument) {
371        if (financialDocument == null) {
372            throw new IllegalArgumentException("invalid (null) document");
373        }
374        try {
375            return (SourceAccountingLine) financialDocument.getSourceAccountingLineClass().newInstance();
376        }
377        catch (Exception e) {
378            throw new IllegalArgumentException("unable to create a new source accounting line", e);
379        }
380    }
381
382    /**
383     * @param financialDocument
384     * @return a new target accounting line for the documet
385     */
386    protected TargetAccountingLine createNewTargetAccountingLine(AccountingDocument financialDocument) {
387        if (financialDocument == null) {
388            throw new IllegalArgumentException("invalid (null) document");
389        }
390        try {
391            return (TargetAccountingLine) financialDocument.getTargetAccountingLineClass().newInstance();
392        }
393        catch (Exception e) {
394            throw new IllegalArgumentException("unable to create a new target accounting line", e);
395        }
396    }
397
398    /**
399     * This method takes a generic list, hopefully with some AccountingLine objects in it, and returns a list of AccountingLine
400     * objects, because Java generics are just so wonderful.
401     * 
402     * @param lines a list of objects
403     * @return a list of the accounting lines that were in the lines parameter
404     */
405    protected List<AccountingLine> harvestAccountingLines(List lines) {
406        List<AccountingLine> accountingLines = new ArrayList<AccountingLine>();
407        for (Object o : lines) {
408            if (o instanceof AccountingLine) {
409                accountingLines.add((AccountingLine) o);
410            }
411        }
412        return accountingLines;
413    }
414
415    /**
416     * A <code>{@link Map}</code> of names of optional accounting line fields that require a quickfinder.
417     * 
418     * @return a Map of fields
419     */
420    public void setForcedLookupOptionalFields(Map fieldMap) {
421        forcedLookupOptionalFields = fieldMap;
422    }
423
424    /**
425     * A <code>{@link Map}</code> of names of optional accounting line fields that require a quickfinder.
426     * 
427     * @return a Map of fields
428     */
429    public Map getForcedLookupOptionalFields() {
430        return forcedLookupOptionalFields;
431    }
432
433    /**
434     * Adds the accounting line file size to the list of max file sizes.
435     * 
436     * @see org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase#customInitMaxUploadSizes()
437     */
438    @Override
439    protected void customInitMaxUploadSizes() {
440        super.customInitMaxUploadSizes();
441        addMaxUploadSize(SpringContext.getBean(ParameterService.class).getParameterValueAsString(OleParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, OLEConstants.ACCOUNTING_LINE_IMPORT_MAX_FILE_SIZE_PARM_NM));
442    }
443    
444    /**
445     * @see org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase#shouldMethodToCallParameterBeUsed(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
446     */
447    @Override
448    public boolean shouldMethodToCallParameterBeUsed(String methodToCallParameterName, String methodToCallParameterValue, HttpServletRequest request) {                
449        if(StringUtils.equals(methodToCallParameterName, KRADConstants.DISPATCH_REQUEST_PARAMETER)) {
450            if(this.getExcludedmethodToCall().contains(methodToCallParameterValue)) {
451                return true;
452            }
453        }
454        return super.shouldMethodToCallParameterBeUsed(methodToCallParameterName, methodToCallParameterValue, request);
455    }
456    
457    /**
458     * get the names of the methods to call that can be excluded from the "be used" check.
459     * @return the names of the methods to call that can be excluded from the "be used" check 
460     */
461    protected List<String> getExcludedmethodToCall() {
462        return new ArrayList<String>();
463    }
464}