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.sys.web.struts;
017    
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import javax.servlet.http.HttpServletRequest;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.apache.struts.upload.FormFile;
028    import org.kuali.kfs.coa.businessobject.Account;
029    import org.kuali.kfs.coa.businessobject.ObjectCode;
030    import org.kuali.kfs.coa.businessobject.SubAccount;
031    import org.kuali.kfs.coa.businessobject.SubObjectCode;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.KFSPropertyConstants;
034    import org.kuali.kfs.sys.businessobject.AccountingLine;
035    import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
036    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
037    import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
038    import org.kuali.kfs.sys.context.SpringContext;
039    import org.kuali.kfs.sys.document.AccountingDocument;
040    import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentFormBase;
041    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
042    import org.kuali.rice.kns.exception.InfrastructureException;
043    import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
044    import org.kuali.rice.kns.service.KualiConfigurationService;
045    import org.kuali.rice.kns.service.ParameterService;
046    import org.kuali.rice.kns.util.KNSConstants;
047    import org.kuali.rice.kns.util.ObjectUtils;
048    import org.kuali.rice.kns.web.format.CurrencyFormatter;
049    
050    /**
051     * This class is the base action form for all financial documents.
052     */
053    public class KualiAccountingDocumentFormBase extends FinancialSystemTransactionalDocumentFormBase {
054        protected static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiAccountingDocumentFormBase.class);
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(), KFSPropertyConstants.NEW_SOURCE_LINE, parameterMap);
103            populateTargetAccountingLine(getNewTargetLine(), KFSPropertyConstants.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, KFSConstants.COPY_METHOD) && !StringUtils.equals(methodToCall, KFSConstants.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, KFSPropertyConstants.DOCUMENT+"."+KFSPropertyConstants.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, KFSPropertyConstants.DOCUMENT+"."+KFSPropertyConstants.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(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(KualiConfigurationService.class).getPropertyString(KFSConstants.EXTERNALIZABLE_HELP_URL_KEY) + SpringContext.getBean(ParameterService.class).getParameterValue(KfsParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, KFSConstants.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 InfrastructureException("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 InfrastructureException("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.pojo.PojoFormBase#customInitMaxUploadSizes()
437         */
438        @Override
439        protected void customInitMaxUploadSizes() {
440            super.customInitMaxUploadSizes();
441            addMaxUploadSize(SpringContext.getBean(ParameterService.class).getParameterValue(KfsParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, KFSConstants.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, KNSConstants.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    }