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.bc.document.service.impl;
017    
018    import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.NONE;
019    
020    import java.math.BigDecimal;
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.kuali.kfs.integration.ld.LaborLedgerObject;
030    import org.kuali.kfs.integration.ld.LaborModuleService;
031    import org.kuali.kfs.module.bc.BCConstants;
032    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAppointmentFundingReason;
033    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAppointmentFundingReasonCode;
034    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionCalculatedSalaryFoundationTracker;
035    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionHeader;
036    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition;
037    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
038    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger;
039    import org.kuali.kfs.module.bc.businessobject.SalarySettingExpansion;
040    import org.kuali.kfs.module.bc.document.BudgetConstructionDocument;
041    import org.kuali.kfs.module.bc.document.service.BenefitsCalculationService;
042    import org.kuali.kfs.module.bc.document.service.BudgetConstructionProcessorService;
043    import org.kuali.kfs.module.bc.document.service.BudgetDocumentService;
044    import org.kuali.kfs.module.bc.document.service.LockService;
045    import org.kuali.kfs.module.bc.document.service.SalarySettingService;
046    import org.kuali.kfs.module.bc.util.BudgetParameterFinder;
047    import org.kuali.kfs.module.bc.util.SalarySettingCalculator;
048    import org.kuali.kfs.module.bc.util.SalarySettingFieldsHolder;
049    import org.kuali.kfs.sys.KFSConstants;
050    import org.kuali.kfs.sys.KFSPropertyConstants;
051    import org.kuali.kfs.sys.ObjectUtil;
052    import org.kuali.kfs.sys.context.SpringContext;
053    import org.kuali.kfs.sys.service.OptionsService;
054    import org.kuali.rice.kew.exception.WorkflowException;
055    import org.kuali.rice.kim.bo.Person;
056    import org.kuali.rice.kns.document.authorization.TransactionalDocumentAuthorizer;
057    import org.kuali.rice.kns.service.BusinessObjectService;
058    import org.kuali.rice.kns.service.DocumentHelperService;
059    import org.kuali.rice.kns.service.DocumentService;
060    import org.kuali.rice.kns.service.KualiConfigurationService;
061    import org.kuali.rice.kns.util.GlobalVariables;
062    import org.kuali.rice.kns.util.KualiDecimal;
063    import org.kuali.rice.kns.util.KualiInteger;
064    import org.kuali.rice.kns.util.ObjectUtils;
065    import org.springframework.transaction.annotation.Transactional;
066    
067    /**
068     * implements the service methods defined in the SalarySettingService
069     * 
070     * @see org.kuali.kfs.module.bc.document.service.SalarySettingService
071     */
072    @Transactional
073    public class SalarySettingServiceImpl implements SalarySettingService {
074        public static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SalarySettingServiceImpl.class);
075    
076        private KualiConfigurationService kualiConfigurationService;
077        private BusinessObjectService businessObjectService;
078        private LaborModuleService laborModuleService;
079        private BudgetDocumentService budgetDocumentService;
080        private BenefitsCalculationService benefitsCalculationService;
081        private OptionsService optionsService;
082        private LockService lockService;
083        private DocumentHelperService documentHelperService;
084        private DocumentService documentService;
085        private BudgetConstructionProcessorService budgetConstructionProcessorService;
086    
087        /**
088         * for now just return false, implement application parameter if decision is made implement this functionality
089         * 
090         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#isSalarySettingDisabled()
091         */
092        public boolean isSalarySettingDisabled() {
093            return false;
094        }
095    
096        /**
097         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateHourlyPayRate(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
098         */
099        public BigDecimal calculateHourlyPayRate(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
100            LOG.debug("calculateHourlyPayRate() start");
101    
102            KualiInteger requestedAmount = appointmentFunding.getAppointmentRequestedAmount();
103            BigDecimal fteQuantity = this.calculateFteQuantityFromAppointmentFunding(appointmentFunding);
104    
105            BigDecimal annualWorkingHours = BigDecimal.valueOf(BudgetParameterFinder.getAnnualWorkingHours());
106            BigDecimal totalPayHoursForYear = fteQuantity.multiply(annualWorkingHours);
107            BigDecimal hourlyPayRate = BigDecimal.ZERO;
108            if (totalPayHoursForYear.compareTo(BigDecimal.ZERO) != 0) {
109                hourlyPayRate = requestedAmount.divide(totalPayHoursForYear).setScale(2, BigDecimal.ROUND_HALF_UP);
110            }
111    
112            return hourlyPayRate;
113        }
114    
115        /**
116         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateAnnualPayAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
117         */
118        public KualiInteger calculateAnnualPayAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
119            LOG.debug("calculateAnnualPayAmount() start");
120    
121            BigDecimal hourlyPayRate = appointmentFunding.getAppointmentRequestedPayRate();
122            BigDecimal fteQuantity = this.calculateFteQuantityFromAppointmentFunding(appointmentFunding);
123            BigDecimal annualWorkingHours = BigDecimal.valueOf(BudgetParameterFinder.getAnnualWorkingHours());
124            BigDecimal totalPayHoursForYear = fteQuantity.multiply(annualWorkingHours);
125            KualiInteger annualPayAmount = new KualiInteger(hourlyPayRate.multiply(totalPayHoursForYear));
126    
127            return annualPayAmount;
128        }
129    
130        /**
131         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#normalizePayRateAndAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
132         */
133        public void normalizePayRateAndAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
134            LOG.debug("normalizePayRateAndAmount() start");
135    
136            BigDecimal currentHourlyPayRate = appointmentFunding.getAppointmentRequestedPayRate();
137            if (currentHourlyPayRate != null && !currentHourlyPayRate.equals(BigDecimal.ZERO)) {
138                KualiInteger annualPayAmount = this.calculateAnnualPayAmount(appointmentFunding);
139                appointmentFunding.setAppointmentRequestedAmount(annualPayAmount);
140            } else {
141    
142                KualiInteger currentAnnualPayAmount = appointmentFunding.getAppointmentRequestedAmount();
143                if (currentAnnualPayAmount != null && currentAnnualPayAmount.isNonZero()) {
144                    BigDecimal hourlyPayRate = this.calculateHourlyPayRate(appointmentFunding);
145                    appointmentFunding.setAppointmentRequestedPayRate(hourlyPayRate);
146                }
147    
148                currentHourlyPayRate = appointmentFunding.getAppointmentRequestedPayRate();
149                if (currentHourlyPayRate != null) {
150                    KualiInteger annualPayAmount = this.calculateAnnualPayAmount(appointmentFunding);
151                    appointmentFunding.setAppointmentRequestedAmount(annualPayAmount);
152                }
153            }
154        }
155    
156        /**
157         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateFteQuantityForAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
158         */
159        public BigDecimal calculateFteQuantityFromAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
160            LOG.debug("calculateFteQuantity() start");
161    
162            // appointmentFunding.refreshReferenceObject(BCPropertyConstants.BUDGET_CONSTRUCTION_POSITION);
163            BudgetConstructionPosition position = appointmentFunding.getBudgetConstructionPosition();
164            if (ObjectUtils.isNull(position)) {
165                return BigDecimal.ZERO;
166            }
167    
168            Integer payMonth = position.getIuPayMonths();
169            Integer fundingMonth = appointmentFunding.getAppointmentFundingMonth();
170            BigDecimal requestedTimePercent = appointmentFunding.getAppointmentRequestedTimePercent();
171    
172            return this.calculateFteQuantity(payMonth, fundingMonth, requestedTimePercent);
173        }
174    
175        /**
176         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateFteQuantity(java.lang.Integer, java.lang.Integer,
177         *      java.math.BigDecimal)
178         */
179        public BigDecimal calculateFteQuantity(Integer payMonth, Integer fundingMonth, BigDecimal requestedTimePercent) {
180            LOG.debug("calculateFteQuantity() start");
181    
182            if (payMonth == null || fundingMonth == null || requestedTimePercent == null) {
183                return BigDecimal.ZERO;
184            }
185    
186            BigDecimal payMonthAsDecimal = BigDecimal.valueOf(payMonth);
187            BigDecimal fundingMonthAsDecimal = BigDecimal.valueOf(fundingMonth);
188            BigDecimal fundingMonthPercent = fundingMonthAsDecimal.divide(payMonthAsDecimal, 5, BigDecimal.ROUND_HALF_UP);
189    
190            BigDecimal fteQuantity = requestedTimePercent.multiply(fundingMonthPercent).divide(KFSConstants.ONE_HUNDRED.bigDecimalValue());
191    
192            return fteQuantity.setScale(5, BigDecimal.ROUND_HALF_UP);
193        }
194    
195        /**
196         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateCSFFteQuantityFromAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
197         */
198        public BigDecimal calculateCSFFteQuantityFromAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
199            LOG.debug("calculateCSFFteQuantity() start");
200    
201            // appointmentFunding.refreshReferenceObject(BCPropertyConstants.BUDGET_CONSTRUCTION_POSITION);
202            BudgetConstructionPosition position = appointmentFunding.getBudgetConstructionPosition();
203            if (position == null) {
204                return BigDecimal.ZERO;
205            }
206    
207            Integer payMonth = position.getIuPayMonths();
208            Integer normalWorkMonth = position.getIuNormalWorkMonths();
209            BigDecimal requestedCSFTimePercent = appointmentFunding.getAppointmentRequestedCsfTimePercent();
210    
211            return this.calculateCSFFteQuantity(payMonth, normalWorkMonth, requestedCSFTimePercent);
212        }
213    
214        /**
215         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#calculateCSFFteQuantity(java.lang.Integer,
216         *      java.lang.Integer, java.math.BigDecimal)
217         */
218        public BigDecimal calculateCSFFteQuantity(Integer payMonth, Integer normalWorkMonth, BigDecimal requestedCSFTimePercent) {
219            LOG.debug("calculateCSFFteQuantity() start");
220    
221            if (payMonth == null || normalWorkMonth == null || requestedCSFTimePercent == null) {
222                return BigDecimal.ZERO;
223            }
224    
225            BigDecimal payMonthAsDecimal = BigDecimal.valueOf(payMonth);
226            BigDecimal normalMonthAsDecimal = BigDecimal.valueOf(normalWorkMonth);
227            BigDecimal fundingMonthPercent = normalMonthAsDecimal.divide(payMonthAsDecimal, 5, BigDecimal.ROUND_HALF_UP);
228    
229            BigDecimal fteQuantity = requestedCSFTimePercent.multiply(fundingMonthPercent).divide(KFSConstants.ONE_HUNDRED.bigDecimalValue());
230    
231            return fteQuantity.setScale(5, BigDecimal.ROUND_HALF_UP);
232        }
233    
234        /**
235         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#isHourlyPaid(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger)
236         */
237        public boolean isHourlyPaid(PendingBudgetConstructionGeneralLedger pendingBudgetConstructionGeneralLedger) {
238            LOG.debug("isHourlyPaid() start");
239    
240            Integer fiscalYear = pendingBudgetConstructionGeneralLedger.getUniversityFiscalYear();
241            String chartOfAccountsCode = pendingBudgetConstructionGeneralLedger.getChartOfAccountsCode();
242            String objectCode = pendingBudgetConstructionGeneralLedger.getFinancialObjectCode();
243    
244            return this.isHourlyPaidObject(fiscalYear, chartOfAccountsCode, objectCode);
245        }
246    
247        /**
248         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#isHourlyPaid(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
249         */
250        public boolean isHourlyPaid(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
251            LOG.debug("isHourlyPaid() start");
252    
253            Integer fiscalYear = appointmentFunding.getUniversityFiscalYear();
254            String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode();
255            String objectCode = appointmentFunding.getFinancialObjectCode();
256    
257            return this.isHourlyPaidObject(fiscalYear, chartOfAccountsCode, objectCode);
258        }
259    
260        /**
261         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#isHourlyPaid(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionGeneralLedger)
262         */
263        public boolean isHourlyPaidObject(Integer fiscalYear, String chartOfAccountsCode, String objectCode) {
264            LOG.debug("isHourlyPaid() start");
265    
266            LaborLedgerObject laborLedgerObject = laborModuleService.retrieveLaborLedgerObject(fiscalYear, chartOfAccountsCode, objectCode);
267    
268            if (laborLedgerObject == null) {
269                return false;
270            }
271    
272            return BudgetParameterFinder.getBiweeklyPayTypeCodes().contains(laborLedgerObject.getFinancialObjectPayTypeCode());
273        }
274    
275        /**
276         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#canBeVacant(java.util.List,
277         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
278         */
279        public boolean canBeVacant(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
280            LOG.debug("canBeVacant(List, PendingBudgetConstructionAppointmentFunding) start");
281    
282            if (!this.canBeVacant(appointmentFunding)) {
283                return false;
284            }
285    
286            return this.findVacantAppointmentFunding(appointmentFundings, appointmentFunding) == null;
287        }
288    
289        /**
290         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#findVacantAppointmentFunding(java.util.List,
291         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
292         */
293        public PendingBudgetConstructionAppointmentFunding findVacantAppointmentFunding(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
294            LOG.debug("findVacantAppointmentFunding() start");
295    
296            PendingBudgetConstructionAppointmentFunding vacantAppointmentFunding = this.createVacantAppointmentFunding(appointmentFunding);
297    
298            return this.findAppointmentFunding(appointmentFundings, vacantAppointmentFunding);
299        }
300    
301        /**
302         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#findAppointmentFunding(java.util.List,
303         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
304         */
305        public PendingBudgetConstructionAppointmentFunding findAppointmentFunding(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
306            LOG.debug("findAppointmentFunding() start");
307    
308            Map<String, Object> keyFieldValues = appointmentFunding.getValuesMap();
309            List<String> keyFields = new ArrayList<String>();
310            keyFields.addAll(keyFieldValues.keySet());
311    
312            // determine whether there is vacant for the given appointment funding in its list
313            for (PendingBudgetConstructionAppointmentFunding fundingLine : appointmentFundings) {
314                if (ObjectUtil.equals(fundingLine, appointmentFunding, keyFields)) {
315                    return fundingLine;
316                }
317            }
318    
319            return null;
320        }
321    
322        /**
323         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#canBeVacant(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
324         */
325        public boolean canBeVacant(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
326            LOG.debug("canBeVacant() start");
327    
328            if (appointmentFunding.isNewLineIndicator()) {
329                return false;
330            }
331    
332            // the given funding line has not been deleted
333            if (appointmentFunding.isAppointmentFundingDeleteIndicator()) {
334                return false;
335            }
336    
337            // the given funding line cannot be a vacant line
338            String emplid = appointmentFunding.getEmplid();
339            if (BCConstants.VACANT_EMPLID.equals(emplid)) {
340                return false;
341            }
342    
343            // check if the associated position is valid and active
344            BudgetConstructionPosition position = appointmentFunding.getBudgetConstructionPosition();
345            if (position == null || !position.isBudgetedPosition() || !position.isEffective()) {
346                return false;
347            }
348    
349            // check if there is an existing vacant appintment funcding for the given funding line
350            boolean hasBeenVacated = this.hasBeenVacated(appointmentFunding);
351            if (hasBeenVacated) {
352                return false;
353            }
354    
355            return true;
356        }
357    
358        /**
359         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#vacateAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
360         */
361        public PendingBudgetConstructionAppointmentFunding vacateAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
362            LOG.debug("vacateAppointmentFunding() start");
363    
364            PendingBudgetConstructionAppointmentFunding vacantAppointmentFunding = this.createVacantAppointmentFunding(appointmentFunding);
365            this.markAsDelete(appointmentFunding);
366    
367            return vacantAppointmentFunding;
368        }
369    
370        /**
371         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#vacateAppointmentFunding(java.util.List,
372         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
373         */
374        public PendingBudgetConstructionAppointmentFunding vacateAppointmentFunding(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
375            PendingBudgetConstructionAppointmentFunding vacantAppointmentFunding = this.vacateAppointmentFunding(appointmentFunding);
376    
377            if (vacantAppointmentFunding != null) {
378                appointmentFundings.add(vacantAppointmentFunding);
379            }
380    
381            return vacantAppointmentFunding;
382        }
383    
384        /**
385         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#purgeAppointmentFundings(java.util.List)
386         */
387        public void purgeAppointmentFundings(List<PendingBudgetConstructionAppointmentFunding> purgedAppointmentFundings) {
388            // remove the purged appointment funding lines and their referenced records
389            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : purgedAppointmentFundings) {
390                if (!appointmentFunding.isNewLineIndicator()) {
391                    businessObjectService.delete(appointmentFunding);
392                }
393            }
394        }
395    
396        /**
397         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#adjustRequestedSalaryByAmount(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
398         */
399        public void adjustRequestedSalaryByAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
400            LOG.debug("adjustRequestedSalaryByAmount() start");
401    
402            int inputAdjustmentAmount = appointmentFunding.getAdjustmentAmount().intValue();
403    
404            KualiInteger adjustmentAmount = new KualiInteger(inputAdjustmentAmount);
405            KualiInteger csfAmount = this.getCsfAmount(appointmentFunding);
406            KualiInteger appointmentRequestedAmount = csfAmount.add(adjustmentAmount);
407    
408            appointmentFunding.setAppointmentRequestedAmount(appointmentRequestedAmount);
409    
410            if (appointmentFunding.isHourlyPaid()) {
411                appointmentFunding.setAppointmentRequestedPayRate(BigDecimal.ZERO);
412                this.normalizePayRateAndAmount(appointmentFunding);
413            }
414        }
415    
416        /**
417         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#adjustRequestedSalaryByPercent(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
418         */
419        public void adjustRequestedSalaryByPercent(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
420            LOG.debug("adjustRequestedSalaryByPercent() start");
421    
422            KualiInteger csfAmount = this.getCsfAmount(appointmentFunding);
423    
424            if (csfAmount.isNonZero()) {
425                KualiDecimal percent = appointmentFunding.getAdjustmentAmount();
426                BigDecimal adjustedAmount = csfAmount.multiply(percent).divide(KFSConstants.ONE_HUNDRED);
427    
428                KualiInteger appointmentRequestedAmount = new KualiInteger(adjustedAmount).add(csfAmount);
429                appointmentFunding.setAppointmentRequestedAmount(appointmentRequestedAmount);
430            }
431    
432            if (appointmentFunding.isHourlyPaid()) {
433                appointmentFunding.setAppointmentRequestedPayRate(BigDecimal.ZERO);
434                this.normalizePayRateAndAmount(appointmentFunding);
435            }
436        }
437    
438        /**
439         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#saveSalarySetting(org.kuali.kfs.module.bc.businessobject.SalarySettingExpansion)
440         */
441        public void saveSalarySetting(SalarySettingExpansion salarySettingExpansion) {
442            LOG.debug("saveSalarySetting() start");
443    
444            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingExpansion.getPendingBudgetConstructionAppointmentFunding();
445            this.resetDeletedFundingLines(appointmentFundings);
446            this.updateAppointmentFundingsBeforeSaving(appointmentFundings);
447    
448            KualiInteger requestedAmountTotal = SalarySettingCalculator.getAppointmentRequestedAmountTotal(appointmentFundings);
449            KualiInteger changes = KualiInteger.ZERO;
450    
451            if (requestedAmountTotal != null) {
452                KualiInteger annualBalanceAmount = salarySettingExpansion.getAccountLineAnnualBalanceAmount();
453                changes = (annualBalanceAmount != null) ? requestedAmountTotal.subtract(annualBalanceAmount) : requestedAmountTotal;
454            }
455    
456            salarySettingExpansion.setAccountLineAnnualBalanceAmount(requestedAmountTotal);
457            businessObjectService.save(salarySettingExpansion);
458    
459            // now create a pseudo funding line if the BCAF list is empty so we can pass it to create 2PLG below
460            Boolean wasSalarySettingExpansionBCAFEmpty = salarySettingExpansion.getPendingBudgetConstructionAppointmentFunding().isEmpty();
461            if (wasSalarySettingExpansionBCAFEmpty) {
462                appointmentFundings.add(this.createPseudoAppointmentFundingLine(salarySettingExpansion));
463            }
464    
465            // update or create plug line if the total amount has been changed
466            if (changes.isNonZero()) {
467    
468                budgetDocumentService.updatePendingBudgetGeneralLedgerPlug(appointmentFundings.get(0), changes.negated());
469            }
470        }
471    
472        public void savePBGLSalarySetting(SalarySettingExpansion salarySettingExpansion) {
473            LOG.debug("savePBGLSalarySetting() start");
474    
475            // gwp - added this method to handle detail salary setting PBGL updates
476            // instead of using saveSalarySetting(SalarySettingExpansion salarySettingExpansion)
477    
478            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingExpansion.getPendingBudgetConstructionAppointmentFunding();
479    
480            // this is already done in saveSalarySetting by a call to saveAppointmentFundings
481            // this.resetDeletedFundingLines(appointmentFundings);
482            // this.updateAppointmentFundingsBeforeSaving(appointmentFundings);
483    
484            KualiInteger requestedAmountTotal = SalarySettingCalculator.getAppointmentRequestedAmountTotal(appointmentFundings);
485            KualiInteger changes = KualiInteger.ZERO;
486    
487            if (requestedAmountTotal != null) {
488                KualiInteger annualBalanceAmount = salarySettingExpansion.getAccountLineAnnualBalanceAmount();
489                changes = (annualBalanceAmount != null) ? requestedAmountTotal.subtract(annualBalanceAmount) : requestedAmountTotal;
490            }
491    
492            // salarySettingExpansion.setAccountLineAnnualBalanceAmount(requestedAmountTotal);
493            // businessObjectService.save(salarySettingExpansion);
494    
495            // now create a pseudo funding line if the BCAF list is empty so we can pass it to create 2PLG below
496            Boolean wasSalarySettingExpansionBCAFEmpty = salarySettingExpansion.getPendingBudgetConstructionAppointmentFunding().isEmpty();
497            if (wasSalarySettingExpansionBCAFEmpty) {
498                appointmentFundings.add(this.createPseudoAppointmentFundingLine(salarySettingExpansion));
499            }
500    
501            // For detail salary setting, we need to update existing or create new PBGL row for the BCAF set
502            // We can't save salarySettingExpansion here since it will also save the associated BCAF rows
503            // which in this case would be rows we might not have worked on.
504            // Also, don't save PBGL if it is not in DB already and the BCAF set was empty (no doo doo).
505            // that is, save if PBGL exists in DB or BCAF was not empty
506            if (salarySettingExpansion.getVersionNumber() != null || !wasSalarySettingExpansionBCAFEmpty) {
507    
508                budgetDocumentService.updatePendingBudgetGeneralLedger(appointmentFundings.get(0), changes);
509            }
510    
511            // update or create plug line if the total amount has been changed
512            if (changes.isNonZero()) {
513    
514                budgetDocumentService.updatePendingBudgetGeneralLedgerPlug(appointmentFundings.get(0), changes.negated());
515            }
516        }
517    
518        /**
519         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#saveSalarySetting(java.util.List)
520         */
521        public void saveSalarySetting(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, Boolean isSalarySettingByIncumbent) {
522    
523            // Do the save/delete of BCAF rows from the salary setting detail screen first
524            this.saveAppointmentFundings(appointmentFundings);
525    
526            // From the DB get the current unique set of SalarySettingExpansions (PBGL)
527            // associated with our Incumbent or Position BCAF rows.
528            // Each PBGL row from the DB will, in turn, have all the BCAF rows associated, including the
529            // ones we just stored as part of this save operation (see above)
530            // No one else would be updating these since we have a transaction lock on the account
531            Set<SalarySettingExpansion> salarySettingExpansionSet = new HashSet<SalarySettingExpansion>();
532    
533            // these keep track of purged/unpurged used to unlock funding
534            // when the last line for that account is purged
535            Set<SalarySettingExpansion> purgedSseSet = new HashSet<SalarySettingExpansion>();
536            Set<SalarySettingExpansion> unpurgedSseSet = new HashSet<SalarySettingExpansion>();
537    
538            // these keep track of purged/unpurged used to unlock positions
539            // when the last line for that position is purged
540            Set<BudgetConstructionPosition> purgedBPOSNSet = new HashSet<BudgetConstructionPosition>();
541            Set<BudgetConstructionPosition> unpurgedBPOSNSet = new HashSet<BudgetConstructionPosition>();
542    
543            for (PendingBudgetConstructionAppointmentFunding fundingLine : appointmentFundings) {
544                SalarySettingExpansion salarySettingExpansion = this.retriveSalarySalarySettingExpansion(fundingLine);
545    
546                if (salarySettingExpansion != null) {
547                    salarySettingExpansionSet.add(salarySettingExpansion);
548                }
549                else {
550                    // No PBGL row yet, create one to work with in memory only for now.
551                    // Don't set versionNumber, this will indicate this is in memory only,
552                    // so we can check for the case where there are no BCAF rows in the DB
553                    // and no PBGL row either. We don't want to create a new zero request PBGL row in this case.
554                    salarySettingExpansion = new SalarySettingExpansion();
555                    salarySettingExpansion.setUniversityFiscalYear(fundingLine.getUniversityFiscalYear());
556                    salarySettingExpansion.setChartOfAccountsCode(fundingLine.getChartOfAccountsCode());
557                    salarySettingExpansion.setAccountNumber(fundingLine.getAccountNumber());
558                    salarySettingExpansion.setSubAccountNumber(fundingLine.getSubAccountNumber());
559                    salarySettingExpansion.setFinancialObjectCode(fundingLine.getFinancialObjectCode());
560                    salarySettingExpansion.setFinancialSubObjectCode(fundingLine.getFinancialSubObjectCode());
561                    salarySettingExpansion.setFinancialBalanceTypeCode(optionsService.getOptions(fundingLine.getUniversityFiscalYear()).getBaseBudgetFinancialBalanceTypeCd());
562                    salarySettingExpansion.setFinancialObjectTypeCode(optionsService.getOptions(fundingLine.getUniversityFiscalYear()).getFinObjTypeExpenditureexpCd());
563                    salarySettingExpansion.setAccountLineAnnualBalanceAmount(KualiInteger.ZERO);
564                    salarySettingExpansion.setFinancialBeginningBalanceLineAmount(KualiInteger.ZERO);
565    
566                    // If this has been created in memory already, the list should already be attached
567                    // and be in the current salarySettingExpansionSet.
568                    // This handles the case where at least 2 new BCAF rows for the same non-existent PBGL row
569                    // were saved earlier as part of this save operation.
570                    if (!salarySettingExpansionSet.contains(salarySettingExpansion)) {
571    
572                        // Get the BCAF rows from the DB that are associated with the
573                        // newly created salarySettingExpansion and attach so the
574                        // method savePBGLSalarySetting() called below can get the total.
575                        List<PendingBudgetConstructionAppointmentFunding> bcafRows = this.retrievePendingBudgetConstructionAppointmentFundings(salarySettingExpansion);
576                        salarySettingExpansion.getPendingBudgetConstructionAppointmentFunding().addAll(bcafRows);
577                        salarySettingExpansionSet.add(salarySettingExpansion);
578                    }
579                }
580    
581                // collect the set of purge/notpurged SalarySettingExpansions here
582                if (fundingLine.isPurged()) {
583                    purgedSseSet.add(salarySettingExpansion);
584                }
585                else {
586                    unpurgedSseSet.add(salarySettingExpansion);
587                }
588    
589                // if SS by incumbent collect the set of purged/notpurged BudgetConstructionPositions here
590                if (isSalarySettingByIncumbent) {
591                    BudgetConstructionPosition budgetConstructionPosition = fundingLine.getBudgetConstructionPosition();
592                    if (fundingLine.isPurged()) {
593                        purgedBPOSNSet.add(budgetConstructionPosition);
594                    }
595                    else {
596                        unpurgedBPOSNSet.add(budgetConstructionPosition);
597                    }
598                }
599            }
600    
601            // remove from set of purged SSEs the set of notpurged SSEs
602            // leftover are those SSEs to release funding locks for after successful save
603            purgedSseSet.removeAll(unpurgedSseSet);
604    
605            // if SS by incumbent, remove from set of purged BPOSNs the set of nonpurged BPOSNs
606            if (isSalarySettingByIncumbent) {
607                purgedBPOSNSet.removeAll(unpurgedBPOSNSet);
608            }
609    
610            // Use the salarySettingExpansionSet to drive the update of PBGL rows (including any 2PLGs)
611            for (SalarySettingExpansion salarySettingExpansion : salarySettingExpansionSet) {
612    
613                this.savePBGLSalarySetting(salarySettingExpansion);
614            }
615    
616            // iterate leftover purged SSEs and release funding lock for each
617            for (SalarySettingExpansion salarySettingExpansion : purgedSseSet) {
618                String chartOfAccountsCode = salarySettingExpansion.getChartOfAccountsCode();
619                String accountNumber = salarySettingExpansion.getAccountNumber();
620                String subAccountNumber = salarySettingExpansion.getSubAccountNumber();
621                Integer fiscalYear = salarySettingExpansion.getUniversityFiscalYear();
622                String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId();
623    
624                // release the associated funding lock
625                lockService.unlockFunding(chartOfAccountsCode, accountNumber, subAccountNumber, fiscalYear, principalId);
626    
627            }
628    
629            // if SS by incumbent iterate leftover purged BPOSNs and release position lock for each
630            for (BudgetConstructionPosition budgetConstructionPosition : purgedBPOSNSet) {
631                Person person = GlobalVariables.getUserSession().getPerson();
632                lockService.unlockPostion(budgetConstructionPosition, person);
633            }
634        }
635    
636        /**
637         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#saveAppointmentFundings(java.util.List)
638         */
639        public void saveAppointmentFundings(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings) {
640            LOG.debug("saveAppointmentFundings() start");
641    
642            // remove the appointment funding lines being purged
643            List<PendingBudgetConstructionAppointmentFunding> purgedAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
644            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) {
645                if (appointmentFunding.isPurged()) {
646                    purgedAppointmentFundings.add(appointmentFunding);
647                }
648            }
649            this.purgeAppointmentFundings(purgedAppointmentFundings);
650    
651            // save the appointment funding lines that have been updated or newly created
652            List<PendingBudgetConstructionAppointmentFunding> savableAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>(appointmentFundings);
653            savableAppointmentFundings.removeAll(purgedAppointmentFundings);
654    
655            // gwp - added this as part of double save optimistic exception fix
656            // since savePBGLSalarySetting does not call this like saveSalarySetting does
657            this.resetDeletedFundingLines(appointmentFundings);
658    
659            this.updateAppointmentFundingsBeforeSaving(savableAppointmentFundings);
660            
661            // save each line so deletion aware reasons get removed when needed
662            for (PendingBudgetConstructionAppointmentFunding savableAppointmentFunding : savableAppointmentFundings){
663                businessObjectService.save(savableAppointmentFunding);
664            }
665        }
666    
667        /**
668         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#retriveSalarySalarySettingExpansion(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
669         */
670        public SalarySettingExpansion retriveSalarySalarySettingExpansion(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
671            BudgetConstructionHeader budgetDocument = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding);
672    
673            Map<String, Object> fieldValues = ObjectUtil.buildPropertyMap(appointmentFunding, SalarySettingExpansion.getPrimaryKeyFields());
674            fieldValues.put(KFSPropertyConstants.DOCUMENT_NUMBER, budgetDocument.getDocumentNumber());
675    
676            return (SalarySettingExpansion) businessObjectService.findByPrimaryKey(SalarySettingExpansion.class, fieldValues);
677        }
678    
679        /**
680         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#retrievePendingBudgetConstructionAppointmentFundings(org.kuali.kfs.module.bc.businessobject.SalarySettingExpansion)
681         */
682        public List<PendingBudgetConstructionAppointmentFunding> retrievePendingBudgetConstructionAppointmentFundings(SalarySettingExpansion salarySettingExpansion) {
683    
684            Map<String, Object> fieldValues = new HashMap<String, Object>();
685            fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, salarySettingExpansion.getUniversityFiscalYear());
686            fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, salarySettingExpansion.getChartOfAccountsCode());
687            fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, salarySettingExpansion.getAccountNumber());
688            fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, salarySettingExpansion.getSubAccountNumber());
689            fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, salarySettingExpansion.getFinancialObjectCode());
690            fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, salarySettingExpansion.getFinancialSubObjectCode());
691    
692            return (List<PendingBudgetConstructionAppointmentFunding>) businessObjectService.findMatching(PendingBudgetConstructionAppointmentFunding.class, fieldValues);
693        }
694    
695        /**
696         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#resetAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
697         */
698        public void resetAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
699            appointmentFunding.setAppointmentRequestedAmount(KualiInteger.ZERO);
700            appointmentFunding.setAppointmentRequestedTimePercent(BigDecimal.ZERO);
701            appointmentFunding.setAppointmentRequestedPayRate(BigDecimal.ZERO);
702            appointmentFunding.setAppointmentRequestedFteQuantity(BigDecimal.ZERO);
703    
704            appointmentFunding.setAppointmentRequestedCsfAmount(KualiInteger.ZERO);
705            appointmentFunding.setAppointmentRequestedCsfFteQuantity(BigDecimal.ZERO);
706            appointmentFunding.setAppointmentRequestedCsfTimePercent(BigDecimal.ZERO);
707    
708            appointmentFunding.setAppointmentTotalIntendedAmount(KualiInteger.ZERO);
709            appointmentFunding.setAppointmentTotalIntendedFteQuantity(BigDecimal.ZERO);
710    
711            appointmentFunding.setAppointmentFundingDurationCode(BCConstants.AppointmentFundingDurationCodes.NONE.durationCode);
712    
713            appointmentFunding.setPositionObjectChangeIndicator(false);
714            appointmentFunding.setPositionSalaryChangeIndicator(false);
715        }
716    
717        /**
718         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#markAsDelete(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
719         */
720        public void markAsDelete(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
721            this.resetAppointmentFunding(appointmentFunding);
722    
723            appointmentFunding.setAppointmentFundingDeleteIndicator(true);
724        }
725    
726        /**
727         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#revert(java.util.List,
728         *      org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
729         */
730        public void revert(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
731            PendingBudgetConstructionAppointmentFunding vacantFunding = this.findVacantAppointmentFunding(appointmentFundings, appointmentFunding);
732    
733            if (vacantFunding != null) {
734                appointmentFundings.remove(vacantFunding);
735            }
736    
737            PendingBudgetConstructionAppointmentFunding newAppointmentFunding = (PendingBudgetConstructionAppointmentFunding) businessObjectService.retrieve(appointmentFunding);
738            appointmentFundings.add(newAppointmentFunding);
739            appointmentFundings.remove(appointmentFunding);
740        }
741    
742        /**
743         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#updateAccessOfAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
744         *      org.kuali.kfs.module.bc.util.SalarySettingFieldsHolder, boolean, java.util.Map, org.kuali.rice.kim.bo.Person)
745         */
746        public boolean updateAccessOfAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding, SalarySettingFieldsHolder salarySettingFieldsHolder, boolean budgetByObjectMode, boolean hasDocumentEditAccess, Person person) {
747            String budgetChartOfAccountsCode = salarySettingFieldsHolder.getChartOfAccountsCode();
748            String budgetAccountNumber = salarySettingFieldsHolder.getAccountNumber();
749            String budgetSubAccountNumber = salarySettingFieldsHolder.getSubAccountNumber();
750            String budgetObjectCode = salarySettingFieldsHolder.getFinancialObjectCode();
751            String budgetSubObjectCode = salarySettingFieldsHolder.getFinancialSubObjectCode();
752    
753            String chartOfAccountsCode = appointmentFunding.getChartOfAccountsCode();
754            String accountNumber = appointmentFunding.getAccountNumber();
755            String subAccountNumber = appointmentFunding.getSubAccountNumber();
756            String objectCode = appointmentFunding.getFinancialObjectCode();
757            String subObjectCode = appointmentFunding.getFinancialSubObjectCode();
758    
759            // just allow edit if budget by object mode (general case of single account mode)
760            if (budgetByObjectMode && StringUtils.equals(chartOfAccountsCode, budgetChartOfAccountsCode) && StringUtils.equals(accountNumber, budgetAccountNumber) && StringUtils.equals(subAccountNumber, budgetSubAccountNumber)) {
761                // use the edit permission already calculated for the home account during document open
762                appointmentFunding.setDisplayOnlyMode(!hasDocumentEditAccess);
763    
764                return true;
765            }
766    
767            boolean isUpdatedByUserLevel = this.updateAccessOfAppointmentFundingByUserLevel(appointmentFunding, person);
768            if (isUpdatedByUserLevel) {
769                return true;
770            }
771    
772            return false;
773        }
774    
775        /**
776         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#updateAccessOfAppointmentFundingByUserLevel(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding,
777         *      org.kuali.rice.kim.bo.Person)
778         */
779        public boolean updateAccessOfAppointmentFundingByUserLevel(PendingBudgetConstructionAppointmentFunding appointmentFunding, Person user) {
780            BudgetConstructionHeader budgetConstructionHeader = budgetDocumentService.getBudgetConstructionHeader(appointmentFunding);
781            if (budgetConstructionHeader == null) {
782                return false;
783            }
784    
785            BudgetConstructionDocument document;
786            try {
787                document = (BudgetConstructionDocument) documentService.getByDocumentHeaderId(budgetConstructionHeader.getDocumentNumber());
788            }
789            catch (WorkflowException e) {
790                throw new RuntimeException("Fail to retrieve budget document for doc id " + budgetConstructionHeader.getDocumentNumber());
791            }
792    
793            TransactionalDocumentAuthorizer documentAuthorizer = (TransactionalDocumentAuthorizer) getDocumentHelperService().getDocumentAuthorizer(document);
794    
795            boolean hasEditAccess = documentAuthorizer.isAuthorized(document, BCConstants.BUDGET_CONSTRUCTION_NAMESPACE, BCConstants.KimConstants.EDIT_BCAF_PERMISSION_NAME, user.getPrincipalId());
796            appointmentFunding.setDisplayOnlyMode(!hasEditAccess);
797    
798            boolean hasViewAmountsAccess = documentAuthorizer.isAuthorized(document, BCConstants.BUDGET_CONSTRUCTION_NAMESPACE, BCConstants.KimConstants.VIEW_BCAF_AMOUNTS_PERMISSION_NAME, user.getPrincipalId());
799            appointmentFunding.setExcludedFromTotal(!hasViewAmountsAccess);
800    
801            return true;
802        }
803    
804        /**
805         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#updateDerivedInformationForAppointmentFundings(java.util.List)
806         */
807        public void updateAppointmentFundingsBeforeSaving(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings) {
808            LOG.debug("updateDerivedInformationForAppointmentFundings() start");
809    
810            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) {
811                this.recalculateDerivedInformation(appointmentFunding);
812    
813                appointmentFunding.setNewLineIndicator(false);
814            }
815        }
816    
817        /**
818         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#updateDerivedInformationForAppointmentFunding(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
819         */
820        public void recalculateDerivedInformation(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
821            this.preprocessFundingReason(appointmentFunding);
822            this.preprocessLeaveRequest(appointmentFunding);
823    
824            boolean isHourlyPaid = this.isHourlyPaid(appointmentFunding);
825            appointmentFunding.setHourlyPaid(isHourlyPaid);
826    
827            if (appointmentFunding.isHourlyPaid()) {
828                this.normalizePayRateAndAmount(appointmentFunding);
829            }
830            else {
831                appointmentFunding.setAppointmentRequestedPayRate(BigDecimal.ZERO);
832            }
833    
834            BigDecimal requestedFteQuantity = this.calculateFteQuantityFromAppointmentFunding(appointmentFunding);
835            appointmentFunding.setAppointmentRequestedFteQuantity(requestedFteQuantity);
836    
837            if (!appointmentFunding.getAppointmentFundingDurationCode().equals(NONE.durationCode)) {
838                BigDecimal requestedCSFFteQuantity = this.calculateCSFFteQuantityFromAppointmentFunding(appointmentFunding);
839                appointmentFunding.setAppointmentRequestedCsfFteQuantity(requestedCSFFteQuantity);
840            }
841        }
842    
843        /**
844         * reset the amount values of each line in the given appointment fundings as zeros and remove the reason annotations if the line
845         * is marked as deleted
846         * 
847         * @param pendingBudgetConstructionAppointmentFunding the given appointment fundings
848         */
849        protected void resetDeletedFundingLines(List<PendingBudgetConstructionAppointmentFunding> pendingBudgetConstructionAppointmentFunding) {
850            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : pendingBudgetConstructionAppointmentFunding) {
851                if (!appointmentFunding.isAppointmentFundingDeleteIndicator() || appointmentFunding.isPersistedDeleteIndicator()) {
852                    continue;
853                }
854    
855                this.markAsDelete(appointmentFunding);
856                List<BudgetConstructionAppointmentFundingReason> reasons = appointmentFunding.getBudgetConstructionAppointmentFundingReason();
857                if (reasons != null) {
858                    reasons.clear();
859                }
860    
861                appointmentFunding.setPersistedDeleteIndicator(true);
862            }
863        }
864    
865        /**
866         * get the csf tracker amount of the given appointment funding
867         * 
868         * @param appointmentFunding the given appointment funding
869         * @return the csf tracker amount of the given appointment funding if any; otherwise, return zero
870         */
871        protected KualiInteger getCsfAmount(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
872            if (appointmentFunding == null) {
873                return KualiInteger.ZERO;
874            }
875    
876            BudgetConstructionCalculatedSalaryFoundationTracker csfTracker = appointmentFunding.getEffectiveCSFTracker();
877            if (csfTracker == null) {
878                return KualiInteger.ZERO;
879            }
880    
881            return csfTracker.getCsfAmount();
882        }
883    
884        /**
885         * determine whether there exists at lease one vacant funding line for the given appointment funding
886         * 
887         * @param appointmentFunding the given appointment funding
888         * @return true if there exists at lease one vacant funding line for the given appointment funding; otherwise, return false
889         */
890        protected boolean hasBeenVacated(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
891            Map<String, Object> keyFieldValues = appointmentFunding.getValuesMap();
892            keyFieldValues.put(KFSPropertyConstants.EMPLID, BCConstants.VACANT_EMPLID);
893    
894            return businessObjectService.countMatching(PendingBudgetConstructionAppointmentFunding.class, keyFieldValues) > 0;
895        }
896    
897        /**
898         * create a vacant appointment funding based on the given budget funding
899         * 
900         * @param appointmentFunding the given appointment funding
901         * @return a vacant appointment funding
902         */
903        protected PendingBudgetConstructionAppointmentFunding createVacantAppointmentFunding(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
904            PendingBudgetConstructionAppointmentFunding vacantAppointmentFunding = new PendingBudgetConstructionAppointmentFunding();
905    
906            ObjectUtil.buildObjectWithoutReferenceFields(vacantAppointmentFunding, appointmentFunding);
907            vacantAppointmentFunding.setEmplid(BCConstants.VACANT_EMPLID);
908            vacantAppointmentFunding.setAppointmentFundingDeleteIndicator(false);
909            vacantAppointmentFunding.setPersistedDeleteIndicator(false);
910            vacantAppointmentFunding.setVersionNumber(null);
911    
912            return vacantAppointmentFunding;
913        }
914    
915        /**
916         * create a pseudo appointment funding for the salary setting expansion this is used when there are no funding lines for the
917         * salary setting expansion to get a funding line to be used to pass primary key info
918         * 
919         * @param salarySettingExpansion
920         * @return a pseudo appointment funding
921         */
922        protected PendingBudgetConstructionAppointmentFunding createPseudoAppointmentFundingLine(SalarySettingExpansion salarySettingExpansion) {
923            PendingBudgetConstructionAppointmentFunding pseudoAppointmentFunding = new PendingBudgetConstructionAppointmentFunding();
924    
925            pseudoAppointmentFunding.setUniversityFiscalYear(salarySettingExpansion.getUniversityFiscalYear());
926            pseudoAppointmentFunding.setChartOfAccountsCode(salarySettingExpansion.getChartOfAccountsCode());
927            pseudoAppointmentFunding.setAccountNumber(salarySettingExpansion.getAccountNumber());
928            pseudoAppointmentFunding.setSubAccountNumber(salarySettingExpansion.getSubAccountNumber());
929            pseudoAppointmentFunding.setFinancialObjectCode(salarySettingExpansion.getFinancialObjectCode());
930            pseudoAppointmentFunding.setFinancialSubObjectCode(salarySettingExpansion.getFinancialSubObjectCode());
931            pseudoAppointmentFunding.setAppointmentFundingDeleteIndicator(false);
932            pseudoAppointmentFunding.setAppointmentRequestedAmount(KualiInteger.ZERO);
933            pseudoAppointmentFunding.refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
934    
935            return pseudoAppointmentFunding;
936        }
937    
938        /**
939         * preprocess the funding reason of the given appointment funding before the funding is saved
940         * 
941         * @param appointmentFunding the given appointment funding
942         */
943        public void preprocessFundingReason(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
944    
945            List<BudgetConstructionAppointmentFundingReason> fundingReasons = appointmentFunding.getBudgetConstructionAppointmentFundingReason();
946            
947            // do special removal of any reason rows where the reason code is blank
948            if (!fundingReasons.isEmpty() && StringUtils.isBlank(fundingReasons.get(0).getAppointmentFundingReasonCode())) {
949                fundingReasons.clear();
950            }
951        }
952    
953        /**
954         * preprocess the leave request of the given appointment funding before the funding is saved
955         * 
956         * @param appointmentFunding the given appointment funding
957         */
958        public void preprocessLeaveRequest(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
959            String durationCode = appointmentFunding.getAppointmentFundingDurationCode();
960    
961            if (StringUtils.isEmpty(durationCode) || StringUtils.equals(durationCode, BCConstants.AppointmentFundingDurationCodes.NONE.durationCode)) {
962                appointmentFunding.setAppointmentRequestedCsfAmount(KualiInteger.ZERO);
963                appointmentFunding.setAppointmentRequestedCsfFteQuantity(BigDecimal.ZERO);
964                appointmentFunding.setAppointmentRequestedCsfTimePercent(BigDecimal.ZERO);
965            }
966        }
967    
968        /**
969         * @see org.kuali.kfs.module.bc.document.service.SalarySettingService#hasExistingFundingReason(org.kuali.kfs.module.bc.businessobject.BudgetConstructionAppointmentFundingReasonCode)
970         */
971        public boolean hasExistingFundingReason(BudgetConstructionAppointmentFundingReasonCode budgetConstructionAppointmentFundingReasonCode) {
972    
973            Map<String, Object> queryMap = new HashMap<String, Object>();
974            queryMap.put("appointmentFundingReasonCode", budgetConstructionAppointmentFundingReasonCode.getAppointmentFundingReasonCode());
975    
976            return (businessObjectService.countMatching(BudgetConstructionAppointmentFundingReason.class, queryMap) > 0);
977        }
978    
979        /**
980         * Sets the kualiConfigurationService attribute value.
981         * 
982         * @param kualiConfigurationService The kualiConfigurationService to set.
983         */
984        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
985            this.kualiConfigurationService = kualiConfigurationService;
986        }
987    
988        /**
989         * Sets the businessObjectService attribute value.
990         * 
991         * @param businessObjectService The businessObjectService to set.
992         */
993        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
994            this.businessObjectService = businessObjectService;
995        }
996    
997        /**
998         * Sets the laborModuleService attribute value.
999         * 
1000         * @param laborModuleService The laborModuleService to set.
1001         */
1002        public void setLaborModuleService(LaborModuleService laborModuleService) {
1003            this.laborModuleService = laborModuleService;
1004        }
1005    
1006        /**
1007         * Sets the budgetDocumentService attribute value.
1008         * 
1009         * @param budgetDocumentService The budgetDocumentService to set.
1010         */
1011        public void setBudgetDocumentService(BudgetDocumentService budgetDocumentService) {
1012            this.budgetDocumentService = budgetDocumentService;
1013        }
1014    
1015        /**
1016         * Sets the benefitsCalculationService attribute value.
1017         * 
1018         * @param benefitsCalculationService The benefitsCalculationService to set.
1019         */
1020        public void setBenefitsCalculationService(BenefitsCalculationService benefitsCalculationService) {
1021            this.benefitsCalculationService = benefitsCalculationService;
1022        }
1023    
1024        /**
1025         * Sets the optionsService attribute value.
1026         * 
1027         * @param optionsService The optionsService to set.
1028         */
1029        public void setOptionsService(OptionsService optionsService) {
1030            this.optionsService = optionsService;
1031        }
1032    
1033        /**
1034         * Sets the lockService attribute value.
1035         * 
1036         * @param lockService The lockService to set.
1037         */
1038        public void setLockService(LockService lockService) {
1039            this.lockService = lockService;
1040        }
1041    
1042        /**
1043         * Gets the documentHelperService attribute.
1044         * 
1045         * @return Returns the documentHelperService.
1046         */
1047        public DocumentHelperService getDocumentHelperService() {
1048            if (documentHelperService == null) {
1049                documentHelperService = SpringContext.getBean(DocumentHelperService.class);
1050            }
1051            return documentHelperService;
1052        }
1053    
1054        /**
1055         * Sets the documentHelperService attribute value.
1056         * 
1057         * @param documentHelperService The documentHelperService to set.
1058         */
1059        public void setDocumentHelperService(DocumentHelperService documentHelperService) {
1060            this.documentHelperService = documentHelperService;
1061        }
1062    
1063        /**
1064         * Sets the documentService attribute value.
1065         * 
1066         * @param documentService The documentService to set.
1067         */
1068        public void setDocumentService(DocumentService documentService) {
1069            this.documentService = documentService;
1070        }
1071    
1072        /**
1073         * Sets the budgetConstructionProcessorService attribute value.
1074         * 
1075         * @param budgetConstructionProcessorService The budgetConstructionProcessorService to set.
1076         */
1077        public void setBudgetConstructionProcessorService(BudgetConstructionProcessorService budgetConstructionProcessorService) {
1078            this.budgetConstructionProcessorService = budgetConstructionProcessorService;
1079        }
1080    }