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.gl.batch.service.impl;
017    
018    import java.io.File;
019    import java.io.FileNotFoundException;
020    import java.io.PrintStream;
021    import java.sql.Date;
022    import java.util.ArrayList;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    import org.kuali.kfs.coa.businessobject.AccountIntf;
028    import org.kuali.kfs.coa.businessobject.CarryForwardReversionProcessOrganizationInfo;
029    import org.kuali.kfs.coa.businessobject.ClosedAccountOrganizationReversion;
030    import org.kuali.kfs.coa.businessobject.ObjectCode;
031    import org.kuali.kfs.coa.businessobject.OrganizationReversion;
032    import org.kuali.kfs.coa.businessobject.OrganizationReversionCategory;
033    import org.kuali.kfs.coa.businessobject.OrganizationReversionCategoryInfo;
034    import org.kuali.kfs.coa.service.OrganizationReversionService;
035    import org.kuali.kfs.coa.service.PriorYearAccountService;
036    import org.kuali.kfs.gl.GeneralLedgerConstants;
037    import org.kuali.kfs.gl.batch.service.OrganizationReversionCategoryLogic;
038    import org.kuali.kfs.gl.batch.service.OrganizationReversionProcess;
039    import org.kuali.kfs.gl.batch.service.OrganizationReversionUnitOfWorkService;
040    import org.kuali.kfs.gl.batch.service.impl.exception.FatalErrorException;
041    import org.kuali.kfs.gl.businessobject.Balance;
042    import org.kuali.kfs.gl.businessobject.OrgReversionUnitOfWork;
043    import org.kuali.kfs.gl.businessobject.OrgReversionUnitOfWorkCategoryAmount;
044    import org.kuali.kfs.gl.businessobject.OriginEntryFull;
045    import org.kuali.kfs.gl.report.LedgerSummaryReport;
046    import org.kuali.kfs.gl.service.BalanceService;
047    import org.kuali.kfs.gl.service.OriginEntryService;
048    import org.kuali.kfs.sys.KFSConstants;
049    import org.kuali.kfs.sys.KFSKeyConstants;
050    import org.kuali.kfs.sys.KFSPropertyConstants;
051    import org.kuali.kfs.sys.businessobject.SystemOptions;
052    import org.kuali.kfs.sys.context.SpringContext;
053    import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
054    import org.kuali.kfs.sys.service.OptionsService;
055    import org.kuali.kfs.sys.service.ReportWriterService;
056    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
057    import org.kuali.rice.kns.service.DateTimeService;
058    import org.kuali.rice.kns.service.KualiConfigurationService;
059    import org.kuali.rice.kns.service.ParameterService;
060    import org.kuali.rice.kns.service.PersistenceService;
061    import org.kuali.rice.kns.util.KualiDecimal;
062    import org.kuali.rice.kns.util.ObjectUtils;
063    import org.springframework.beans.factory.InitializingBean;
064    import org.springframework.transaction.annotation.Transactional;
065    
066    /**
067     * This class actually runs the year end organization reversion process
068     */
069    @Transactional
070    public class OrganizationReversionProcessImpl implements OrganizationReversionProcess, InitializingBean {
071        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrganizationReversionProcessImpl.class);
072    
073        // Services
074        private OrganizationReversionService organizationReversionService;
075        private BalanceService balanceService;
076        private OriginEntryService originEntryService;
077        private PersistenceService persistenceService;
078        private DateTimeService dateTimeService;
079        private OrganizationReversionCategoryLogic cashOrganizationReversionCategoryLogic;
080        private PriorYearAccountService priorYearAccountService;
081        private OrganizationReversionUnitOfWorkService orgReversionUnitOfWorkService;
082        private FlexibleOffsetAccountService flexibleOffsetAccountService;
083        private ParameterService parameterService;
084        private KualiConfigurationService configurationService;
085    
086        private String batchFileDirectoryName;
087        private String outputFileName;
088        private OrgReversionUnitOfWork unitOfWork;
089        private Map<String, OrganizationReversionCategoryLogic> categories;
090        private List<OrganizationReversionCategory> categoryList;
091        private CarryForwardReversionProcessOrganizationInfo organizationReversion;
092        private AccountIntf account;
093    
094        private Map jobParameters;
095        private Map<String, Integer> organizationReversionCounts;
096    
097        private boolean usePriorYearInformation;
098    
099        private boolean holdGeneratedOriginEntries = false;
100        private List<OriginEntryFull> generatedOriginEntries;
101    
102        public String CARRY_FORWARD_OBJECT_CODE;
103        public String DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE;
104        public String DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE;
105        public String DEFAULT_FINANCIAL_BALANCE_TYPE_CODE;
106        public String DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END;
107        public String DEFAULT_DOCUMENT_NUMBER_PREFIX;
108    
109        private String CASH_REVERTED_TO_MESSAGE;
110        private String FUND_BALANCE_REVERTED_TO_MESSAGE;
111        private String CASH_REVERTED_FROM_MESSAGE;
112        private String FUND_BALANCE_REVERTED_FROM_MESSAGE;
113        private String FUND_CARRIED_MESSAGE;
114        private String FUND_REVERTED_TO_MESSAGE;
115        private String FUND_REVERTED_FROM_MESSAGE;
116    
117        private SystemOptions systemOptions;
118        private Integer paramFiscalYear;
119        
120        private LedgerSummaryReport ledgerReport;
121        
122        private PrintStream outputPs;
123         
124        /**
125         * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
126         */
127        public void afterPropertiesSet() throws Exception {
128            this.CARRY_FORWARD_OBJECT_CODE = getParameterService().getParameterValue(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.CARRY_FORWARD_OBJECT_CODE);
129            this.DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE = getParameterService().getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_DOCUMENT_TYPE);
130            this.DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE = getParameterService().getParameterValue(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE);
131            this.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE = getParameterService().getParameterValue(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
132            this.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END = getParameterService().getParameterValue(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
133            this.DEFAULT_DOCUMENT_NUMBER_PREFIX = getParameterService().getParameterValue(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_DOCUMENT_NUMBER_PREFIX);
134            this.CASH_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.CASH_REVERTED_TO);
135            this.FUND_BALANCE_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.FUND_BALANCE_REVERTED_TO);
136            this.CASH_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.CASH_REVERTED_FROM);
137            this.FUND_BALANCE_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.FUND_BALANCE_REVERTED_FROM);
138            this.FUND_CARRIED_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.FUND_CARRIED);
139            this.FUND_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.FUND_REVERTED_TO);
140            this.FUND_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyString(KFSKeyConstants.OrganizationReversionProcess.FUND_REVERTED_FROM);
141            
142            outputFileName = getBatchFileDirectoryName() + File.separator + (usePriorYearInformation ? GeneralLedgerConstants.BatchFileSystem.ORGANIZATION_REVERSION_CLOSING_FILE : GeneralLedgerConstants.BatchFileSystem.ORGANIZATION_REVERSION_PRE_CLOSING_FILE) + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
143        }
144    
145        /**
146         * This evilly named method actually runs the organization reversion process.
147         */
148        public void organizationReversionProcess(Map jobParameters, Map<String, Integer> organizationReversionCounts) {
149            if (LOG.isDebugEnabled()) {
150                LOG.debug("organizationReversionProcess() started");
151            }
152            this.jobParameters = jobParameters;
153            this.organizationReversionCounts = organizationReversionCounts;
154    
155            LOG.info("Initializing the process");
156            initializeProcess();
157            
158            //create files
159            File outputFile = new File(outputFileName);
160            
161            try {
162                outputPs = new PrintStream(outputFile);
163            
164                Iterator<Balance> balances = getBalanceService().findOrganizationReversionBalancesForFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR), usePriorYearInformation);
165                processBalances(balances);
166                
167                outputPs.close();
168            } catch (FileNotFoundException e) {
169                throw new RuntimeException("Organization Reversion File Files doesn't exist " + outputFileName);
170            }
171    
172        }
173    
174        /**
175         * Given a list of balances, this method generates the origin entries for the organization reversion/carry forward process, and saves those
176         * to an initialized origin entry group
177         * 
178         * @param balances an iterator of balances to process; each balance returned by the iterator will be processed by this method
179         */
180        public void processBalances(Iterator<Balance> balances) {
181            boolean skipToNextUnitOfWork = false;
182            unitOfWork = new OrgReversionUnitOfWork();
183            unitOfWork.setCategories(categoryList);
184            
185            Balance bal;
186            while (balances.hasNext()) {
187                bal = balances.next();
188                if (LOG.isDebugEnabled()) {
189                    LOG.debug("BALANCE SELECTED: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()));
190                }
191    
192                try {
193                    if (!unitOfWork.isInitialized()) {
194                        unitOfWork.setFields(bal.getChartOfAccountsCode(), bal.getAccountNumber(), bal.getSubAccountNumber());
195                        retrieveCurrentReversionAndAccount(bal);
196                    }
197                    else if (!unitOfWork.wouldHold(bal)) {
198                        if (!skipToNextUnitOfWork) {
199                            calculateTotals();
200                            List<OriginEntryFull> originEntriesToWrite = generateOutputOriginEntries();
201                            summarizeOriginEntries(originEntriesToWrite);
202                            if (holdGeneratedOriginEntries) {
203                                generatedOriginEntries.addAll(originEntriesToWrite);
204                            }
205                            int recordsWritten = writeOriginEntries(originEntriesToWrite);
206                            incrementCount("recordsWritten", recordsWritten);
207                            getOrgReversionUnitOfWorkService().save(unitOfWork);
208                        }
209                        unitOfWork.setFields(bal.getChartOfAccountsCode(), bal.getAccountNumber(), bal.getSubAccountNumber());
210                        retrieveCurrentReversionAndAccount(bal);
211                        skipToNextUnitOfWork = false;
212                    }
213                    if (skipToNextUnitOfWork) {
214                        continue; // if there is no org reversion or an org reversion detail is missing or the balances are off for
215                        // this unit of work,
216                        // just skip all the balances until we change unit of work
217                    }
218                    calculateBucketAmounts(bal);
219                }
220                catch (FatalErrorException fee) {
221                    LOG.info(fee.getMessage());
222                    skipToNextUnitOfWork = true;
223                }
224            }
225            // save the final unit of work
226            if (!skipToNextUnitOfWork && getBalancesSelected() > 0) {
227                try {
228                    calculateTotals();
229                    List<OriginEntryFull> originEntriesToWrite = generateOutputOriginEntries();
230                    summarizeOriginEntries(originEntriesToWrite);
231                    if (holdGeneratedOriginEntries) {
232                        generatedOriginEntries.addAll(originEntriesToWrite);
233                    }
234                    int recordsWritten = writeOriginEntries(originEntriesToWrite);
235                    incrementCount("recordsWritten", recordsWritten);
236                    getOrgReversionUnitOfWorkService().save(unitOfWork);
237                }
238                catch (FatalErrorException fee) {
239                    LOG.info(fee.getMessage());
240                }
241            }
242            
243        }
244    
245        /**
246         * Given a balance, returns the current organization reversion record and account or prior year account for the balance; it sets them
247         * to private properties
248         * 
249         * @param bal the balance to find the account/prior year account and organization reversion record for 
250         * @throws FatalErrorException if an organization reversion record cannot be found in the database 
251         */
252        protected void retrieveCurrentReversionAndAccount(Balance bal) throws FatalErrorException {
253            // initialize the account
254            if ((account == null) || (!bal.getChartOfAccountsCode().equals(account.getChartOfAccountsCode())) || (!bal.getAccountNumber().equals(account.getAccountNumber()))) {
255                if (usePriorYearInformation) {
256                    account = getPriorYearAccountService().getByPrimaryKey(bal.getChartOfAccountsCode(), bal.getAccountNumber());
257                }
258                else {
259                    account = bal.getAccount();
260                }
261            }
262    
263            if ((organizationReversion == null) || (!organizationReversion.getChartOfAccountsCode().equals(bal.getChartOfAccountsCode())) || (!organizationReversion.getOrganizationCode().equals(account.getOrganizationCode()))) {
264                if (LOG.isDebugEnabled()) {
265                    LOG.debug("Organization Reversion Service: " + getOrganizationReversionService() + "; fiscal year: " + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + "; account: " + account + "; account organization code: " + account.getOrganizationCode() + "; balance: " + bal + "; balance chart: " + bal.getChartOfAccountsCode());
266                }
267                organizationReversion = getOrganizationReversionService().getByPrimaryId((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR), bal.getChartOfAccountsCode(), account.getOrganizationCode());
268            }
269    
270            if (organizationReversion == null) {
271                // we can't find an organization reversion for this balance? Throw exception
272                throw new FatalErrorException("No Organization Reversion found for: " + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + "-" + bal.getChartOfAccountsCode() + "-" + account.getOrganizationCode());
273            }
274            
275            if (account.isClosed()) {
276                organizationReversion = new ClosedAccountOrganizationReversion(organizationReversion);
277            }
278        }
279    
280        /**
281         * This method initializes several properties needed for the process to run correctly
282         */
283        public void initializeProcess() {
284    
285            // clear out summary tables
286            LOG.info("destroying all unit of work summaries");
287            orgReversionUnitOfWorkService.destroyAllUnitOfWorkSummaries();
288    
289            categories = getOrganizationReversionService().getCategories();
290            categoryList = getOrganizationReversionService().getCategoryList();
291    
292            this.paramFiscalYear = (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR);
293    
294            organizationReversionCounts.put("balancesRead", balanceService.countBalancesForFiscalYear(paramFiscalYear));
295            organizationReversionCounts.put("balancesSelected", new Integer(0));
296            organizationReversionCounts.put("recordsWritten", new Integer(0));
297    
298            this.systemOptions = SpringContext.getBean(OptionsService.class).getOptions(paramFiscalYear);
299            
300            ledgerReport = new LedgerSummaryReport();
301        }
302    
303        /**
304         * Depending on the category that this balance belongs to, adds the balance to the appropriate bucket 
305         * 
306         * @param bal the current balance to process
307         */
308        protected void calculateBucketAmounts(Balance bal) {
309            getPersistenceService().retrieveReferenceObject(bal, "financialObject");
310    
311            if (LOG.isDebugEnabled()) {
312                LOG.debug("CONSIDERING IF TO ADD BALANCE: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()));
313            }
314    
315            if (getCashOrganizationReversionCategoryLogic().containsObjectCode(bal.getFinancialObject()) && bal.getBalanceTypeCode().equals(systemOptions.getActualFinancialBalanceTypeCd())) {
316                unitOfWork.addTotalCash(bal.getBeginningBalanceLineAmount());
317                unitOfWork.addTotalCash(bal.getAccountLineAnnualBalanceAmount());
318                incrementCount("balancesSelected");
319                if (LOG.isDebugEnabled()) {
320                    LOG.debug("ADDING BALANCE TO CASH: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()) + " TO CASH, TOTAL CASH NOW = " + unitOfWork.getTotalCash());
321                }
322            }
323            else {
324                for (OrganizationReversionCategory cat : categoryList) {
325                    OrganizationReversionCategoryLogic logic = categories.get(cat.getOrganizationReversionCategoryCode());
326                    if (logic.containsObjectCode(bal.getFinancialObject())) {
327                        if (systemOptions.getActualFinancialBalanceTypeCd().equals(bal.getBalanceTypeCode())) {
328                            // Actual
329                            unitOfWork.addActualAmount(cat.getOrganizationReversionCategoryCode(), bal.getBeginningBalanceLineAmount());
330                            unitOfWork.addActualAmount(cat.getOrganizationReversionCategoryCode(), bal.getAccountLineAnnualBalanceAmount());
331                            incrementCount("balancesSelected");
332                            if (LOG.isDebugEnabled()) {
333                                LOG.debug("ADDING BALANCE TO ACTUAL: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()) + " TO ACTUAL, ACTUAL FOR CATEGORY " + cat.getOrganizationReversionCategoryName() + " NOW = " + unitOfWork.getCategoryAmounts().get(cat.getOrganizationReversionCategoryCode()).getActual());
334                            }
335                        }
336                        else if (systemOptions.getFinObjTypeExpenditureexpCd().equals(bal.getBalanceTypeCode()) || systemOptions.getCostShareEncumbranceBalanceTypeCd().equals(bal.getBalanceTypeCode()) || systemOptions.getIntrnlEncumFinBalanceTypCd().equals(bal.getBalanceTypeCode())) {
337                            // Encumbrance
338                            KualiDecimal amount = bal.getBeginningBalanceLineAmount().add(bal.getAccountLineAnnualBalanceAmount());
339                            if (amount.isPositive()) {
340                                unitOfWork.addEncumbranceAmount(cat.getOrganizationReversionCategoryCode(), amount);
341                                incrementCount("balancesSelected");
342                                if (LOG.isDebugEnabled()) {
343                                    LOG.debug("ADDING BALANCE TO ENCUMBRANCE: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()) + " TO ENCUMBRANCE, ENCUMBRANCE FOR CATEGORY " + cat.getOrganizationReversionCategoryName() + " NOW = " + unitOfWork.getCategoryAmounts().get(cat.getOrganizationReversionCategoryCode()).getEncumbrance());
344                                }
345                            }
346                        }
347                        else if (KFSConstants.BALANCE_TYPE_CURRENT_BUDGET.equals(bal.getBalanceTypeCode())) {
348                            // Budget
349                            if (!CARRY_FORWARD_OBJECT_CODE.equals(bal.getObjectCode())) {
350                                unitOfWork.addBudgetAmount(cat.getOrganizationReversionCategoryCode(), bal.getBeginningBalanceLineAmount());
351                                unitOfWork.addBudgetAmount(cat.getOrganizationReversionCategoryCode(), bal.getAccountLineAnnualBalanceAmount());
352                                incrementCount("balancesSelected");
353                                if (LOG.isDebugEnabled()) {
354                                    LOG.debug("ADDING BALANCE TO BUDGET: " + bal.getUniversityFiscalYear() + bal.getChartOfAccountsCode() + bal.getAccountNumber() + bal.getSubAccountNumber() + bal.getObjectCode() + bal.getSubObjectCode() + bal.getBalanceTypeCode() + bal.getObjectTypeCode() + " " + bal.getAccountLineAnnualBalanceAmount().add(bal.getBeginningBalanceLineAmount()) + " TO CURRENT BUDGET, CURRENT BUDGET FOR CATEGORY " + cat.getOrganizationReversionCategoryName() + " NOW = " + unitOfWork.getCategoryAmounts().get(cat.getOrganizationReversionCategoryCode()).getBudget());
355                                }
356                            }
357                        }
358                        break;
359                    }
360                }
361            }
362        }
363    
364        /**
365         * This method determines which origin entries (reversion, cash reversion, or carry forward) need to be generated for the current unit of work,
366         * and then delegates to the origin entry generation methods to create those entries
367         * 
368         * @return a list of OriginEntries which need to be written
369         * @throws FatalErrorException thrown if object codes are missing in any of the generation methods
370         */
371        public List<OriginEntryFull> generateOutputOriginEntries() throws FatalErrorException {
372            List<OriginEntryFull> originEntriesToWrite = new ArrayList<OriginEntryFull>();
373            if (unitOfWork.getTotalReversion().compareTo(KualiDecimal.ZERO) != 0) {
374                generateReversions(originEntriesToWrite);
375            }
376            if ((unitOfWork.getTotalCarryForward().compareTo(KualiDecimal.ZERO) != 0)) {
377                if (!organizationReversion.isCarryForwardByObjectCodeIndicator()) {
378                    generateCarryForwards(originEntriesToWrite);
379                }
380                else {
381                    generateMany(originEntriesToWrite);
382                }
383            }
384            if (unitOfWork.getTotalCash().compareTo(KualiDecimal.ZERO) != 0) {
385                generateCashReversions(originEntriesToWrite);
386            }
387            return originEntriesToWrite;
388        }
389    
390        /**
391         * This method writes a list of OriginEntryFulls to a given origin entry group
392         * 
393         * @param writeGroup the origin entry group to write to
394         * @param originEntriesToWrite a list of origin entry fulls to write
395         * @return the count of origin entries that were written
396         */
397        protected int writeOriginEntries(List<OriginEntryFull> originEntriesToWrite) {
398            int originEntriesWritten = 0;
399    
400            for (OriginEntryFull originEntry : originEntriesToWrite) {
401                getOriginEntryService().createEntry(originEntry, outputPs);
402                originEntriesWritten += 1;
403            }
404    
405            return originEntriesWritten;
406        }
407    
408        /**
409         * This method starts the creation of an origin entry, by setting fields that are the same in every Org Rev origin entries
410         * 
411         * @return an OriginEntryFull partially filled out with constant information
412         */
413        protected OriginEntryFull getEntry() {
414            OriginEntryFull entry = new OriginEntryFull();
415            entry.setUniversityFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR));
416            entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH13);
417            entry.setFinancialDocumentTypeCode(DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE);
418            entry.setFinancialSystemOriginationCode(DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE);
419            entry.setTransactionLedgerEntrySequenceNumber(1);
420            entry.setTransactionDebitCreditCode(KFSConstants.GL_BUDGET_CODE);
421            entry.setTransactionDate((Date) jobParameters.get(KFSConstants.TRANSACTION_DT));
422            entry.setProjectCode(KFSConstants.getDashProjectCode());
423            return entry;
424        }
425    
426        /**
427         * This method generates cash reversion origin entries for the current organization reversion, and adds them to the given list
428         * 
429         * @param originEntriesToWrite a list of OriginEntryFulls to stick generated origin entries into
430         * @throws FatalErrorException thrown if an origin entry's object code can't be found
431         */
432        public void generateCashReversions(List<OriginEntryFull> originEntriesToWrite) throws FatalErrorException {
433            int entriesWritten = 0;
434            
435            // Reversion of cash from the actual account in the fiscal year ending (balance type of NB)
436            OriginEntryFull entry = getEntry();
437            entry.refreshReferenceObject("option");
438    
439            entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
440            entry.setAccountNumber(unitOfWork.accountNumber);
441            entry.setSubAccountNumber(unitOfWork.subAccountNumber);
442            entry.setFinancialObjectCode(organizationReversion.getOrganizationChartCashObjectCode());
443            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
444            entry.setFinancialBalanceTypeCode(systemOptions.getNominalFinancialBalanceTypeCd());
445    
446            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
447            if (ObjectUtils.isNull(entry.getFinancialObject())) {
448                throw new FatalErrorException("Object Code for Entry not found: " + entry);
449            }
450    
451            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + entry.getAccountNumber());
452            entry.setTransactionLedgerEntryDescription(CASH_REVERTED_TO_MESSAGE + " " + organizationReversion.getCashReversionAccountNumber());
453            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash());
454            if (unitOfWork.getTotalCash().compareTo(KualiDecimal.ZERO) > 0) {
455                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
456            }
457            else {
458                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
459                entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash().negated());
460            }
461            entry.setFinancialObjectTypeCode(entry.getFinancialObject().getFinancialObjectTypeCode());
462    
463            // 3468 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
464            // 3469 WS-AMT-N.
465            // 3470 MOVE WS-AMT-X TO TRN-AMT-RED-X.
466    
467            originEntriesToWrite.add(entry);
468    
469            // Reversion of fund balance, starting with the actual account, to match the cash that was reverted (balance type of NB) 
470            entry = getEntry();
471            entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
472            entry.setAccountNumber(unitOfWork.accountNumber);
473            entry.setSubAccountNumber(unitOfWork.subAccountNumber);
474            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.FUND_BAL_OBJECT_CD));
475            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
476            entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
477    
478            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
479            if (ObjectUtils.isNull(entry.getFinancialObject())) {
480                throw new FatalErrorException("Object Code for Entry not found: " + entry);
481            }
482    
483            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
484            entry.setTransactionLedgerEntryDescription(FUND_BALANCE_REVERTED_TO_MESSAGE + organizationReversion.getCashReversionAccountNumber());
485            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash().abs());
486            if (unitOfWork.getTotalCash().compareTo(KualiDecimal.ZERO) > 0) {
487                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
488            }
489            else {
490                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
491            }
492            entry.setFinancialObjectTypeCode(entry.getFinancialObject().getFinancialObjectTypeCode());
493    
494            // 3570 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
495            // 3571 WS-AMT-N.
496            // 3572 MOVE WS-AMT-X TO TRN-AMT-RED-X.
497    
498            getFlexibleOffsetAccountService().updateOffset(entry);
499            originEntriesToWrite.add(entry);
500    
501            // Reversion of cash to the cash reversion account in the fiscal year ending (balance type of NB)
502            entry = getEntry();
503            entry.setChartOfAccountsCode(organizationReversion.getCashReversionFinancialChartOfAccountsCode());
504            entry.setAccountNumber(organizationReversion.getCashReversionAccountNumber());
505            entry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
506            entry.setFinancialObjectCode(organizationReversion.getCashReversionChartCashObjectCode());
507            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
508            entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
509    
510            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
511            if (ObjectUtils.isNull(entry.getFinancialObject())) {
512                throw new FatalErrorException("Object Code for Entry not found: " + entry);
513            }
514    
515            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
516            entry.setTransactionLedgerEntryDescription(CASH_REVERTED_FROM_MESSAGE + unitOfWork.accountNumber + " " + unitOfWork.subAccountNumber);
517            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash());
518            if (unitOfWork.getTotalCash().compareTo(KualiDecimal.ZERO) > 0) {
519                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
520            }
521            else {
522                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
523                entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash().negated());
524            }
525            entry.setFinancialObjectTypeCode(entry.getFinancialObject().getFinancialObjectTypeCode());
526    
527            // 3668 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
528            // 3669 WS-AMT-N.
529            // 3670 MOVE WS-AMT-X TO TRN-AMT-RED-X.
530    
531            originEntriesToWrite.add(entry);
532    
533            // Reversion of fund balance, starting with the cash reversion account, to match the cash that was reverted (balance type of NB) 
534            entry = getEntry();
535            entry.setChartOfAccountsCode(organizationReversion.getCashReversionFinancialChartOfAccountsCode());
536            entry.setAccountNumber(organizationReversion.getCashReversionAccountNumber());
537            entry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
538            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.FUND_BAL_OBJECT_CD));
539            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
540            entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
541    
542            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
543            if (ObjectUtils.isNull(entry.getFinancialObject())) {
544                throw new FatalErrorException("Object Code for Entry not found: " + entry);
545            }
546    
547            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
548            entry.setTransactionLedgerEntryDescription(FUND_BALANCE_REVERTED_FROM_MESSAGE + unitOfWork.accountNumber + " " + unitOfWork.subAccountNumber);
549            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash());
550            if (unitOfWork.getTotalCash().compareTo(KualiDecimal.ZERO) > 0) {
551                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
552            }
553            else {
554                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
555                entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCash().negated());
556            }
557            entry.setFinancialObjectTypeCode(entry.getFinancialObject().getFinancialObjectTypeCode());
558    
559            // 3768 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
560            // 3769 WS-AMT-N.
561            // 3770 MOVE WS-AMT-X TO TRN-AMT-RED-X.
562            
563            getFlexibleOffsetAccountService().updateOffset(entry);
564            originEntriesToWrite.add(entry);
565        }
566    
567        /**
568         * Generates carry forward origin entries on a category by category basis (if the organization reversion record asks for that), assuming carry
569         * forwards are required for the current unit of work
570         * 
571         * @param originEntriesToWrite a list of origin entries to write, which any generated origin entries should be added to
572         * @throws FatalErrorException thrown if an object code cannot be found
573         */
574        public void generateMany(List<OriginEntryFull> originEntriesToWrite) throws FatalErrorException {
575            int originEntriesCreated = 0;
576            for (Iterator<OrganizationReversionCategory> iter = categoryList.iterator(); iter.hasNext();) {
577                OrganizationReversionCategory cat = iter.next();
578                OrganizationReversionCategoryInfo detail = organizationReversion.getOrganizationReversionDetail(cat.getOrganizationReversionCategoryCode());
579                OrgReversionUnitOfWorkCategoryAmount amount = unitOfWork.amounts.get(cat.getOrganizationReversionCategoryCode());
580    
581                if (!amount.getCarryForward().isZero()) {
582                    KualiDecimal commonAmount = amount.getCarryForward();
583                    String commonObject = detail.getOrganizationReversionObjectCode();
584    
585                    OriginEntryFull entry = getEntry();
586                    entry.setUniversityFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + 1);
587                    entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
588                    entry.setAccountNumber(unitOfWork.accountNumber);
589                    entry.setSubAccountNumber(unitOfWork.subAccountNumber);
590                    entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.BEG_BUD_CASH_OBJECT_CD));
591                    entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
592                    entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_CURRENT_BUDGET);
593    
594                    getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
595                    if (ObjectUtils.isNull(entry.getFinancialObject())) {
596                        throw new FatalErrorException("Object Code for Entry not found: " + entry);
597                    }
598    
599                    ObjectCode objectCode = entry.getFinancialObject();
600                    entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
601                    entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH1);
602                    entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
603                    entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR));
604                    entry.setTransactionLedgerEntryAmount(commonAmount);
605    
606                    // 3259 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
607                    // 3260 WS-AMT-N.
608                    // 3261 MOVE WS-AMT-X TO TRN-AMT-RED-X.
609    
610                    originEntriesToWrite.add(entry);
611    
612                    entry = getEntry();
613                    entry.setUniversityFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + 1);
614                    entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
615                    entry.setAccountNumber(unitOfWork.accountNumber);
616                    entry.setSubAccountNumber(unitOfWork.subAccountNumber);
617    
618                    entry.setFinancialObjectCode(commonObject);
619                    entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
620                    entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_CURRENT_BUDGET);
621    
622                    getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
623                    if (ObjectUtils.isNull(entry.getFinancialObject())) {
624                        throw new FatalErrorException("Object Code for Entry not found: " + entry);
625                    }
626    
627                    objectCode = entry.getFinancialObject();
628                    entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
629                    entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH1);
630                    entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
631                    entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR));
632                    entry.setTransactionLedgerEntryAmount(commonAmount);
633    
634                    // 3343 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
635                    // 3344 WS-AMT-N.
636                    // 3345 MOVE WS-AMT-X TO TRN-AMT-RED-X.
637    
638                    originEntriesToWrite.add(entry);
639                }
640            }
641        }
642    
643        /**
644         * If carry forwards need to be generated for this unit of work, this method will generate the origin entries to accomplish those object codes.
645         * Note: this will only be called if the organization reversion record tells the process to munge all carry forwards for all categories
646         * together; if the organization reversion record does not call for such a thing, then generateMany will be called
647         * 
648         * @param originEntriesToWrite a list of origin entries to write, that any generated origin entries should be added to
649         * @throws FatalErrorException thrown if the current object code can't be found in the database
650         */
651        public void generateCarryForwards(List<OriginEntryFull> originEntriesToWrite) throws FatalErrorException {
652            int originEntriesWritten = 0;
653    
654            OriginEntryFull entry = getEntry();
655            entry.setUniversityFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + 1);
656            entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
657            entry.setAccountNumber(unitOfWork.accountNumber);
658            entry.setSubAccountNumber(unitOfWork.subAccountNumber);
659            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.BEG_BUD_CASH_OBJECT_CD));
660            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
661            entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_CURRENT_BUDGET);
662    
663            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
664            if (ObjectUtils.isNull(entry.getFinancialObject())) {
665                throw new FatalErrorException("Object Code for Entry not found: " + entry);
666            }
667    
668            ObjectCode objectCode = entry.getFinancialObject();
669            entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
670            entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH1);
671            entry.setFinancialDocumentTypeCode(DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE);
672            entry.setFinancialSystemOriginationCode(DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE);
673            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
674            entry.setTransactionLedgerEntrySequenceNumber(1);
675            entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR));
676            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCarryForward());
677            entry.setTransactionDate((Date) jobParameters.get(KFSConstants.TRANSACTION_DT));
678            entry.setProjectCode(KFSConstants.getDashProjectCode());
679            // 2995 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
680            // 2996 WS-AMT-N.
681            // 2997 MOVE WS-AMT-X TO TRN-AMT-RED-X.
682    
683            originEntriesToWrite.add(entry);
684    
685            entry = getEntry();
686            entry.setUniversityFiscalYear((Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR) + 1);
687            entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
688            entry.setAccountNumber(unitOfWork.accountNumber);
689            entry.setSubAccountNumber(unitOfWork.subAccountNumber);
690            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.UNALLOC_OBJECT_CD));
691    
692            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
693            if (ObjectUtils.isNull(entry.getFinancialObject())) {
694                throw new FatalErrorException("Object Code for Entry not found: " + entry);
695            }
696    
697            objectCode = entry.getFinancialObject();
698            entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
699            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
700            entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_CURRENT_BUDGET);
701            entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH1);
702            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
703            entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(KFSConstants.UNIV_FISCAL_YR));
704            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCarryForward());
705    
706            // 3079 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
707            // 3080 WS-AMT-N.
708            // 3081 MOVE WS-AMT-X TO TRN-AMT-RED-X.
709    
710            originEntriesToWrite.add(entry);
711    
712        }
713    
714        /**
715         * If reversions are necessary, this will generate the origin entries for those reversions
716         * 
717         * @param originEntriesToWrite the list of origin entries to add reversions into
718         * @throws FatalErrorException thrown if object code if the entry can't be found
719         */
720        public void generateReversions(List<OriginEntryFull> originEntriesToWrite) throws FatalErrorException {
721            int originEntriesWritten = 0;
722    
723            OriginEntryFull entry = getEntry();
724            entry.setChartOfAccountsCode(unitOfWork.chartOfAccountsCode);
725            entry.setAccountNumber(unitOfWork.accountNumber);
726            entry.setSubAccountNumber(unitOfWork.subAccountNumber);
727            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.UNALLOC_OBJECT_CD));
728            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
729            entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
730    
731            getPersistenceService().retrieveReferenceObject(entry, KFSPropertyConstants.FINANCIAL_OBJECT);
732            if (ObjectUtils.isNull(entry.getFinancialObject())) {
733                throw new FatalErrorException("Object Code for Entry not found: " + entry);
734            }
735    
736            ObjectCode objectCode = entry.getFinancialObject();
737            entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
738    
739            entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH13);
740    
741            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + entry.getAccountNumber());
742    
743            entry.setTransactionLedgerEntryDescription(FUND_REVERTED_TO_MESSAGE + organizationReversion.getBudgetReversionAccountNumber());
744            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalReversion().negated());
745    
746            originEntriesToWrite.add(entry);
747    
748            entry = getEntry();
749            entry.setChartOfAccountsCode(organizationReversion.getBudgetReversionChartOfAccountsCode());
750            entry.setAccountNumber(organizationReversion.getBudgetReversionAccountNumber());
751            entry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
752            entry.setFinancialObjectCode((String) jobParameters.get(KFSConstants.UNALLOC_OBJECT_CD));
753            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
754            entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
755            entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
756            entry.setUniversityFiscalPeriodCode(KFSConstants.MONTH13);
757            entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
758            if (unitOfWork.accountNumber.equals(KFSConstants.getDashSubAccountNumber())) {
759                entry.setTransactionLedgerEntryDescription(FUND_REVERTED_FROM_MESSAGE + unitOfWork.accountNumber);
760            }
761            else {
762                entry.setTransactionLedgerEntryDescription(FUND_REVERTED_FROM_MESSAGE + unitOfWork.accountNumber + " " + unitOfWork.subAccountNumber);
763            }
764            entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalReversion());
765    
766            // 2899 MOVE TRN-LDGR-ENTR-AMT TO WS-AMT-W-PERIOD
767            // 2900 WS-AMT-N.
768            // 2901 MOVE WS-AMT-X TO TRN-AMT-RED-X.
769    
770            originEntriesToWrite.add(entry);
771        }
772    
773        /**
774         * This method calculates the totals for a given unit of work's reversion
775         * 
776         * @throws FatalErrorException
777         */
778        public void calculateTotals() throws FatalErrorException {
779            /*
780             * How this works: At the start, in the clearCalculationTotals(), both the unit of work's totalAvailable and totalReversion
781             * are set to the available amounts from each of the category amounts. Then, as the logic is applied, the totalCarryForward
782             * is added to and the totalReversion is subtracted from. Let's look at a simple example: Let's say you've got an amount for
783             * C01, which has $2000 available, no encumbrances, that's all you've got. This means that at the end of
784             * clearCalculationTotals(), there's $2000 in totalAvailable, $2000 in totalReversion, and $0 in totalCarryForward. Now, C01,
785             * let's say, is for code A. So, look below at the if that catches Code A. You'll note that it adds the available amount to
786             * totalCarryForward, it's own carryForward, the negated available to totalReversion, and that, done, it sets available to
787             * $0. With our example, that means that $2000 is in totalCarryForward (and in the amount's carryForward), the
788             * totalReversion has been knocked down to $0, and the available is $0. So, carry forward origin entries get created, and
789             * reversions do not. This is also why you don't see a block about calculating R2 totals below...the process has a natural
790             * inclination towards creating R2 (ie, ignore encumbrances and revert all available) entries.
791             */
792    
793            // clear out the unit of work totals we're going to calculate values in, in preperation for applying rules
794            clearCalculationTotals();
795    
796            // For each category, apply the rules
797            for (OrganizationReversionCategory category : categoryList) {
798                String categoryCode = category.getOrganizationReversionCategoryCode();
799                OrganizationReversionCategoryLogic logic = categories.get(categoryCode);
800                OrgReversionUnitOfWorkCategoryAmount amount = unitOfWork.amounts.get(categoryCode);
801    
802                OrganizationReversionCategoryInfo detail = organizationReversion.getOrganizationReversionDetail(categoryCode);
803    
804                if (detail == null) {
805                    throw new FatalErrorException("Organization Reversion " + organizationReversion.getUniversityFiscalYear() + "-" + organizationReversion.getChartOfAccountsCode() + "-" + organizationReversion.getOrganizationCode() + " does not have a detail for category " + categoryCode);
806                }
807                String ruleCode = detail.getOrganizationReversionCode();
808    
809                if (LOG.isDebugEnabled()) {
810                    LOG.debug("Unit of Work: " + unitOfWork.getChartOfAccountsCode() + unitOfWork.getAccountNumber() + unitOfWork.getSubAccountNumber() + ", category " + category.getOrganizationReversionCategoryName() + ": budget = " + amount.getBudget() + "; actual = " + amount.getActual() + "; encumbrance = " + amount.getEncumbrance() + "; available = " + amount.getAvailable() + "; apply rule code " + ruleCode);
811                }
812    
813                if (KFSConstants.RULE_CODE_R1.equals(ruleCode) || KFSConstants.RULE_CODE_N1.equals(ruleCode) || KFSConstants.RULE_CODE_C1.equals(ruleCode)) {
814                    if (amount.getAvailable().compareTo(KualiDecimal.ZERO) > 0) { // do we have budget left?
815                        if (amount.getAvailable().compareTo(amount.getEncumbrance()) > 0) { // is it more than enough to cover our
816                            // encumbrances?
817                            unitOfWork.addTotalCarryForward(amount.getEncumbrance());
818                            amount.addCarryForward(amount.getEncumbrance());
819                            unitOfWork.addTotalReversion(amount.getEncumbrance().negated());
820                            amount.addAvailable(amount.getEncumbrance().negated());
821                        }
822                        else {
823                            // there's not enough available left to cover the encumbrances; cover what we can
824                            unitOfWork.addTotalCarryForward(amount.getAvailable());
825                            amount.addCarryForward(amount.getAvailable());
826                            unitOfWork.addTotalReversion(amount.getAvailable().negated());
827                            amount.setAvailable(KualiDecimal.ZERO);
828                        }
829                    }
830                }
831    
832                if (KFSConstants.RULE_CODE_A.equals(ruleCode)) {
833                    unitOfWork.addTotalCarryForward(amount.getAvailable());
834                    amount.addCarryForward(amount.getAvailable());
835                    unitOfWork.addTotalReversion(amount.getAvailable().negated());
836                    amount.setAvailable(KualiDecimal.ZERO);
837                }
838    
839                if (KFSConstants.RULE_CODE_C1.equals(ruleCode) || KFSConstants.RULE_CODE_C2.equals(ruleCode)) {
840                    if (amount.getAvailable().compareTo(KualiDecimal.ZERO) > 0) {
841                        unitOfWork.addTotalCarryForward(amount.getAvailable());
842                        amount.addCarryForward(amount.getAvailable());
843                        unitOfWork.addTotalReversion(amount.getAvailable().negated());
844                        amount.setAvailable(KualiDecimal.ZERO);
845                    }
846                }
847    
848                if (KFSConstants.RULE_CODE_N1.equals(ruleCode) || KFSConstants.RULE_CODE_N2.equals(ruleCode)) {
849                    if (amount.getAvailable().compareTo(KualiDecimal.ZERO) < 0) {
850                        unitOfWork.addTotalCarryForward(amount.getAvailable());
851                        amount.addCarryForward(amount.getAvailable());
852                        unitOfWork.addTotalReversion(amount.getAvailable().negated());
853                        amount.setAvailable(KualiDecimal.ZERO);
854                    }
855                }
856    
857                if (LOG.isDebugEnabled()) {
858                    LOG.debug("Totals Now: " + unitOfWork.getChartOfAccountsCode() + unitOfWork.getAccountNumber() + unitOfWork.getSubAccountNumber() + ", total cash now " + unitOfWork.getTotalCash() + ": total available = " + unitOfWork.getTotalAvailable() + "; total reversion = " + unitOfWork.getTotalReversion() + "; total carry forward = " + unitOfWork.getTotalCarryForward());
859                }
860            }
861        }
862    
863        /**
864         * This method clears the unit of work's amounts to what they should be before each category bucket is calculated; specifically,
865         * the total available for each category is calculated, and the total available and total reversion are set to the sum of all
866         * available from each category bucket.  The total carry forward is set to 0.
867         */
868        protected void clearCalculationTotals() {
869            // Initialize all the amounts before applying the proper rule
870            KualiDecimal totalAvailable = KualiDecimal.ZERO;
871            for (OrganizationReversionCategory category : categoryList) {
872                OrganizationReversionCategoryLogic logic = categories.get(category.getOrganizationReversionCategoryCode());
873    
874                OrgReversionUnitOfWorkCategoryAmount amount = unitOfWork.amounts.get(category.getOrganizationReversionCategoryCode());
875                if (logic.isExpense()) {
876                    amount.setAvailable(amount.getBudget().subtract(amount.getActual()));
877                }
878                else {
879                    amount.setAvailable(amount.getActual().subtract(amount.getBudget()));
880                }
881                totalAvailable = totalAvailable.add(amount.getAvailable());
882                amount.setCarryForward(KualiDecimal.ZERO);
883            }
884            unitOfWork.setTotalAvailable(totalAvailable);
885            unitOfWork.setTotalReversion(totalAvailable);
886            unitOfWork.setTotalCarryForward(KualiDecimal.ZERO);
887        }
888        
889        /**
890         * Summarizes the given origin entries to the ledger report
891         * @param originEntries the List of originEntries to summarize
892         */
893        protected void summarizeOriginEntries(List<OriginEntryFull> originEntries) {
894            for (OriginEntryFull originEntry: originEntries) {
895                ledgerReport.summarizeEntry(originEntry);
896            }
897        }
898    
899        public OrgReversionUnitOfWork getUnitOfWork() {
900            return unitOfWork;
901        }
902    
903        public void setUnitOfWork(OrgReversionUnitOfWork unitOfWork) {
904            this.unitOfWork = unitOfWork;
905        }
906    
907        public List<OrganizationReversionCategory> getCategoryList() {
908            return this.categoryList;
909        }
910    
911        /**
912         * Gets the generatedOriginEntries attribute.
913         * 
914         * @return Returns the generatedOriginEntries.
915         */
916        public List<OriginEntryFull> getGeneratedOriginEntries() {
917            return generatedOriginEntries;
918        }
919    
920        /**
921         * Sets the holdGeneratedOriginEntries attribute value.
922         * 
923         * @param holdGeneratedOriginEntries The holdGeneratedOriginEntries to set.
924         */
925        public void setHoldGeneratedOriginEntries(boolean holdGeneratedOriginEntries) {
926            this.holdGeneratedOriginEntries = holdGeneratedOriginEntries;
927            this.generatedOriginEntries = new ArrayList<OriginEntryFull>();
928        }
929    
930        /**
931         * Returns the total number of balances for the previous fiscal year
932         * 
933         * @return the total number of balances for the previous fiscal year
934         */
935        public int getBalancesRead() {
936            return organizationReversionCounts.get("balancesRead").intValue();
937        }
938    
939        /**
940         * Returns the total number of balances selected for inclusion in this process
941         * 
942         * @return the total number of balances selected for inclusion in this process
943         */
944        public int getBalancesSelected() {
945            return organizationReversionCounts.get("balancesSelected").intValue();
946        }
947    
948        /**
949         * Returns the total number of origin entries written by this process
950         * 
951         * @return the total number of origin entries written by this process
952         */
953        public int getRecordsWritten() {
954            return organizationReversionCounts.get("recordsWritten").intValue();
955        }
956    
957        /**
958         * Used mainly for unit testing, this method allows a way to change the output group of a org reversion process run
959         * 
960         * @param outputGroup
961         */
962        public void setOutputFileName(String outputFileName) {
963            this.outputFileName = outputFileName;
964        }
965    
966        /**
967         * Increments one of the totals held in the count map this process uses for reported statistics
968         * 
969         * @param countName the name of the count to increment
970         */
971        private void incrementCount(String countName) {
972            incrementCount(countName, 1);
973        }
974    
975        /**
976         * Increments one of the totals held in the count map this process uses for reported statistics by a given increment
977         * 
978         * @param countName the name of the count to increment
979         * @param increment the amount to increment
980         */
981        protected void incrementCount(String countName, int increment) {
982            Integer count = organizationReversionCounts.get(countName);
983            if (countName.equals("recordsWritten")) {
984                int countAsInt = count.intValue();
985                // add by 1, so we're guaranteed to hit the 1000th
986                for (int i = 1; i <= increment; i++) {
987                    countAsInt += 1;
988                    if (countAsInt % 1000 == 0) {
989                        LOG.info(" ORIGIN ENTRIES INSERTED = "+countAsInt);
990                    } else if (countAsInt == 367471) {
991                        LOG.info(" YOU HAVE ACHIEVED 367471 ORIGIN ENTRIES INSERTED!  TRIUMPH IS YOURS!  ");
992                    }
993                }
994                organizationReversionCounts.put(countName, new Integer(countAsInt));
995            } else {
996                organizationReversionCounts.put(countName, new Integer(count.intValue() + increment));
997            }
998        }
999        
1000        /**
1001         * Writes out the encapsulated origin entry ledger report to the given reportWriterService
1002         * @param reportWriterService the report to write the ledger summary report to
1003         */
1004        public void writeLedgerSummaryReport(ReportWriterService reportWriterService) {
1005            ledgerReport.writeReport(reportWriterService);
1006        }
1007    
1008        /**
1009         * Gets the organizationReversionService attribute. 
1010         * @return Returns the organizationReversionService.
1011         */
1012        public OrganizationReversionService getOrganizationReversionService() {
1013            return organizationReversionService;
1014        }
1015    
1016        /**
1017         * Sets the organizationReversionService attribute value.
1018         * @param organizationReversionService The organizationReversionService to set.
1019         */
1020        public void setOrganizationReversionService(OrganizationReversionService organizationReversionService) {
1021            this.organizationReversionService = organizationReversionService;
1022        }
1023    
1024        /**
1025         * Gets the balanceService attribute. 
1026         * @return Returns the balanceService.
1027         */
1028        public BalanceService getBalanceService() {
1029            return balanceService;
1030        }
1031    
1032        /**
1033         * Sets the balanceService attribute value.
1034         * @param balanceService The balanceService to set.
1035         */
1036        public void setBalanceService(BalanceService balanceService) {
1037            this.balanceService = balanceService;
1038        }
1039    
1040        /**
1041         * Gets the originEntryService attribute. 
1042         * @return Returns the originEntryService.
1043         */
1044        public OriginEntryService getOriginEntryService() {
1045            return originEntryService;
1046        }
1047    
1048        /**
1049         * Sets the originEntryService attribute value.
1050         * @param originEntryService The originEntryService to set.
1051         */
1052        public void setOriginEntryService(OriginEntryService originEntryService) {
1053            this.originEntryService = originEntryService;
1054        }
1055    
1056        /**
1057         * Gets the persistenceService attribute. 
1058         * @return Returns the persistenceService.
1059         */
1060        public PersistenceService getPersistenceService() {
1061            return persistenceService;
1062        }
1063    
1064        /**
1065         * Sets the persistenceService attribute value.
1066         * @param persistenceService The persistenceService to set.
1067         */
1068        public void setPersistenceService(PersistenceService persistenceService) {
1069            this.persistenceService = persistenceService;
1070        }
1071    
1072        /**
1073         * Gets the dateTimeService attribute. 
1074         * @return Returns the dateTimeService.
1075         */
1076        public DateTimeService getDateTimeService() {
1077            return dateTimeService;
1078        }
1079    
1080        /**
1081         * Sets the dateTimeService attribute value.
1082         * @param dateTimeService The dateTimeService to set.
1083         */
1084        public void setDateTimeService(DateTimeService dateTimeService) {
1085            this.dateTimeService = dateTimeService;
1086        }
1087    
1088        /**
1089         * Gets the priorYearAccountService attribute. 
1090         * @return Returns the priorYearAccountService.
1091         */
1092        public PriorYearAccountService getPriorYearAccountService() {
1093            return priorYearAccountService;
1094        }
1095    
1096        /**
1097         * Sets the priorYearAccountService attribute value.
1098         * @param priorYearAccountService The priorYearAccountService to set.
1099         */
1100        public void setPriorYearAccountService(PriorYearAccountService priorYearAccountService) {
1101            this.priorYearAccountService = priorYearAccountService;
1102        }
1103    
1104        /**
1105         * Gets the orgReversionUnitOfWorkService attribute. 
1106         * @return Returns the orgReversionUnitOfWorkService.
1107         */
1108        public OrganizationReversionUnitOfWorkService getOrgReversionUnitOfWorkService() {
1109            return orgReversionUnitOfWorkService;
1110        }
1111    
1112        /**
1113         * Sets the orgReversionUnitOfWorkService attribute value.
1114         * @param orgReversionUnitOfWorkService The orgReversionUnitOfWorkService to set.
1115         */
1116        public void setOrgReversionUnitOfWorkService(OrganizationReversionUnitOfWorkService orgReversionUnitOfWorkService) {
1117            this.orgReversionUnitOfWorkService = orgReversionUnitOfWorkService;
1118        }
1119    
1120        /**
1121         * Gets the flexibleOffsetAccountService attribute. 
1122         * @return Returns the flexibleOffsetAccountService.
1123         */
1124        public FlexibleOffsetAccountService getFlexibleOffsetAccountService() {
1125            return flexibleOffsetAccountService;
1126        }
1127    
1128        /**
1129         * Sets the flexibleOffsetAccountService attribute value.
1130         * @param flexibleOffsetAccountService The flexibleOffsetAccountService to set.
1131         */
1132        public void setFlexibleOffsetAccountService(FlexibleOffsetAccountService flexibleOffsetAccountService) {
1133            this.flexibleOffsetAccountService = flexibleOffsetAccountService;
1134        }
1135    
1136        /**
1137         * Gets the parameterService attribute. 
1138         * @return Returns the parameterService.
1139         */
1140        public ParameterService getParameterService() {
1141            return parameterService;
1142        }
1143    
1144        /**
1145         * Sets the parameterService attribute value.
1146         * @param parameterService The parameterService to set.
1147         */
1148        public void setParameterService(ParameterService parameterService) {
1149            this.parameterService = parameterService;
1150        }
1151    
1152        /**
1153         * Gets the configurationService attribute. 
1154         * @return Returns the configurationService.
1155         */
1156        public KualiConfigurationService getConfigurationService() {
1157            return configurationService;
1158        }
1159    
1160        /**
1161         * Sets the configurationService attribute value.
1162         * @param configurationService The configurationService to set.
1163         */
1164        public void setConfigurationService(KualiConfigurationService configurationService) {
1165            this.configurationService = configurationService;
1166        }
1167    
1168        /**
1169         * Gets the usePriorYearInformation attribute. 
1170         * @return Returns the usePriorYearInformation.
1171         */
1172        public boolean isUsePriorYearInformation() {
1173            return usePriorYearInformation;
1174        }
1175    
1176        /**
1177         * Sets the usePriorYearInformation attribute value.
1178         * @param usePriorYearInformation The usePriorYearInformation to set.
1179         */
1180        public void setUsePriorYearInformation(boolean endOfYear) {
1181            this.usePriorYearInformation = endOfYear;
1182        }
1183    
1184        /**
1185         * Gets the cashOrganizationReversionCategoryLogic attribute. 
1186         * @return Returns the cashOrganizationReversionCategoryLogic.
1187         */
1188        public OrganizationReversionCategoryLogic getCashOrganizationReversionCategoryLogic() {
1189            return cashOrganizationReversionCategoryLogic;
1190        }
1191    
1192        /**
1193         * Sets the cashOrganizationReversionCategoryLogic attribute value.
1194         * @param cashOrganizationReversionCategoryLogic The cashOrganizationReversionCategoryLogic to set.
1195         */
1196        public void setCashOrganizationReversionCategoryLogic(OrganizationReversionCategoryLogic cashOrganizationReversionCategoryLogic) {
1197            this.cashOrganizationReversionCategoryLogic = cashOrganizationReversionCategoryLogic;
1198        }
1199    
1200    
1201        /**
1202         * Gets the batchFileDirectoryName attribute. 
1203         * @return Returns the batchFileDirectoryName.
1204         */
1205        public String getBatchFileDirectoryName() {
1206            return batchFileDirectoryName;
1207        }
1208    
1209        /**
1210         * Sets the batchFileDirectoryName attribute value.
1211         * @param batchFileDirectoryName The batchFileDirectoryName to set.
1212         */
1213        public void setBatchFileDirectoryName(String batchFileDirectoryName) {
1214            this.batchFileDirectoryName = batchFileDirectoryName;
1215        }
1216    
1217        /**
1218         * Sets the jobParameters attribute value.
1219         * @param jobParameters The jobParameters to set.
1220         */
1221        public void setJobParameters(Map jobParameters) {
1222            this.jobParameters = jobParameters;
1223        }
1224    
1225        /**
1226         * Sets the organizationReversionCounts attribute value.
1227         * @param organizationReversionCounts The organizationReversionCounts to set.
1228         */
1229        public void setOrganizationReversionCounts(Map<String, Integer> organizationReversionCounts) {
1230            this.organizationReversionCounts = organizationReversionCounts;
1231        }
1232        
1233    }