/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ld.batch.service.impl; import static org.kuali.kfs.module.ld.LaborConstants.DestinationNames.LEDGER_BALANCE; import static org.kuali.kfs.module.ld.LaborConstants.DestinationNames.ORIGN_ENTRY; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.math.BigDecimal; import java.sql.Date; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.gl.GeneralLedgerConstants; import org.kuali.kfs.gl.businessobject.OriginEntryInformation; import org.kuali.kfs.gl.report.PosterOutputSummaryReport; import org.kuali.kfs.module.ld.LaborConstants; import org.kuali.kfs.module.ld.LaborConstants.YearEnd; import org.kuali.kfs.module.ld.LaborKeyConstants; import org.kuali.kfs.module.ld.batch.LaborYearEndBalanceForwardStep; import org.kuali.kfs.module.ld.batch.service.LaborYearEndBalanceForwardService; import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry; import org.kuali.kfs.module.ld.businessobject.LedgerBalanceForYearEndBalanceForward; import org.kuali.kfs.module.ld.service.LaborLedgerBalanceService; import org.kuali.kfs.module.ld.util.DebitCreditUtil; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.Message; import org.kuali.kfs.sys.ObjectUtil; import org.kuali.kfs.sys.businessobject.SystemOptions; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.OptionsService; import org.kuali.kfs.sys.service.ReportWriterService; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.service.BusinessObjectService; import org.springframework.transaction.annotation.Transactional; /** * Labor Ledger Year End Inception to Date Beginning Balance process moves the Year-to-Date Total plus the Contracts and Grants * Beginning Balances to the Contracts and Grants Beginning Balances of the new fiscal year for a designated group of accounts (to * be identified by fund group and sub fund group). */ @Transactional public class LaborYearEndBalanceForwardServiceImpl implements LaborYearEndBalanceForwardService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborYearEndBalanceForwardServiceImpl.class); private LaborLedgerBalanceService laborLedgerBalanceService; private OptionsService optionsService; private BusinessObjectService businessObjectService; private DateTimeService dateTimeService; private ParameterService parameterService; private String batchFileDirectoryName; private ReportWriterService laborBalanceForwardReportWriterService; protected static String PROCESSED_BALANCE_TYPES_LABEL = "PROCESSED BALANCE TYPES"; protected static String PROCESSED_OBJECT_TYPES_LABEL = "PROCESSED OBJECT TYPES"; /** * @see org.kuali.kfs.module.ld.batch.service.LaborYearEndBalanceForwardService#forwardBalance() */ public void forwardBalance() { Integer fiscalYear = Integer.valueOf(parameterService.getParameterValueAsString(LaborYearEndBalanceForwardStep.class, YearEnd.OLD_FISCAL_YEAR)); this.forwardBalance(fiscalYear); } /** * @see org.kuali.kfs.module.ld.batch.service.LaborYearEndBalanceForwardService#forwardBalance(java.lang.Integer) */ public void forwardBalance(Integer fiscalYear) { forwardBalance(fiscalYear, fiscalYear + 1); } /** * @see org.kuali.kfs.module.ld.batch.service.LaborYearEndBalanceForwardService#forwardBalance(java.lang.Integer, * java.lang.Integer) */ public void forwardBalance(Integer fiscalYear, Integer newFiscalYear) { SystemOptions options = optionsService.getOptions(fiscalYear); Date runDate = dateTimeService.getCurrentSqlDate(); Map<String, Integer> reportSummary = this.constructReportSummary(); PosterOutputSummaryReport posterOutputSummaryReport = new PosterOutputSummaryReport(); Collection<String> processableBalanceTypeCodes = this.getProcessableBalanceTypeCode(options); Collection<String> processableObjectTypeCodes = this.getProcessableObjectTypeCodes(options); Collection<String> subFundGroupCodes = this.getSubFundGroupProcessed(); Collection<String> fundGroupCodes = this.getFundGroupProcessed(); // create files String balanceForwardsFileName = batchFileDirectoryName + File.separator + LaborConstants.BatchFileSystem.BALANCE_FORWARDS_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; File balanceForwardsFile = new File(balanceForwardsFileName); PrintStream balanceForwardsPs = null; try { balanceForwardsPs = new PrintStream(balanceForwardsFile); } catch (FileNotFoundException e) { throw new RuntimeException("balanceForwardsFile Files doesn't exist " + balanceForwardsFileName); } // process the selected balances by balance type and object type Map<String, String> fieldValues = new HashMap<String, String>(); int numberOfSelectedBalance = 0; for (String balanceTypeCode : processableBalanceTypeCodes) { fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, balanceTypeCode); for (String objectTypeCode : processableObjectTypeCodes) { fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE, objectTypeCode); fieldValues.remove(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); fieldValues.remove(KFSPropertyConstants.ACCOUNT_NUMBER); Iterator<LedgerBalanceForYearEndBalanceForward> balanceIterator = laborLedgerBalanceService.findBalancesForFiscalYear(fiscalYear, fieldValues, subFundGroupCodes, fundGroupCodes); numberOfSelectedBalance += postSelectedBalancesAsOriginEntries(balanceIterator, newFiscalYear, balanceForwardsPs, runDate, posterOutputSummaryReport, reportSummary); } } this.fillStatisticsReportWriter(reportSummary); fillParametersReportWriter(runDate, fiscalYear, fundGroupCodes, subFundGroupCodes, getOriginationCode() , processableBalanceTypeCodes, processableObjectTypeCodes, getDocumentTypeCode()); posterOutputSummaryReport.writeReport(laborBalanceForwardReportWriterService); balanceForwardsPs.close(); this.createDoneFile(balanceForwardsFileName); } // create a done file after balance forward completes. protected void createDoneFile(String originEntryFileName) { String doneFileName = originEntryFileName.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION); File doneFile = new File(doneFileName); if (!doneFile.exists()) { try { doneFile.createNewFile(); } catch (IOException e) { throw new RuntimeException(); } } } /** * post the qualified balances into origin entry table for the further labor ledger processing * * @param balanceIterator the given ledger balances that will be carried forward * @param newFiscalYear the new fiscal year * @param validGroup the group that the posted transaction belongs to * @param errorMap the map that records the error messages * @param runDate the date the transaction is posted * @return the number of qualified balances */ protected int postSelectedBalancesAsOriginEntries(Iterator<LedgerBalanceForYearEndBalanceForward> balanceIterator, Integer newFiscalYear, PrintStream balanceForwardsPs, Date runDate, PosterOutputSummaryReport posterOutputSummaryReport, Map<String, Integer> reportSummary) { int numberOfSelectedBalance = 0; String description = this.getDescription(); String originationCode = this.getOriginationCode(); String documentTypeCode = this.getDocumentTypeCode(); while (balanceIterator != null && balanceIterator.hasNext()) { LedgerBalanceForYearEndBalanceForward balance = balanceIterator.next(); this.updateReportSummary(reportSummary, LEDGER_BALANCE, KFSConstants.OperationType.READ); List<Message> errors = null; boolean isValidBalance = validateBalance(balance, errors); LaborOriginEntry laborOriginEntry = new LaborOriginEntry(); if (isValidBalance) { laborOriginEntry.setUniversityFiscalYear(newFiscalYear); laborOriginEntry.setFinancialDocumentTypeCode(documentTypeCode); laborOriginEntry.setFinancialSystemOriginationCode(originationCode); laborOriginEntry.setTransactionLedgerEntryDescription(description); this.postAsOriginEntry(balance, laborOriginEntry, balanceForwardsPs, runDate); numberOfSelectedBalance++; posterOutputSummaryReport.summarize((OriginEntryInformation) laborOriginEntry); this.updateReportSummary(reportSummary, LEDGER_BALANCE, KFSConstants.OperationType.SELECT); this.updateReportSummary(reportSummary, LEDGER_BALANCE, KFSConstants.OperationType.INSERT); } else if (errors != null && !errors.isEmpty()) { ObjectUtil.buildObject(laborOriginEntry, balance); laborBalanceForwardReportWriterService.writeError(laborOriginEntry, errors); this.updateReportSummary(reportSummary, LEDGER_BALANCE, KFSConstants.OperationType.REPORT_ERROR); } } return numberOfSelectedBalance; } /** * determine if the given balance is qualified to be carried forward to new fiscal year * * @param balance the given ledger balance that could be carried forward * @param errors the error list that is updated if the given balacne is not qualified for carry forward * @return true if the balance is qualified; otherwise, false */ protected boolean validateBalance(LedgerBalanceForYearEndBalanceForward balance, List<Message> errors) { /** This is the placeholder for addtional business rule validation. The former rules were moved down to data access layer. * */ return true; } /** * post the qualified balance into origin entry table for the further labor ledger processing * * @param balance the given ledger balance that will be carried forward * @param newFiscalYear the new fiscal year * @param validGroup the group that the posted transaction belongs to * @param postingDate the date the transaction is posted */ protected void postAsOriginEntry(LedgerBalanceForYearEndBalanceForward balance, LaborOriginEntry originEntry, PrintStream balanceForwardsPs, Date postingDate) { try { originEntry.setAccountNumber(balance.getAccountNumber()); originEntry.setChartOfAccountsCode(balance.getChartOfAccountsCode()); originEntry.setSubAccountNumber(balance.getSubAccountNumber()); originEntry.setFinancialObjectCode(balance.getFinancialObjectCode()); originEntry.setFinancialSubObjectCode(balance.getFinancialSubObjectCode()); originEntry.setFinancialBalanceTypeCode(balance.getFinancialBalanceTypeCode()); originEntry.setFinancialObjectTypeCode(balance.getFinancialObjectTypeCode()); originEntry.setPositionNumber(balance.getPositionNumber()); originEntry.setEmplid(balance.getEmplid()); originEntry.setDocumentNumber(balance.getFinancialBalanceTypeCode() + balance.getAccountNumber()); originEntry.setProjectCode(KFSConstants.getDashProjectCode()); originEntry.setUniversityFiscalPeriodCode(KFSConstants.PERIOD_CODE_CG_BEGINNING_BALANCE); KualiDecimal transactionAmount = balance.getAccountLineAnnualBalanceAmount(); transactionAmount = transactionAmount.add(balance.getContractsGrantsBeginningBalanceAmount()); originEntry.setTransactionLedgerEntryAmount(transactionAmount.abs()); originEntry.setTransactionDebitCreditCode(DebitCreditUtil.getDebitCreditCode(transactionAmount, false)); originEntry.setTransactionLedgerEntrySequenceNumber(null); originEntry.setTransactionTotalHours(BigDecimal.ZERO); originEntry.setTransactionDate(postingDate); try { balanceForwardsPs.printf("%s\n", originEntry.getLine()); } catch (Exception e) { throw new RuntimeException(e.toString()); } } catch (Exception e) { LOG.error(e); } } /** * get the fund group codes that are acceptable by year-end process * * @return the fund group codes that are acceptable by year-end process */ protected Collection<String> getFundGroupProcessed() { return parameterService.getParameterValuesAsString(LaborYearEndBalanceForwardStep.class, YearEnd.FUND_GROUP_PROCESSED); } /** * get the fund group codes that are acceptable by year-end process * * @return the fund group codes that are acceptable by year-end process */ protected Collection<String> getSubFundGroupProcessed() { return parameterService.getParameterValuesAsString(LaborYearEndBalanceForwardStep.class, YearEnd.SUB_FUND_GROUP_PROCESSED); } /** * get the balance type codes that are acceptable by year-end process * * @return the balance type codes that are acceptable by year-end process */ protected List<String> getProcessableBalanceTypeCode(SystemOptions options) { List<String> processableBalanceTypeCodes = new ArrayList<String>(); processableBalanceTypeCodes.add(options.getActualFinancialBalanceTypeCd()); return processableBalanceTypeCodes; } /** * get the object type codes that are acceptable by year-end process * * @param options the given system options * @return the object type codes that are acceptable by year-end process */ protected List<String> getProcessableObjectTypeCodes(SystemOptions options) { List<String> processableObjectTypeCodes = new ArrayList<String>(); processableObjectTypeCodes.add(options.getFinObjTypeExpenditureexpCd()); processableObjectTypeCodes.add(options.getFinObjTypeExpNotExpendCode()); return processableObjectTypeCodes; } // fill the report writer with the collected data protected Map<String, Integer> constructReportSummary() { Map<String, Integer> reportSummary = new HashMap<String, Integer>(); reportSummary.put(LEDGER_BALANCE + "," + KFSConstants.OperationType.READ, 0); reportSummary.put(LEDGER_BALANCE + "," + KFSConstants.OperationType.SELECT, 0); reportSummary.put(LEDGER_BALANCE + "," + KFSConstants.OperationType.REPORT_ERROR, 0); reportSummary.put(ORIGN_ENTRY + "," + KFSConstants.OperationType.INSERT, 0); return reportSummary; } // fill the gl entry report writer with the collected data protected void fillStatisticsReportWriter(Map<String, Integer> glEntryReportSummary) { laborBalanceForwardReportWriterService.writeStatisticLine("NUMBER OF RECORDS READ %,9d", glEntryReportSummary.get(LEDGER_BALANCE + "," + KFSConstants.OperationType.READ)); laborBalanceForwardReportWriterService.writeStatisticLine("NUMBER OF RECORDS SELECTED %,9d", glEntryReportSummary.get(LEDGER_BALANCE + "," + KFSConstants.OperationType.SELECT)); laborBalanceForwardReportWriterService.writeStatisticLine("NUMBER OF RECORDS IN ERROR %,9d", glEntryReportSummary.get(LEDGER_BALANCE + "," + KFSConstants.OperationType.REPORT_ERROR)); laborBalanceForwardReportWriterService.writeStatisticLine("NUMBER OF RECORDS INSERTED %,9d", glEntryReportSummary.get(ORIGN_ENTRY + "," + KFSConstants.OperationType.INSERT)); } /** * Report out which significant parameters were used in this report. * @param runDate the date when this job was run * @param closingYear the fiscal year closed by this job * @param processedFundGroups the fund groups processed by this job * @param processedSubFundGroups the sub-fund groups processed by this job * @param originationCode the origination code of the posted entries * @param processedBalanceTypeCodes the balance type codes processed by this job * @param processedObjectTypeCodes the object type codes processed by this job * @param documentTypeCode the document type code of posted entries */ protected void fillParametersReportWriter(Date runDate, Integer closingYear, Collection<String> processedFundGroups, Collection<String> processedSubFundGroups, String originationCode, Collection<String> processedBalanceTypeCodes, Collection<String> processedObjectTypeCodes, String documentTypeCode) { laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", GeneralLedgerConstants.ANNUAL_CLOSING_TRANSACTION_DATE_PARM, runDate.toString()); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborConstants.YearEnd.OLD_FISCAL_YEAR, closingYear); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborConstants.YearEnd.FUND_GROUP_PROCESSED, StringUtils.join(processedFundGroups, ", ")); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborConstants.YearEnd.SUB_FUND_GROUP_PROCESSED, StringUtils.join(processedSubFundGroups, ", ")); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborConstants.YearEnd.ORIGINATION_CODE, originationCode); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborYearEndBalanceForwardServiceImpl.PROCESSED_BALANCE_TYPES_LABEL, StringUtils.join(processedBalanceTypeCodes, ", " )); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", LaborYearEndBalanceForwardServiceImpl.PROCESSED_OBJECT_TYPES_LABEL, StringUtils.join(processedObjectTypeCodes, ", " )); laborBalanceForwardReportWriterService.writeParameterLine("%32s %10s", GeneralLedgerConstants.ANNUAL_CLOSING_DOCUMENT_TYPE, documentTypeCode); laborBalanceForwardReportWriterService.pageBreak(); } // update the entry in the given report summary protected void updateReportSummary(Map<String, Integer> reportSummary, String destination, String operation) { String key = destination + "," + operation; if (reportSummary.containsKey(key)) { Integer count = reportSummary.get(key); reportSummary.put(key, count + 1); } else { reportSummary.put(key, 1); } } /** * get the document type code of the transaction posted by year-end process * * @return the document type code of the transaction posted by year-end process */ protected String getDocumentTypeCode() { return parameterService.getParameterValueAsString(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, GeneralLedgerConstants.ANNUAL_CLOSING_DOCUMENT_TYPE); } /** * get the origination code of the transaction posted by year-end process * * @return the origination code of the transaction posted by year-end process */ protected String getOriginationCode() { return parameterService.getParameterValueAsString(LaborYearEndBalanceForwardStep.class, YearEnd.ORIGINATION_CODE); } /** * get the description of the transaction posted by year-end process * * @return the description of the transaction posted by year-end process */ protected String getDescription() { return SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(LaborKeyConstants.MESSAGE_YEAR_END_TRANSACTION_DESCRIPTON); } /** * Sets the businessObjectService attribute value. * * @param businessObjectService The businessObjectService to set. */ public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } /** * Sets the dateTimeService attribute value. * * @param dateTimeService The dateTimeService to set. */ public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } /** * Sets the parameterService attribute value. * * @param parameterService The parameterService to set. */ public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } /** * Sets the laborLedgerBalanceService attribute value. * * @param laborLedgerBalanceService The laborLedgerBalanceService to set. */ public void setLaborLedgerBalanceService(LaborLedgerBalanceService laborLedgerBalanceService) { this.laborLedgerBalanceService = laborLedgerBalanceService; } /** * Sets the optionsService attribute value. * * @param optionsService The optionsService to set. */ public void setOptionsService(OptionsService optionsService) { this.optionsService = optionsService; } /** * Sets the batchFileDirectoryName attribute value. * * @param batchFileDirectoryName The batchFileDirectoryName to set. */ public void setBatchFileDirectoryName(String batchFileDirectoryName) { this.batchFileDirectoryName = batchFileDirectoryName; } /** * Sets the laborOutputSummaryReportWriterService attribute value. * * @param laborOutputSummaryReportWriterService The laborOutputSummaryReportWriterService to set. */ public void setLaborBalanceForwardReportWriterService(ReportWriterService laborBalanceForwardReportWriterService) { this.laborBalanceForwardReportWriterService = laborBalanceForwardReportWriterService; } }