/* * 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.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.io.Reader; import java.text.MessageFormat; 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.coa.businessobject.Account; import org.kuali.kfs.gl.batch.service.ReconciliationParserService; import org.kuali.kfs.gl.batch.service.impl.ExceptionCaughtStatus; import org.kuali.kfs.gl.batch.service.impl.FileReconBadLoadAbortedStatus; import org.kuali.kfs.gl.batch.service.impl.FileReconOkLoadOkStatus; import org.kuali.kfs.gl.batch.service.impl.ReconciliationBlock; import org.kuali.kfs.gl.report.LedgerSummaryReport; import org.kuali.kfs.gl.service.OriginEntryService; import org.kuali.kfs.gl.service.impl.EnterpriseFeederStatusAndErrorMessagesWrapper; import org.kuali.kfs.module.ld.LaborConstants; import org.kuali.kfs.module.ld.LaborKeyConstants; import org.kuali.kfs.module.ld.LaborPropertyConstants; import org.kuali.kfs.module.ld.batch.LaborEnterpriseFeedStep; import org.kuali.kfs.module.ld.batch.service.FileEnterpriseFeederHelperService; import org.kuali.kfs.module.ld.batch.service.ReconciliationService; import org.kuali.kfs.module.ld.businessobject.BenefitsCalculation; import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry; import org.kuali.kfs.module.ld.businessobject.PositionObjectBenefit; import org.kuali.kfs.module.ld.report.EnterpriseFeederReportData; import org.kuali.kfs.module.ld.service.LaborBenefitsCalculationService; import org.kuali.kfs.module.ld.service.LaborPositionObjectBenefitService; import org.kuali.kfs.module.ld.util.LaborOriginEntryFileIterator; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSParameterKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.Message; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.ReportWriterService; import org.kuali.kfs.sys.service.UniversityDateService; 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.kuali.rice.krad.util.ObjectUtils; /** * This class reads origin entries in a flat file format, reconciles them, and loads them into the origin entry table. * Note: the feeding algorithm of this service will read the data file twice to minimize memory usage. */ public class FileEnterpriseFeederHelperServiceImpl implements FileEnterpriseFeederHelperService { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FileEnterpriseFeederHelperServiceImpl.class); protected ReconciliationParserService reconciliationParserService; protected ReconciliationService reconciliationService; protected OriginEntryService originEntryService; protected ParameterService parameterService; protected LaborPositionObjectBenefitService laborPositionObjectBenefitService; protected LaborBenefitsCalculationService laborBenefitsCalculationService; protected BusinessObjectService businessObjectService; protected ConfigurationService configurationService; protected DateTimeService dateTimeService; /** * This method does the reading and the loading of reconciliation. Read * class description. This method DOES NOT handle the deletion of the done * file * * @param doneFile * a URL that must be present. The contents may be empty * @param dataFile * a URL to a flat file of origin entry rows. * @param reconFile * a URL to the reconciliation file. See the implementation of * {@link ReconciliationParserService} for file format. * @param originEntryGroup * the group into which the origin entries will be loaded * @param feederProcessName * the name of the feeder process * @param reconciliationTableId * the name of the block to use for reconciliation within the * reconciliation file * @param statusAndErrors * any status information should be stored within this object * @see org.kuali.module.gl.service.impl.FileEnterpriseFeederHelperService#feedOnFile(java.io.File, * java.io.File, java.io.File, * org.kuali.kfs.gl.businessobject.OriginEntryGroup) */ @Override public void feedOnFile(File doneFile, File dataFile, File reconFile, PrintStream enterpriseFeedPs, String feederProcessName, String reconciliationTableId, EnterpriseFeederStatusAndErrorMessagesWrapper statusAndErrors, LedgerSummaryReport ledgerSummaryReport, ReportWriterService errorStatisticsReport, EnterpriseFeederReportData feederReportData) { if ( LOG.isInfoEnabled() ) { LOG.info("Processing done file: " + doneFile.getAbsolutePath()); } List<Message> errorMessages = statusAndErrors.getErrorMessages(); BufferedReader dataFileReader = null; ReconciliationBlock reconciliationBlock = null; Reader reconReader = null; try { reconReader = new FileReader(reconFile); reconciliationBlock = reconciliationParserService.parseReconciliationBlock(reconReader, reconciliationTableId); } catch (IOException e) { LOG.error("IO Error occured trying to read the recon file.", e); errorMessages.add(new Message("IO Error occured trying to read the recon file.", Message.TYPE_FATAL)); reconciliationBlock = null; statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus()); throw new RuntimeException(e); } catch (RuntimeException e) { LOG.error("Error occured trying to parse the recon file.", e); errorMessages.add(new Message("Error occured trying to parse the recon file.", Message.TYPE_FATAL)); reconciliationBlock = null; statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus()); throw e; } finally { if (reconReader != null) { try { reconReader.close(); } catch (IOException e) { LOG.error("Error occured trying to close recon file: " + reconFile.getAbsolutePath(), e); } } } try { if (reconciliationBlock == null) { errorMessages.add(new Message("Unable to parse reconciliation file.", Message.TYPE_FATAL)); } else { dataFileReader = new BufferedReader(new FileReader(dataFile)); Iterator<LaborOriginEntry> fileIterator = new LaborOriginEntryFileIterator(dataFileReader, false); reconciliationService.reconcile(fileIterator, reconciliationBlock, errorMessages); fileIterator = null; dataFileReader.close(); dataFileReader = null; } if (reconciliationProcessSucceeded(errorMessages)) { dataFileReader = new BufferedReader(new FileReader(dataFile)); String line; int count = 0; // create an entry to temporarily parse each line as it comes in Map<String, List<LaborOriginEntry>> salaryBenefitOffsets = new HashMap<String, List<LaborOriginEntry>>(); List<LaborOriginEntry> entries = new ArrayList<LaborOriginEntry>(); boolean calculateOffsets = parameterService.getParameterValueAsBoolean(LaborEnterpriseFeedStep.class, LaborConstants.BenefitCalculation.LABOR_BENEFIT_CALCULATION_OFFSET_IND); String offsetDocTypes = null; Collection<String> offsetDocTypeList = parameterService.getParameterValuesAsString(LaborEnterpriseFeedStep.class, LaborConstants.BenefitCalculation.LABOR_BENEFIT_OFFSET_DOCTYPE); if( !offsetDocTypeList.isEmpty() ) { offsetDocTypes = "," + StringUtils.join(offsetDocTypeList, ','); } while ((line = dataFileReader.readLine()) != null) { try { LaborOriginEntry tempEntry = new LaborOriginEntry(); tempEntry.setFromTextFileForBatch(line, count); this.applyDefaultIfNecessary(tempEntry); feederReportData.incrementNumberOfRecordsRead(); feederReportData.addToTotalAmountRead(tempEntry.getTransactionLedgerEntryAmount()); enterpriseFeedPs.printf("%s\n", line); ledgerSummaryReport.summarizeEntry(tempEntry); feederReportData.incrementNumberOfRecordsWritten(); feederReportData.addToTotalAmountWritten(tempEntry.getTransactionLedgerEntryAmount()); List<LaborOriginEntry> benefitEntries = generateBenefits(tempEntry, errorStatisticsReport, feederReportData); for(LaborOriginEntry benefitEntry : benefitEntries) { enterpriseFeedPs.printf("%s\n", benefitEntry.getLine()); feederReportData.incrementNumberOfRecordsWritten(); feederReportData.addToTotalAmountWritten(benefitEntry.getTransactionLedgerEntryAmount()); //If the LABOR_BENEFIT_CALCULATION_OFFSET_IND system parameter is set to 'Y' //and the LABOR_BENEFIT_OFFSET_DOCTYPE system parameter is not empty //and the document type is in the LABOR_BENEFIT_OFFSET_DOCTYPE system parameter then //group together the benefit entries for the salary benefit offset calculation if(calculateOffsets && offsetDocTypes != null && offsetDocTypes.toUpperCase().contains("," + tempEntry.getFinancialDocumentTypeCode().toUpperCase() + ",")) { String key = tempEntry.getUniversityFiscalYear() + "_" + tempEntry.getChartOfAccountsCode() + "_" + tempEntry.getAccountNumber() + "_" + tempEntry.getFinancialObjectCode(); if(!salaryBenefitOffsets.containsKey(key)) { entries = new ArrayList<LaborOriginEntry>(); salaryBenefitOffsets.put(key, entries); } else { entries = salaryBenefitOffsets.get(key); } benefitEntry.setFinancialObjectCode(tempEntry.getFinancialObjectCode()); benefitEntry.setAccountNumber(tempEntry.getAccountNumber()); benefitEntry.setChartOfAccountsCode(tempEntry.getChartOfAccountsCode()); benefitEntry.setUniversityFiscalYear(tempEntry.getUniversityFiscalYear()); benefitEntry.setUniversityFiscalPeriodCode(tempEntry.getUniversityFiscalPeriodCode()); entries.add(benefitEntry); } } } catch (Exception e) { throw new IOException(e); } count++; } //If the LABOR_BENEFIT_CALCULATION_OFFSET_IND system parameter is set to 'Y' //and the LABOR_BENEFIT_OFFSET_DOCTYPE system parameter is not empty //then create the salary benefit offset entries if(calculateOffsets && offsetDocTypes != null) { for(List<LaborOriginEntry> entryList : salaryBenefitOffsets.values()) { if(entryList != null && entryList.size() > 0) { LaborOriginEntry offsetEntry = new LaborOriginEntry(); KualiDecimal total = KualiDecimal.ZERO; //Loop through all the benefit entries to calculate the total for the salary benefit offset entry for(LaborOriginEntry entry : entryList) { if(entry.getTransactionDebitCreditCode().equalsIgnoreCase(KFSConstants.GL_DEBIT_CODE)) { total = total.add(entry.getTransactionLedgerEntryAmount()); } else { total = total.subtract(entry.getTransactionLedgerEntryAmount()); } } //No need to process for the salary benefit offset if the total is 0 if(total.isNonZero()) { //Lookup the position object benefit to get the object benefit type code Collection<PositionObjectBenefit> positionObjectBenefits = laborPositionObjectBenefitService.getActivePositionObjectBenefits(entryList.get(0).getUniversityFiscalYear(), entryList.get(0).getChartOfAccountsCode(), entryList.get(0).getFinancialObjectCode()); LaborOriginEntry entry = entryList.get(0); if (positionObjectBenefits == null || positionObjectBenefits.isEmpty()) { writeMissingBenefitsTypeError(entry, errorStatisticsReport, feederReportData); } else { for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) { Map<String, Object> fieldValues = new HashMap<String, Object>(3); fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, entry.getUniversityFiscalYear()); fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, entry.getChartOfAccountsCode()); fieldValues.put(LaborPropertyConstants.POSITION_BENEFIT_TYPE_CODE, positionObjectBenefit.getFinancialObjectBenefitsTypeCode()); //Lookup the benefit calculation to get the offset account number and object code BenefitsCalculation benefitsCalculation = businessObjectService.findByPrimaryKey(BenefitsCalculation.class, fieldValues); offsetEntry.setAccountNumber(benefitsCalculation.getAccountCodeOffset()); offsetEntry.setFinancialObjectCode(benefitsCalculation.getObjectCodeOffset()); } } //Set all the fields required to process through the scrubber and poster jobs offsetEntry.setUniversityFiscalPeriodCode(entry.getUniversityFiscalPeriodCode()); offsetEntry.setChartOfAccountsCode(entry.getChartOfAccountsCode()); offsetEntry.setUniversityFiscalYear(entry.getUniversityFiscalYear()); offsetEntry.setTransactionLedgerEntryDescription("GENERATED BENEFIT OFFSET"); offsetEntry.setFinancialSystemOriginationCode("RN"); offsetEntry.setDocumentNumber(dateTimeService.toString(dateTimeService.getCurrentDate(), "yyyyMMddhhmmssSSS")); //Only + signed amounts offsetEntry.setTransactionLedgerEntryAmount(total.abs()); //Credit if the total is positive and Debit of the total is negative if(total.isGreaterThan(new KualiDecimal(0))) { offsetEntry.setTransactionDebitCreditCode("C"); } else if(total.isLessThan(new KualiDecimal(0))) { offsetEntry.setTransactionDebitCreditCode("D"); } //Set the doc type to the value in the LABOR_BENEFIT_OFFSET_DOCTYPE system parameter (the first value if there is a list) String docTypeCode = offsetDocTypes; if (offsetDocTypes.contains(",")) { String[] splits = offsetDocTypes.split(","); for(String split : splits) { if(!StringUtils.isEmpty(split)) { docTypeCode = split; break; } } } offsetEntry.setFinancialDocumentTypeCode(docTypeCode); //Write the offset entry to the file enterpriseFeedPs.printf("%s\n", offsetEntry.getLine()); } } } } dataFileReader.close(); dataFileReader = null; statusAndErrors.setStatus(new FileReconOkLoadOkStatus()); } else { statusAndErrors.setStatus(new FileReconBadLoadAbortedStatus()); } } catch (Exception e) { LOG.error("Caught exception when reconciling/loading done file: " + doneFile, e); statusAndErrors.setStatus(new ExceptionCaughtStatus()); errorMessages.add(new Message("Caught exception attempting to reconcile/load done file: " + doneFile + ". File contents are NOT loaded", Message.TYPE_FATAL)); // re-throw the exception rather than returning a value so that Spring will auto-rollback if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { // Spring only rolls back when throwing a runtime exception (by default), so we throw a new exception throw new RuntimeException(e); } } finally { if (dataFileReader != null) { try { dataFileReader.close(); } catch (IOException e) { LOG.error("IO Exception occured trying to close connection to the data file", e); errorMessages.add(new Message("IO Exception occured trying to close connection to the data file", Message.TYPE_FATAL)); } } } } /** * <p> * Generates the benefit * <code>LaborOriginEntry<code> lines for the given wage entry * </p> * <p> * Steps for generating benefit lines are: * </p> * <ol> * <li>Determine balance type of entry line and whether benefits should be * generated (based on system parameter)</li> * <li>Find the benefit rate category code for the salary account</li> * <li>Find the benefit type for the year, chart, and object code</li> * <li>Find the benefit calculations based on benefit type and category code * (if not blank)</li> * <li>For each benefit calculation, generate a benefit line with the * benefit object code and benefit amount</li> * </ol> * * @param wageEntry * - the * <code>LaborOriginEntry<code> for wages that benefits should be generated for * @param errorStatisticsReport * - the error report writer for which entries that we cannot * generate benefits for will be summarized in * @param feederReportData * - holds statistic counts * @return the generated benefit entries as a List<LaborOriginEntry> */ protected List<LaborOriginEntry> generateBenefits(LaborOriginEntry wageEntry, ReportWriterService errorStatisticsReport, EnterpriseFeederReportData feederReportData) { List<LaborOriginEntry> benefits = new ArrayList<LaborOriginEntry>(); String balanceTypeCode = wageEntry.getFinancialBalanceTypeCode(); boolean isActual = KFSConstants.BALANCE_TYPE_ACTUAL.equals(balanceTypeCode) || KFSConstants.BALANCE_TYPE_A21.equals(balanceTypeCode); boolean isEncumbrance = KFSConstants.ENCUMB_UPDT_DOCUMENT_CD.equals(wageEntry .getTransactionEncumbranceUpdateCode()) || KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD.equals(wageEntry .getTransactionEncumbranceUpdateCode()); if (isActual) { feederReportData.incrementNumberOfBalanceTypeActualsRead(); // check parameter that indicates whether benefits should be generated for actual balance types boolean generateActualBenefits = parameterService.getParameterValueAsBoolean(LaborEnterpriseFeedStep.class, LaborConstants.BenefitCalculation.GENERATE_FRINGE_BENEFIT_PARAMETER); if (!generateActualBenefits) { LOG.info("Skipping benefit generation due to parameter disabling benefit generation for actual balance type"); return benefits; } } if (isEncumbrance) { feederReportData.incrementNumberOfBalanceTypeEncumbranceRead(); // check parameter that indicates whether benefits should be generated for encumbrance balance types boolean generateEncumbranceBenefits = parameterService.getParameterValueAsBoolean(LaborEnterpriseFeedStep.class, LaborConstants.BenefitCalculation.GENERATE_FRINGE_BENEFIT_ENCUMBRANCE_PARAMETER); if (!generateEncumbranceBenefits) { LOG.info("Skipping benefit generation due to parameter disabling benefit generation for encumbrance balance type"); return benefits; } } // get the benefit rate category code for the entries account number String benefitRateCategoryCode = laborBenefitsCalculationService.getBenefitRateCategoryCode( wageEntry.getChartOfAccountsCode(), wageEntry.getAccountNumber(), wageEntry.getSubAccountNumber()); String defaultLaborBenefitsRateCategoryCode = StringUtils.trimToEmpty( parameterService.getParameterValueAsString(Account.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND) ); //if sysParam == Y then use the Labor Benefit Rate Category Code to help determine the fringe benefit rate boolean useBenefitRateCategoryCode = parameterService.getParameterValueAsBoolean(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, LaborConstants.BenefitCalculation.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_PARAMETER); // get benefit types for the entry object code and for each calculation generation an origin entry Collection<PositionObjectBenefit> positionObjectBenefits = laborPositionObjectBenefitService .getActivePositionObjectBenefits(wageEntry.getUniversityFiscalYear(), wageEntry.getChartOfAccountsCode(), wageEntry.getFinancialObjectCode()); if (positionObjectBenefits == null || positionObjectBenefits.isEmpty()) { writeMissingBenefitsTypeError(wageEntry, errorStatisticsReport, feederReportData); } for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) { BenefitsCalculation benefitsCalculation = null; if (useBenefitRateCategoryCode) { benefitsCalculation = laborBenefitsCalculationService.getBenefitsCalculation( wageEntry.getUniversityFiscalYear(), wageEntry.getChartOfAccountsCode(), positionObjectBenefit.getFinancialObjectBenefitsTypeCode(), benefitRateCategoryCode); }else{ benefitsCalculation = laborBenefitsCalculationService.getBenefitsCalculation( wageEntry.getUniversityFiscalYear(), wageEntry.getChartOfAccountsCode(), positionObjectBenefit.getFinancialObjectBenefitsTypeCode(), defaultLaborBenefitsRateCategoryCode); } if(ObjectUtils.isNull(benefitsCalculation) || !benefitsCalculation.isActive()){ continue; } LaborOriginEntry benefitEntry = new LaborOriginEntry(wageEntry); benefitEntry.setFinancialObjectCode(benefitsCalculation.getPositionFringeBenefitObjectCode()); // calculate the benefit amount (ledger amt * (benfit pct/100) ) KualiDecimal fringeBenefitPercent = benefitsCalculation.getPositionFringeBenefitPercent(); KualiDecimal fringeBenefitAmount = fringeBenefitPercent.multiply( wageEntry.getTransactionLedgerEntryAmount()).divide(KFSConstants.ONE_HUNDRED.kualiDecimalValue()); benefitEntry.setTransactionLedgerEntryAmount(fringeBenefitAmount); benefits.add(benefitEntry); // increment count for successfully generated benefit line if (isActual) { feederReportData.incrementNumberOfFringeActualsGenerated(); } else { feederReportData.incrementNumberOfFringeEncumbrancesGenerated(); } } return benefits; } protected void writeMissingBenefitsTypeError(LaborOriginEntry wageEntry, ReportWriterService errorStatisticsReport, EnterpriseFeederReportData feederReportData) { String benefitKey = wageEntry.getUniversityFiscalYear() + "-" + wageEntry.getChartOfAccountsCode() + "-" + wageEntry.getFinancialObjectCode(); String message = configurationService .getPropertyValueAsString(LaborKeyConstants.EnterpriseFeed.ERROR_BENEFIT_TYPE_NOT_FOUND); message = MessageFormat.format(message, benefitKey); feederReportData.incrementNumberOfErrorEncountered(); LOG.error(message); errorStatisticsReport.writeError(wageEntry, new Message(message, Message.TYPE_FATAL)); } protected void writeMissingBenefitsCalculationError(LaborOriginEntry wageEntry, ReportWriterService errorStatisticsReport, EnterpriseFeederReportData feederReportData, String benefitsTypeCode, String benefitRateCategoryCode, boolean useBenefitRateCategoryCode) { String benefitKey = wageEntry.getUniversityFiscalYear() + "-" + wageEntry.getFinancialObjectCode() + "-" + benefitsTypeCode; if (useBenefitRateCategoryCode) { benefitKey += "-" + benefitRateCategoryCode; } String message = configurationService .getPropertyValueAsString(LaborKeyConstants.EnterpriseFeed.ERROR_BENEFIT_CALCULATION_NOT_FOUND); message = MessageFormat.format(message, benefitKey); feederReportData.incrementNumberOfErrorEncountered(); LOG.error(message); errorStatisticsReport.writeError(wageEntry, new Message(message, Message.TYPE_FATAL)); } /** * Returns whether the reconciliation process succeeded by looking at the reconciliation error messages For this implementation, * the reconciliation does not succeed if at least one of the error messages in the list has a type of * {@link Message#TYPE_FATAL} * * @param errorMessages a List of errorMessages * @return true if any of those error messages were fatal */ protected boolean reconciliationProcessSucceeded(List<Message> errorMessages) { for (Message message : errorMessages) { if (message.getType() == Message.TYPE_FATAL) { return false; } } return true; } /** * apply default values to the fields if their values are not provided */ protected void applyDefaultIfNecessary(LaborOriginEntry laborOriginEntry) { // apply the current fiscal year if it is not provided if(ObjectUtils.isNull(laborOriginEntry.getUniversityFiscalYear())){ UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class); laborOriginEntry.setUniversityFiscalYear(universityDateService.getCurrentFiscalYear()); } } public void setReconciliationParserService(ReconciliationParserService reconciliationParserService) { this.reconciliationParserService = reconciliationParserService; } public void setReconciliationService(ReconciliationService reconciliationService) { this.reconciliationService = reconciliationService; } public void setOriginEntryService(OriginEntryService originEntryService) { this.originEntryService = originEntryService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public void setLaborPositionObjectBenefitService(LaborPositionObjectBenefitService laborPositionObjectBenefitService) { this.laborPositionObjectBenefitService = laborPositionObjectBenefitService; } public void setLaborBenefitsCalculationService(LaborBenefitsCalculationService laborBenefitsCalculationService) { this.laborBenefitsCalculationService = laborBenefitsCalculationService; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } }