View Javadoc
1   /*
2    * Copyright 2006-2009 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.gl.batch.service.impl;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.PrintStream;
21  import java.sql.Date;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.kuali.ole.coa.businessobject.AccountIntf;
28  import org.kuali.ole.coa.businessobject.CarryForwardReversionProcessOrganizationInfo;
29  import org.kuali.ole.coa.businessobject.ClosedAccountOrganizationReversion;
30  import org.kuali.ole.coa.businessobject.ObjectCode;
31  import org.kuali.ole.coa.businessobject.OrganizationReversion;
32  import org.kuali.ole.coa.businessobject.OrganizationReversionCategory;
33  import org.kuali.ole.coa.businessobject.OrganizationReversionCategoryInfo;
34  import org.kuali.ole.coa.service.OrganizationReversionService;
35  import org.kuali.ole.coa.service.PriorYearAccountService;
36  import org.kuali.ole.gl.GeneralLedgerConstants;
37  import org.kuali.ole.gl.batch.service.OrganizationReversionCategoryLogic;
38  import org.kuali.ole.gl.batch.service.OrganizationReversionProcess;
39  import org.kuali.ole.gl.batch.service.OrganizationReversionUnitOfWorkService;
40  import org.kuali.ole.gl.batch.service.impl.exception.FatalErrorException;
41  import org.kuali.ole.gl.businessobject.Balance;
42  import org.kuali.ole.gl.businessobject.OrgReversionUnitOfWork;
43  import org.kuali.ole.gl.businessobject.OrgReversionUnitOfWorkCategoryAmount;
44  import org.kuali.ole.gl.businessobject.OriginEntryFull;
45  import org.kuali.ole.gl.report.LedgerSummaryReport;
46  import org.kuali.ole.gl.service.BalanceService;
47  import org.kuali.ole.gl.service.OriginEntryService;
48  import org.kuali.ole.sys.OLEConstants;
49  import org.kuali.ole.sys.OLEKeyConstants;
50  import org.kuali.ole.sys.OLEPropertyConstants;
51  import org.kuali.ole.sys.businessobject.SystemOptions;
52  import org.kuali.ole.sys.context.SpringContext;
53  import org.kuali.ole.sys.service.FlexibleOffsetAccountService;
54  import org.kuali.ole.sys.service.OptionsService;
55  import org.kuali.ole.sys.service.ReportWriterService;
56  import org.kuali.ole.sys.service.impl.OleParameterConstants;
57  import org.kuali.rice.core.api.config.property.ConfigurationService;
58  import org.kuali.rice.core.api.datetime.DateTimeService;
59  import org.kuali.rice.core.api.util.type.KualiDecimal;
60  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
61  import org.kuali.rice.krad.service.PersistenceService;
62  import org.kuali.rice.krad.util.ObjectUtils;
63  import org.springframework.beans.factory.InitializingBean;
64  import org.springframework.transaction.annotation.Transactional;
65  
66  /**
67   * This class actually runs the year end organization reversion process
68   */
69  @Transactional
70  public class OrganizationReversionProcessImpl implements OrganizationReversionProcess, InitializingBean {
71      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OrganizationReversionProcessImpl.class);
72  
73      // Services
74      private OrganizationReversionService organizationReversionService;
75      private BalanceService balanceService;
76      private OriginEntryService originEntryService;
77      private PersistenceService persistenceService;
78      private DateTimeService dateTimeService;
79      private OrganizationReversionCategoryLogic cashOrganizationReversionCategoryLogic;
80      private PriorYearAccountService priorYearAccountService;
81      private OrganizationReversionUnitOfWorkService orgReversionUnitOfWorkService;
82      private FlexibleOffsetAccountService flexibleOffsetAccountService;
83      private ParameterService parameterService;
84      private ConfigurationService configurationService;
85  
86      private String batchFileDirectoryName;
87      private String outputFileName;
88      private OrgReversionUnitOfWork unitOfWork;
89      private Map<String, OrganizationReversionCategoryLogic> categories;
90      private List<OrganizationReversionCategory> categoryList;
91      private CarryForwardReversionProcessOrganizationInfo organizationReversion;
92      private AccountIntf account;
93  
94      private Map jobParameters;
95      private Map<String, Integer> organizationReversionCounts;
96  
97      private boolean usePriorYearInformation;
98  
99      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().getParameterValueAsString(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.CARRY_FORWARD_OBJECT_CODE);
129         this.DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE = getParameterService().getParameterValueAsString(OleParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_DOCUMENT_TYPE);
130         this.DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE = getParameterService().getParameterValueAsString(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE);
131         this.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE = getParameterService().getParameterValueAsString(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
132         this.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END = getParameterService().getParameterValueAsString(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
133         this.DEFAULT_DOCUMENT_NUMBER_PREFIX = getParameterService().getParameterValueAsString(OrganizationReversion.class, GeneralLedgerConstants.OrganizationReversionProcess.DEFAULT_DOCUMENT_NUMBER_PREFIX);
134         this.CASH_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.CASH_REVERTED_TO);
135         this.FUND_BALANCE_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.FUND_BALANCE_REVERTED_TO);
136         this.CASH_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.CASH_REVERTED_FROM);
137         this.FUND_BALANCE_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.FUND_BALANCE_REVERTED_FROM);
138         this.FUND_CARRIED_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.FUND_CARRIED);
139         this.FUND_REVERTED_TO_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.OrganizationReversionProcess.FUND_REVERTED_TO);
140         this.FUND_REVERTED_FROM_MESSAGE = getConfigurationService().getPropertyValueAsString(OLEKeyConstants.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(OLEConstants.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(OLEConstants.UNIV_FISCAL_YR) + "; account: " + account + "; account organization code: " + account.getOrganizationCode() + "; balance: " + bal + "; balance chart: " + bal.getChartOfAccountsCode());
266             }
267             organizationReversion = getOrganizationReversionService().getByPrimaryId((Integer) jobParameters.get(OLEConstants.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(OLEConstants.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(OLEConstants.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 (OLEConstants.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(OLEConstants.UNIV_FISCAL_YR));
416         entry.setUniversityFiscalPeriodCode(OLEConstants.MONTH13);
417         entry.setFinancialDocumentTypeCode(DEFAULT_FINANCIAL_DOCUMENT_TYPE_CODE);
418         entry.setFinancialSystemOriginationCode(DEFAULT_FINANCIAL_SYSTEM_ORIGINATION_CODE);
419         entry.setTransactionLedgerEntrySequenceNumber(1);
420         entry.setTransactionDebitCreditCode(OLEConstants.GL_BUDGET_CODE);
421         entry.setTransactionDate((Date) jobParameters.get(OLEConstants.TRANSACTION_DT));
422         entry.setProjectCode(OLEConstants.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(OLEConstants.getDashFinancialSubObjectCode());
444         entry.setFinancialBalanceTypeCode(systemOptions.getNominalFinancialBalanceTypeCd());
445 
446         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.GL_CREDIT_CODE);
456         }
457         else {
458             entry.setTransactionDebitCreditCode(OLEConstants.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(OLEConstants.FUND_BAL_OBJECT_CD));
475         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
476         entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
477 
478         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.GL_DEBIT_CODE);
488         }
489         else {
490             entry.setTransactionDebitCreditCode(OLEConstants.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(OLEConstants.getDashSubAccountNumber());
506         entry.setFinancialObjectCode(organizationReversion.getCashReversionChartCashObjectCode());
507         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
508         entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
509 
510         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.GL_DEBIT_CODE);
520         }
521         else {
522             entry.setTransactionDebitCreditCode(OLEConstants.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(OLEConstants.getDashSubAccountNumber());
538         entry.setFinancialObjectCode((String) jobParameters.get(OLEConstants.FUND_BAL_OBJECT_CD));
539         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
540         entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE);
541 
542         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.GL_CREDIT_CODE);
552         }
553         else {
554             entry.setTransactionDebitCreditCode(OLEConstants.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(OLEConstants.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(OLEConstants.BEG_BUD_CASH_OBJECT_CD));
591                 entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
592                 entry.setFinancialBalanceTypeCode(OLEConstants.BALANCE_TYPE_CURRENT_BUDGET);
593 
594                 getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.MONTH1);
602                 entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
603                 entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(OLEConstants.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(OLEConstants.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(OLEConstants.getDashFinancialSubObjectCode());
620                 entry.setFinancialBalanceTypeCode(OLEConstants.BALANCE_TYPE_CURRENT_BUDGET);
621 
622                 getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.MONTH1);
630                 entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
631                 entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(OLEConstants.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(OLEConstants.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(OLEConstants.BEG_BUD_CASH_OBJECT_CD));
660         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
661         entry.setFinancialBalanceTypeCode(OLEConstants.BALANCE_TYPE_CURRENT_BUDGET);
662 
663         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.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(OLEConstants.UNIV_FISCAL_YR));
676         entry.setTransactionLedgerEntryAmount(unitOfWork.getTotalCarryForward());
677         entry.setTransactionDate((Date) jobParameters.get(OLEConstants.TRANSACTION_DT));
678         entry.setProjectCode(OLEConstants.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(OLEConstants.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(OLEConstants.UNALLOC_OBJECT_CD));
691 
692         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.getDashFinancialSubObjectCode());
700         entry.setFinancialBalanceTypeCode(OLEConstants.BALANCE_TYPE_CURRENT_BUDGET);
701         entry.setUniversityFiscalPeriodCode(OLEConstants.MONTH1);
702         entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
703         entry.setTransactionLedgerEntryDescription(FUND_CARRIED_MESSAGE + (Integer) jobParameters.get(OLEConstants.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(OLEConstants.UNALLOC_OBJECT_CD));
728         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
729         entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
730 
731         getPersistenceService().retrieveReferenceObject(entry, OLEPropertyConstants.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(OLEConstants.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(OLEConstants.getDashSubAccountNumber());
752         entry.setFinancialObjectCode((String) jobParameters.get(OLEConstants.UNALLOC_OBJECT_CD));
753         entry.setFinancialSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
754         entry.setFinancialBalanceTypeCode(DEFAULT_FINANCIAL_BALANCE_TYPE_CODE_YEAR_END);
755         entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
756         entry.setUniversityFiscalPeriodCode(OLEConstants.MONTH13);
757         entry.setDocumentNumber(DEFAULT_DOCUMENT_NUMBER_PREFIX + unitOfWork.accountNumber);
758         if (unitOfWork.accountNumber.equals(OLEConstants.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 (OLEConstants.RULE_CODE_R1.equals(ruleCode) || OLEConstants.RULE_CODE_N1.equals(ruleCode) || OLEConstants.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 (OLEConstants.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 (OLEConstants.RULE_CODE_C1.equals(ruleCode) || OLEConstants.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 (OLEConstants.RULE_CODE_N1.equals(ruleCode) || OLEConstants.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 ConfigurationService 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(ConfigurationService 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 }