001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.ld.util;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.HashSet;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.kuali.kfs.module.ld.LaborConstants;
028    import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine;
029    import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine;
030    import org.kuali.kfs.module.ld.businessobject.ExpenseTransferTargetAccountingLine;
031    import org.kuali.kfs.module.ld.businessobject.LaborLedgerPendingEntry;
032    import org.kuali.kfs.module.ld.businessobject.PositionObjectBenefit;
033    import org.kuali.kfs.module.ld.document.LaborLedgerPostingDocument;
034    import org.kuali.kfs.module.ld.document.service.LaborPendingEntryConverterService;
035    import org.kuali.kfs.module.ld.service.LaborBenefitsCalculationService;
036    import org.kuali.kfs.module.ld.service.LaborPositionObjectBenefitService;
037    import org.kuali.kfs.sys.KFSPropertyConstants;
038    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
039    import org.kuali.kfs.sys.context.SpringContext;
040    import org.kuali.rice.kns.util.KualiDecimal;
041    import org.kuali.rice.kns.util.ObjectUtils;
042    
043    /**
044     * This class is used to help generating pending entries for the given labor documents
045     */
046    public class LaborPendingEntryGenerator {
047        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborPendingEntryGenerator.class);
048    
049        /**
050         * generate the expense pending entries based on the given document and accouting line
051         * 
052         * @param document the given accounting document
053         * @param accountingLine the given accounting line
054         * @param sequenceHelper the given squence helper
055         * @return a set of expense pending entries
056         */
057        public static List<LaborLedgerPendingEntry> generateExpensePendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
058            List<LaborLedgerPendingEntry> expensePendingEntries = new ArrayList<LaborLedgerPendingEntry>();
059            LaborLedgerPendingEntry expensePendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpensePendingEntry(document, accountingLine, sequenceHelper);
060            expensePendingEntries.add(expensePendingEntry);
061    
062            // if the AL's pay FY and period do not match the University fiscal year and period need to create a reversal entry for
063            // current period
064            if (!isAccountingLinePayFYPeriodMatchesUniversityPayFYPeriod(document, accountingLine)) {
065                LaborLedgerPendingEntry expenseA21PendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpenseA21PendingEntry(document, accountingLine, sequenceHelper);
066                expensePendingEntries.add(expenseA21PendingEntry);
067    
068                LaborLedgerPendingEntry expenseA21ReversalPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getExpenseA21ReversalPendingEntry(document, accountingLine, sequenceHelper);
069                expensePendingEntries.add(expenseA21ReversalPendingEntry);
070            }
071    
072            return expensePendingEntries;
073        }
074    
075        /**
076         * generate the benefit pending entries based on the given document and accounting line
077         * 
078         * @param document the given accounting document
079         * @param accountingLine the given accounting line
080         * @param sequenceHelper the given squence helper
081         * @return a set of benefit pending entries
082         */
083        public static List<LaborLedgerPendingEntry> generateBenefitPendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
084            accountingLine.refreshReferenceObject(KFSPropertyConstants.LABOR_OBJECT);
085            if (ObjectUtils.isNull(accountingLine.getLaborObject())) {
086                return null;
087            }
088    
089            String FringeOrSalaryCode = accountingLine.getLaborObject().getFinancialObjectFringeOrSalaryCode();
090            if (!LaborConstants.SalaryExpenseTransfer.LABOR_LEDGER_SALARY_CODE.equals(FringeOrSalaryCode)) {
091                return null;
092            }
093    
094            Integer payrollFiscalyear = accountingLine.getPayrollEndDateFiscalYear();
095            String chartOfAccountsCode = accountingLine.getChartOfAccountsCode();
096            String objectCode = accountingLine.getFinancialObjectCode();
097            Collection<PositionObjectBenefit> positionObjectBenefits = SpringContext.getBean(LaborPositionObjectBenefitService.class).getPositionObjectBenefits(payrollFiscalyear, chartOfAccountsCode, objectCode);
098    
099            List<LaborLedgerPendingEntry> benefitPendingEntries = new ArrayList<LaborLedgerPendingEntry>();
100            for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) {
101                String fringeBenefitObjectCode = positionObjectBenefit.getBenefitsCalculation().getPositionFringeBenefitObjectCode();
102    
103                KualiDecimal benefitAmount = SpringContext.getBean(LaborBenefitsCalculationService.class).calculateFringeBenefit(positionObjectBenefit, accountingLine.getAmount());
104                if (benefitAmount.isNonZero()) {
105                    List<LaborLedgerPendingEntry> pendingEntries = generateBenefitPendingEntries(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode);
106                    benefitPendingEntries.addAll(pendingEntries);
107                }
108            }
109    
110            return benefitPendingEntries;
111        }
112    
113        /**
114         * generate the benefit pending entries with the given benefit amount and finge benefit object code based on the given document
115         * and accouting line
116         * 
117         * @param document the given accounting document
118         * @param accountingLine the given accounting line
119         * @param sequenceHelper the given squence helper
120         * @param benefitAmount the given benefit amount
121         * @param fringeBenefitObjectCode the given finge benefit object code
122         * @return a set of benefit pending entries with the given benefit amount and finge benefit object code
123         */
124        public static List<LaborLedgerPendingEntry> generateBenefitPendingEntries(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, KualiDecimal benefitAmount, String fringeBenefitObjectCode) {
125            List<LaborLedgerPendingEntry> benefitPendingEntries = new ArrayList<LaborLedgerPendingEntry>();
126            LaborLedgerPendingEntry benefitPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitPendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode);
127            benefitPendingEntries.add(benefitPendingEntry);
128    
129            // if the AL's pay FY and period do not match the University fiscal year and period
130            if (!isAccountingLinePayFYPeriodMatchesUniversityPayFYPeriod(document, accountingLine)) {
131                LaborLedgerPendingEntry benefitA21PendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitA21PendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode);
132                benefitPendingEntries.add(benefitA21PendingEntry);
133    
134                LaborLedgerPendingEntry benefitA21ReversalPendingEntry = SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitA21ReversalPendingEntry(document, accountingLine, sequenceHelper, benefitAmount, fringeBenefitObjectCode);
135                benefitPendingEntries.add(benefitA21ReversalPendingEntry);
136            }
137    
138            return benefitPendingEntries;
139        }
140    
141        /**
142         * generate the benefit clearing pending entries with the given benefit amount and fringe benefit object code based on the given
143         * document and accouting line
144         * 
145         * @param document the given accounting document
146         * @param sequenceHelper the given squence helper
147         * @param accountNumber the given clearing account number
148         * @param chartOfAccountsCode the given clearing chart of accounts code
149         * @return a set of benefit clearing pending entries
150         */
151        public static List<LaborLedgerPendingEntry> generateBenefitClearingPendingEntries(LaborLedgerPostingDocument document, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, String accountNumber, String chartOfAccountsCode) {
152            List<LaborLedgerPendingEntry> benefitClearingPendingEntries = new ArrayList<LaborLedgerPendingEntry>();
153    
154            Map<String, KualiDecimal> sourceLineBenefitAmountSum = new HashMap<String, KualiDecimal>();
155            List<ExpenseTransferSourceAccountingLine> sourceAccountingLines = document.getSourceAccountingLines();
156            for (ExpenseTransferSourceAccountingLine accountingLine : sourceAccountingLines) {
157                updateBenefitAmountSum(sourceLineBenefitAmountSum, accountingLine);
158            }
159    
160            Map<String, KualiDecimal> targetLineBenefitAmountSum = new HashMap<String, KualiDecimal>();
161            List<ExpenseTransferTargetAccountingLine> targetAccountingLines = document.getTargetAccountingLines();
162            for (ExpenseTransferTargetAccountingLine accountingLine : targetAccountingLines) {
163                updateBenefitAmountSum(targetLineBenefitAmountSum, accountingLine);
164            }
165    
166            Set<String> benefitTypeCodes = new HashSet<String>();
167            for (String key : targetLineBenefitAmountSum.keySet()) {
168                benefitTypeCodes.add(key);
169            }
170    
171            for (String key : sourceLineBenefitAmountSum.keySet()) {
172                benefitTypeCodes.add(key);
173            }
174    
175            for (String benefitTypeCode : benefitTypeCodes) {
176                KualiDecimal targetAmount = KualiDecimal.ZERO;
177                if (targetLineBenefitAmountSum.containsKey(benefitTypeCode)) {
178                    targetAmount = targetLineBenefitAmountSum.get(benefitTypeCode);
179                }
180    
181                KualiDecimal sourceAmount = KualiDecimal.ZERO;
182                if (sourceLineBenefitAmountSum.containsKey(benefitTypeCode)) {
183                    sourceAmount = sourceLineBenefitAmountSum.get(benefitTypeCode);
184                }
185    
186                KualiDecimal clearingAmount = sourceAmount.subtract(targetAmount);
187                if (clearingAmount.isNonZero()) {
188                    benefitClearingPendingEntries.add(SpringContext.getBean(LaborPendingEntryConverterService.class).getBenefitClearingPendingEntry(document, sequenceHelper, accountNumber, chartOfAccountsCode, benefitTypeCode, clearingAmount));
189                }
190            }
191    
192            return benefitClearingPendingEntries;
193        }
194    
195        /**
196         * update the benefit amount summary map based on the given accounting line
197         * 
198         * @param benefitAmountSumByBenefitType the given benefit amount summary map
199         * @param accountingLine the given accounting line
200         */
201        protected static void updateBenefitAmountSum(Map<String, KualiDecimal> benefitAmountSumByBenefitType, ExpenseTransferAccountingLine accountingLine) {
202            accountingLine.refreshReferenceObject(KFSPropertyConstants.LABOR_OBJECT);
203            if (ObjectUtils.isNull(accountingLine.getLaborObject())) {
204                return;
205            }
206    
207            String FringeOrSalaryCode = accountingLine.getLaborObject().getFinancialObjectFringeOrSalaryCode();
208            if (!LaborConstants.SalaryExpenseTransfer.LABOR_LEDGER_SALARY_CODE.equals(FringeOrSalaryCode)) {
209                return;
210            }
211    
212            Integer payrollFiscalyear = accountingLine.getPayrollEndDateFiscalYear();
213            String chartOfAccountsCode = accountingLine.getChartOfAccountsCode();
214            String objectCode = accountingLine.getFinancialObjectCode();
215    
216            Collection<PositionObjectBenefit> positionObjectBenefits = SpringContext.getBean(LaborPositionObjectBenefitService.class).getPositionObjectBenefits(payrollFiscalyear, chartOfAccountsCode, objectCode);
217            for (PositionObjectBenefit positionObjectBenefit : positionObjectBenefits) {
218                String benefitTypeCode = positionObjectBenefit.getBenefitsCalculation().getPositionBenefitTypeCode();
219    
220                KualiDecimal benefitAmount = SpringContext.getBean(LaborBenefitsCalculationService.class).calculateFringeBenefit(positionObjectBenefit, accountingLine.getAmount());
221                if (benefitAmountSumByBenefitType.containsKey(benefitTypeCode)) {
222                    benefitAmount = benefitAmount.add(benefitAmountSumByBenefitType.get(benefitTypeCode));
223                }
224                benefitAmountSumByBenefitType.put(benefitTypeCode, benefitAmount);
225            }
226        }
227    
228        /**
229         * determine if the pay fiscal year and period from the accounting line match with its university fiscal year and period.
230         * 
231         * @param document the given document
232         * @param accountingLine the given accounting line of the document
233         * @return true if the pay fiscal year and period from the accounting line match with its university fiscal year and period;
234         *         otherwise, false
235         */
236        protected static boolean isAccountingLinePayFYPeriodMatchesUniversityPayFYPeriod(LaborLedgerPostingDocument document, ExpenseTransferAccountingLine accountingLine) {
237            Integer fiscalYear = document.getPostingYear();
238            Integer payFiscalYear = accountingLine.getPayrollEndDateFiscalYear();
239            if (!fiscalYear.equals(payFiscalYear)) {
240                return false;
241            }
242    
243            String periodCode = document.getPostingPeriodCode();
244            String payPeriodCode = accountingLine.getPayrollEndDateFiscalPeriodCode();
245            if (!StringUtils.equals(periodCode, payPeriodCode)) {
246                return false;
247            }
248    
249            return true;
250        }
251    }