/* * 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 java.io.File; import java.io.FilenameFilter; import java.math.BigDecimal; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.kuali.kfs.gl.GeneralLedgerConstants; import org.kuali.kfs.gl.batch.service.BalancingService; import org.kuali.kfs.gl.batch.service.PosterService; import org.kuali.kfs.gl.batch.service.impl.BalancingServiceBaseImpl; import org.kuali.kfs.gl.businessobject.Balance; import org.kuali.kfs.gl.businessobject.Entry; import org.kuali.kfs.gl.businessobject.LedgerBalanceHistory; import org.kuali.kfs.gl.businessobject.OriginEntryInformation; import org.kuali.kfs.module.ld.LaborConstants; import org.kuali.kfs.module.ld.LaborKeyConstants; import org.kuali.kfs.module.ld.batch.LaborBalancingStep; import org.kuali.kfs.module.ld.businessobject.LaborBalanceHistory; import org.kuali.kfs.module.ld.businessobject.LaborEntryHistory; import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry; import org.kuali.kfs.module.ld.businessobject.LedgerBalance; import org.kuali.kfs.module.ld.businessobject.LedgerEntry; import org.kuali.kfs.sys.FileUtil; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.Message; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.transaction.annotation.Transactional; /** * Service implementation of BalancingService for Labor balancing */ @Transactional public class LaborBalancingServiceImpl extends BalancingServiceBaseImpl<LaborEntryHistory, LaborBalanceHistory> implements BalancingService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborBalancingServiceImpl.class); protected File laborPosterInputFile = null; protected File laborPosterErrorOutputFile = null; @Override public boolean runBalancing() { // clear out the file cache, otherwise, it won't update the history tables with the latest poster files // therefore, it will use the files that were first used when the balancing job was run when the JVM started, and that'll cause out of balance errors clearPosterFileCache(); return super.runBalancing(); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getPosterInputFile() */ @Override public File getPosterInputFile() { // avoid running scanning logic on file system if (laborPosterInputFile != null) { return laborPosterInputFile; } FilenameFilter filenameFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return (name.startsWith(LaborConstants.BatchFileSystem.POSTER_INPUT_FILE) && name.endsWith(GeneralLedgerConstants.BatchFileSystem.EXTENSION)); } }; laborPosterInputFile = FileUtil.getNewestFile(new File(batchFileDirectoryName), filenameFilter); return laborPosterInputFile; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getPosterErrorOutputFile() */ @Override public File getPosterErrorOutputFile() { // avoid running scanning logic on file system if (laborPosterErrorOutputFile != null) { return laborPosterErrorOutputFile; } FilenameFilter filenameFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return (name.startsWith(LaborConstants.BatchFileSystem.POSTER_ERROR_OUTPUT_FILE) && name.endsWith(GeneralLedgerConstants.BatchFileSystem.EXTENSION)); } }; laborPosterErrorOutputFile = FileUtil.getNewestFile(new File(batchFileDirectoryName), filenameFilter); return laborPosterErrorOutputFile; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getPastFiscalYearsToConsider() */ @Override public int getPastFiscalYearsToConsider() { return Integer.parseInt(parameterService.getParameterValueAsString(LaborBalancingStep.class, LaborConstants.Balancing.NUMBER_OF_PAST_FISCAL_YEARS_TO_INCLUDE)); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getComparisonFailuresToPrintPerReport() */ @Override public int getComparisonFailuresToPrintPerReport() { return Integer.parseInt(parameterService.getParameterValueAsString(LaborBalancingStep.class, LaborConstants.Balancing.NUMBER_OF_COMPARISON_FAILURES_TO_PRINT_PER_REPORT)); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getShortTableLabel(java.lang.String) */ @Override public String getShortTableLabel(String businessObjectName) { Map<String, String> names = new HashMap<String, String>(); names.put((Entry.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_ENTRY_LABEL)); names.put((LaborEntryHistory.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_ENTRY_LABEL)); names.put((Balance.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_BALANCE_LABEL)); names.put((LaborBalanceHistory.class).getSimpleName(), kualiConfigurationService.getPropertyValueAsString(LaborKeyConstants.Balancing.REPORT_BALANCE_LABEL)); return names.get(businessObjectName) == null ? kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.REPORT_UNKNOWN_LABEL) : names.get(businessObjectName); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getOriginEntry(java.lang.String, int) */ @Override public OriginEntryInformation getOriginEntry(String inputLine, int lineNumber) { LaborOriginEntry originEntry = new LaborOriginEntry(); originEntry.setFromTextFileForBatch(inputLine, lineNumber); return originEntry; } /** * @see org.kuali.kfs.gl.batch.service.impl.BalancingServiceBaseImpl#updateHistoriesHelper(java.lang.Integer, java.lang.Integer, java.io.File, java.io.File) */ @Override protected int updateHistoriesHelper(Integer postMode, Integer startUniversityFiscalYear, File inputFile, File errorFile) { if(postMode == PosterService.MODE_ENTRIES){ return super.updateHistoriesHelper(postMode, startUniversityFiscalYear, inputFile, errorFile); } return 0; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#updateEntryHistory(org.kuali.kfs.gl.businessobject.OriginEntryInformation) * @see org.kuali.kfs.module.ld.batch.service.impl.LaborPosterServiceImpl#postAsLedgerEntry(org.kuali.kfs.gl.businessobject.Transaction, int, java.util.Date) */ @Override public void updateEntryHistory(Integer postMode, OriginEntryInformation originEntry) { if(postMode == PosterService.MODE_ENTRIES){ // TODO Retrieve and update 1 by 1? Is a HashMap or cache better so that storing only occurs once at the end? LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry; LaborEntryHistory ledgerEntryHistory = new LaborEntryHistory(laborOriginEntry); LaborEntryHistory retrievedLedgerEntryHistory = (LaborEntryHistory) businessObjectService.retrieve(ledgerEntryHistory); if(ObjectUtils.isNotNull(retrievedLedgerEntryHistory)) { ledgerEntryHistory = retrievedLedgerEntryHistory; } ledgerEntryHistory.addAmount(laborOriginEntry.getTransactionLedgerEntryAmount()); businessObjectService.save(ledgerEntryHistory); } } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#updateBalanceHistory(org.kuali.kfs.gl.businessobject.OriginEntryInformation) * @see org.kuali.kfs.module.ld.batch.service.impl.LaborPosterServiceImpl#updateLedgerBalance(org.kuali.kfs.gl.businessobject.Transaction, int, java.util.Date) */ @Override public void updateBalanceHistory(Integer postMode, OriginEntryInformation originEntry) { if(postMode == PosterService.MODE_ENTRIES){ // TODO Retrieve and update 1 by 1? Is a HashMap or cache better so that storing only occurs once at the end? LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry; LaborBalanceHistory ledgerBalanceHistory = new LaborBalanceHistory(laborOriginEntry); LaborBalanceHistory retrievedLedgerBalanceHistory = (LaborBalanceHistory) businessObjectService.retrieve(ledgerBalanceHistory); if(ObjectUtils.isNotNull(retrievedLedgerBalanceHistory)) { ledgerBalanceHistory = retrievedLedgerBalanceHistory; } // Make sure the amount update properly recognized debit / credit logic. This is modeled after // LaborPosterServiceImpl#updateLedgerBalance KualiDecimal amount = laborOriginEntry.getTransactionLedgerEntryAmount(); laborOriginEntry.refreshReferenceObject(KFSPropertyConstants.BALANCE_TYPE); laborOriginEntry.refreshReferenceObject(KFSPropertyConstants.OBJECT_TYPE); if (laborOriginEntry.getBalanceType().isFinancialOffsetGenerationIndicator()) { if (!laborOriginEntry.getTransactionDebitCreditCode().equals(laborOriginEntry.getObjectType().getFinObjectTypeDebitcreditCd())) { amount = amount.negated(); } } ledgerBalanceHistory.addAmount(laborOriginEntry.getUniversityFiscalPeriodCode(), amount); businessObjectService.save(ledgerBalanceHistory); } } /** * Compares entries in the Balance and BalanceHistory tables to ensure the amounts match. * @return count is compare failures */ @Override protected Integer compareBalanceHistory() { Integer countComparisionFailures = 0; String balanceTable = persistenceStructureService.getTableName(LedgerBalance.class); String historyTable = persistenceStructureService.getTableName(balanceHistoryPersistentClass); List<LedgerBalance> data = ledgerEntryBalanceCachingDao.compareBalanceHistory(balanceTable, historyTable, getFiscalYear()); if (!data.isEmpty()) { for (Iterator<LedgerBalance> itr = data.iterator(); itr.hasNext();) { LaborBalanceHistory balance = createBalanceFromMap((Map<String, Object>)itr.next()); countComparisionFailures++; if (countComparisionFailures <= this.getComparisonFailuresToPrintPerReport()) { reportWriterService.writeError(balance, new Message(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_RECORD_FAILED_BALANCING), Message.TYPE_WARNING, balance.getClass().getSimpleName())); } } } return countComparisionFailures; } /** * Compares entries in the Entry and EntryHistory tables to ensure the amounts match. * @return count is compare failures */ @Override protected Integer compareEntryHistory() { Integer countComparisionFailures = 0; String entryTable = persistenceStructureService.getTableName(LedgerEntry.class); String historyTable = persistenceStructureService.getTableName(entryHistoryPersistentClass); List<LedgerEntry> data = ledgerEntryBalanceCachingDao.compareEntryHistory(entryTable, historyTable, getFiscalYear()); if (!data.isEmpty()) { for (Iterator<LedgerEntry> itr = data.iterator(); itr.hasNext();) { LaborEntryHistory entry = createEntryHistoryFromMap((Map<String, Object>)itr.next()); countComparisionFailures++; if (countComparisionFailures <= this.getComparisonFailuresToPrintPerReport()) { reportWriterService.writeError(entry, new Message(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_RECORD_FAILED_BALANCING), Message.TYPE_WARNING, entry.getClass().getSimpleName())); } } } return countComparisionFailures; } /** * * @see org.kuali.kfs.gl.batch.service.BalancingService#clearBalanceHistory() */ @Override public void clearHistories() { Map<String, Object> fieldValues = new HashMap<String, Object>(); businessObjectService.deleteMatching(LaborEntryHistory.class, fieldValues); businessObjectService.deleteMatching(LaborBalanceHistory.class, fieldValues); reportWriterService.writeFormattedMessageLine(kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.Balancing.MESSAGE_BATCH_BALANCING_HISTORY_PURGED)); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getFilenames() */ @Override public String getFilenames() { return (this.laborPosterInputFile == null ? null : this.laborPosterInputFile.getName()) + "\n" + (this.laborPosterErrorOutputFile == null ? null : this.laborPosterErrorOutputFile.getName()); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getBalance(org.kuali.kfs.gl.businessobject.LedgerBalanceHistory) */ @Override public Balance getBalance(LedgerBalanceHistory ledgerBalanceHistory) { LedgerBalance ledgerBalance = new LedgerBalance((LaborBalanceHistory) ledgerBalanceHistory); return (LedgerBalance) businessObjectService.retrieve(ledgerBalance); } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#clearPosterFileCache() */ @Override public void clearPosterFileCache() { this.laborPosterInputFile = null; this.laborPosterErrorOutputFile = null; } protected LaborBalanceHistory createBalanceFromMap(Map<String, Object> map) { LaborBalanceHistory balance = new LaborBalanceHistory(); balance.setUniversityFiscalYear(((BigDecimal)(map.get(GeneralLedgerConstants.ColumnNames.UNIVERSITY_FISCAL_YEAR))).intValue()); balance.setChartOfAccountsCode((String)map.get(GeneralLedgerConstants.ColumnNames.CHART_OF_ACCOUNTS_CODE)); balance.setAccountNumber((String)map.get(GeneralLedgerConstants.ColumnNames.ACCOUNT_NUMBER)); balance.setSubAccountNumber((String)map.get(GeneralLedgerConstants.ColumnNames.SUB_ACCOUNT_NUMBER)); balance.setObjectCode((String)map.get(GeneralLedgerConstants.ColumnNames.OBJECT_CODE)); balance.setSubObjectCode((String)map.get(GeneralLedgerConstants.ColumnNames.SUB_OBJECT_CODE)); balance.setBalanceTypeCode((String)map.get(GeneralLedgerConstants.ColumnNames.BALANCE_TYPE_CODE)); balance.setObjectTypeCode((String)map.get(GeneralLedgerConstants.ColumnNames.OBJECT_TYPE_CODE)); balance.setEmplid((String)map.get(LaborConstants.ColumnNames.EMPLOYEE_IDENTIFIER)); balance.setPositionNumber((String)map.get(LaborConstants.ColumnNames.POSITION_NUMBER)); balance.setAccountLineAnnualBalanceAmount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.ACCOUNTING_LINE_ACTUALS_BALANCE_AMOUNT))); balance.setContractsGrantsBeginningBalanceAmount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.CONTRACT_AND_GRANTS_BEGINNING_BALANCE))); balance.setBeginningBalanceLineAmount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.BEGINNING_BALANCE))); balance.setMonth1Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_1_ACCT_AMT))); balance.setMonth2Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_2_ACCT_AMT))); balance.setMonth3Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_3_ACCT_AMT))); balance.setMonth4Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_4_ACCT_AMT))); balance.setMonth5Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_5_ACCT_AMT))); balance.setMonth6Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_6_ACCT_AMT))); balance.setMonth7Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_7_ACCT_AMT))); balance.setMonth8Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_8_ACCT_AMT))); balance.setMonth9Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_9_ACCT_AMT))); balance.setMonth10Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_10_ACCT_AMT))); balance.setMonth11Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_11_ACCT_AMT))); balance.setMonth12Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_12_ACCT_AMT))); balance.setMonth13Amount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.MONTH_13_ACCT_AMT))); return balance; } protected LaborEntryHistory createEntryHistoryFromMap(Map<String, Object> map) { LaborEntryHistory entry = new LaborEntryHistory(); entry.setUniversityFiscalYear(((BigDecimal)(map.get(GeneralLedgerConstants.ColumnNames.UNIVERSITY_FISCAL_YEAR))).intValue()); entry.setChartOfAccountsCode((String)map.get(GeneralLedgerConstants.ColumnNames.CHART_OF_ACCOUNTS_CODE)); entry.setFinancialObjectCode((String)map.get(GeneralLedgerConstants.ColumnNames.OBJECT_CODE)); entry.setFinancialBalanceTypeCode((String)map.get(GeneralLedgerConstants.ColumnNames.BALANCE_TYPE_CODE)); entry.setUniversityFiscalPeriodCode((String)map.get(GeneralLedgerConstants.ColumnNames.FISCAL_PERIOD_CODE)); entry.setTransactionDebitCreditCode((String)map.get(GeneralLedgerConstants.ColumnNames.TRANSACTION_DEBIT_CREDIT_CD)); entry.setTransactionLedgerEntryAmount(convertBigDecimalToKualiDecimal((BigDecimal)map.get(GeneralLedgerConstants.ColumnNames.TRANSACTION_LEDGER_ENTRY_AMOUNT))); return entry; } protected KualiDecimal convertBigDecimalToKualiDecimal(BigDecimal biggy) { if (ObjectUtils.isNull(biggy)) { return new KualiDecimal(0); } else { return new KualiDecimal(biggy); } } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getReversalInputFile() */ @Override public File getReversalInputFile(){ return null; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getReversalErrorOutputFile() */ @Override public File getReversalErrorOutputFile(){ return null; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getICRInputFile() */ @Override public File getICRInputFile(){ return null; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getICRErrorOutputFile() */ @Override public File getICRErrorOutputFile(){ return null; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getICREncumbranceInputFile() */ @Override public File getICREncumbranceInputFile(){ return null; } /** * @see org.kuali.kfs.gl.batch.service.BalancingService#getICREncumbranceErrorOutputFile() */ @Override public File getICREncumbranceErrorOutputFile(){ return null; } }