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.web.struts;
017    
018    import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.LWPA;
019    import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.LWPF;
020    import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.NONE;
021    
022    import java.math.BigDecimal;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import javax.servlet.ServletException;
027    import javax.servlet.http.HttpServletRequest;
028    import javax.servlet.http.HttpServletResponse;
029    
030    import org.apache.commons.lang.StringUtils;
031    import org.apache.struts.action.ActionForm;
032    import org.apache.struts.action.ActionForward;
033    import org.apache.struts.action.ActionMapping;
034    import org.kuali.kfs.module.bc.BCConstants;
035    import org.kuali.kfs.module.bc.BCKeyConstants;
036    import org.kuali.kfs.module.bc.BCPropertyConstants;
037    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
038    import org.kuali.kfs.module.bc.document.BudgetConstructionDocument;
039    import org.kuali.kfs.module.bc.document.service.BudgetDocumentService;
040    import org.kuali.kfs.module.bc.document.service.LockService;
041    import org.kuali.kfs.module.bc.document.service.SalarySettingService;
042    import org.kuali.kfs.module.bc.document.validation.event.AddAppointmentFundingEvent;
043    import org.kuali.kfs.module.bc.document.validation.event.BudgetExpansionEvent;
044    import org.kuali.kfs.module.bc.document.validation.event.SaveSalarySettingEvent;
045    import org.kuali.kfs.sys.KFSConstants;
046    import org.kuali.kfs.sys.ObjectUtil;
047    import org.kuali.kfs.sys.context.SpringContext;
048    import org.kuali.rice.kim.bo.Person;
049    import org.kuali.rice.kns.question.ConfirmationQuestion;
050    import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
051    import org.kuali.rice.kns.util.ErrorMap;
052    import org.kuali.rice.kns.util.GlobalVariables;
053    import org.kuali.rice.kns.util.KualiInteger;
054    import org.kuali.rice.kns.util.MessageMap;
055    
056    /**
057     * the base struts action for the detail salary setting
058     */
059    public abstract class DetailSalarySettingAction extends SalarySettingBaseAction {
060        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DetailSalarySettingAction.class);
061    
062        private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class);
063        private BudgetDocumentService budgetDocumentService = SpringContext.getBean(BudgetDocumentService.class);
064        private Person currentUser = GlobalVariables.getUserSession().getPerson();
065    
066        /**
067         * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#execute(org.apache.struts.action.ActionMapping,
068         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
069         */
070        @Override
071        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
072            ActionForward executeAction = null;
073    
074            try {
075                executeAction = super.execute(mapping, form, request, response);
076            }
077            catch (Exception e) {
078                // release all locks when encountering runtime exception
079                DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form;
080                if (!salarySettingForm.isViewOnlyEntry()) {
081                    salarySettingForm.releaseTransactionLocks();
082                    salarySettingForm.releasePositionAndFundingLocks();
083                }
084    
085                LOG.fatal("Unexpected errors occurred.", e);
086    
087                // re-throw the exception
088                throw new ServletException(e);
089            }
090    
091            return executeAction;
092        }
093    
094        /**
095         * @see org.kuali.kfs.module.bc.document.web.struts.BudgetExpansionAction#close(org.apache.struts.action.ActionMapping,
096         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
097         */
098        @Override
099        public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
100            DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form;
101            String buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON);
102            boolean isClose = StringUtils.equals(ConfirmationQuestion.YES, buttonClicked) || StringUtils.equals(ConfirmationQuestion.NO, buttonClicked);
103    
104            ActionForward closeActionForward;
105            if (salarySettingForm.isViewOnlyEntry() || salarySettingForm.isSalarySettingClosed()) {
106                closeActionForward = this.returnAfterClose(salarySettingForm, mapping, request, response);
107            }
108            else {
109                closeActionForward = super.close(mapping, salarySettingForm, request, response);
110            }
111    
112            // release all locks before closing the current expansion screen
113            if (isClose && !salarySettingForm.isViewOnlyEntry() && salarySettingForm.isSalarySettingClosed()) {
114                salarySettingForm.releasePositionAndFundingLocks();
115                if (form instanceof PositionSalarySettingForm){
116                    // handle case where there are no funding lines attached to position
117                    this.unlockPositionOnly((PositionSalarySettingForm) form);
118                }
119            }
120    
121            return closeActionForward;
122        }
123    
124        /**
125         * @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping,
126         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
127         */
128        @Override
129        public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
130            super.refresh(mapping, form, request, response);
131    
132            DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form;
133            String refreshCaller = request.getParameter(KFSConstants.REFRESH_CALLER);
134    
135            if (refreshCaller != null && refreshCaller.endsWith(KFSConstants.LOOKUPABLE_SUFFIX)) {
136                salarySettingForm.refreshBCAFLine(salarySettingForm.getNewBCAFLine());
137            }
138    
139            return mapping.findForward(KFSConstants.MAPPING_BASIC);
140        }
141    
142        /**
143         * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#save(org.apache.struts.action.ActionMapping,
144         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
145         */
146        @Override
147        public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
148            DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form;
149            List<PendingBudgetConstructionAppointmentFunding> savableAppointmentFundings = salarySettingForm.getSavableAppointmentFundings();
150            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
151    
152            if (savableAppointmentFundings == null || savableAppointmentFundings.isEmpty()) {
153                GlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_SALARY_SETTING_SAVED);
154                return mapping.findForward(KFSConstants.MAPPING_BASIC);
155            }
156    
157            MessageMap errorMap = GlobalVariables.getMessageMap();
158            for (PendingBudgetConstructionAppointmentFunding savableFunding : savableAppointmentFundings) {
159                String errorKeyPrefix = this.getErrorKeyPrefixOfAppointmentFundingLine(appointmentFundings, savableFunding);
160    
161                // retrieve corresponding document in advance in order to use the rule framework
162                BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(savableFunding);
163                if (document == null) {
164                    errorMap.putError(errorKeyPrefix, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, savableFunding.getAppointmentFundingString());
165                    return mapping.findForward(KFSConstants.MAPPING_BASIC);
166                }
167    
168                // bypass validation rules if the funding line has been marked as purged or deleted
169                if (savableFunding.isPurged() || savableFunding.isAppointmentFundingDeleteIndicator()) {
170                    continue;
171                }
172    
173                salarySettingService.recalculateDerivedInformation(savableFunding);
174    
175                // validate the savable appointment funding lines
176                boolean isValid = this.invokeRules(new SaveSalarySettingEvent(KFSConstants.EMPTY_STRING, errorKeyPrefix, document, savableFunding));
177                if (!isValid) {
178                    return mapping.findForward(KFSConstants.MAPPING_BASIC);
179                }
180            }
181    
182            // acquire transaction locks for all funding lines
183            boolean transactionLocked = salarySettingForm.acquireTransactionLocks(GlobalVariables.getMessageMap());
184            if (!transactionLocked) {
185                return mapping.findForward(KFSConstants.MAPPING_BASIC);
186            }
187    
188            // test if Form is IncumbentSS and call saveSalarySetting() with a isSalarySettingByIncumbent true
189            // so it knows when to remove purged funding position locks when it is the last line for the position
190            if (form instanceof IncumbentSalarySettingForm) {
191                salarySettingService.saveSalarySetting(savableAppointmentFundings, Boolean.TRUE);
192            }
193            else {
194                salarySettingService.saveSalarySetting(savableAppointmentFundings, Boolean.FALSE);
195            }
196    
197            // release all transaction locks
198            salarySettingForm.releaseTransactionLocks();
199    
200            this.clearPurgedAppointmentFundings(appointmentFundings);
201    
202            GlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_SALARY_SETTING_SAVED);
203            return mapping.findForward(KFSConstants.MAPPING_BASIC);
204        }
205    
206        /**
207         * adds an appointment funding line to the set of existing funding lines
208         */
209        public ActionForward addAppointmentFundingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
210            DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form;
211            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
212    
213            PendingBudgetConstructionAppointmentFunding newAppointmentFunding = salarySettingForm.getNewBCAFLine();
214            SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(newAppointmentFunding);
215    
216            salarySettingForm.refreshBCAFLine(newAppointmentFunding);
217    
218            // setup a working appointment funding so that the default values can be applied
219            PendingBudgetConstructionAppointmentFunding workingAppointmentFunding = new PendingBudgetConstructionAppointmentFunding();
220            ObjectUtil.buildObject(workingAppointmentFunding, newAppointmentFunding);
221            this.applyDefaultValuesIfEmpty(workingAppointmentFunding);
222    
223            MessageMap errorMap = GlobalVariables.getMessageMap();
224    
225            // retrieve corresponding document in advance in order to use the rule framework
226            BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(workingAppointmentFunding);
227            if (document == null) {
228                errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, workingAppointmentFunding.getAppointmentFundingString());
229                return mapping.findForward(KFSConstants.MAPPING_BASIC);
230            }
231    
232            // check special case where emplid is vacant and force funding duration to none
233            String emplid = workingAppointmentFunding.getEmplid(); 
234            if (StringUtils.isNotEmpty(emplid) && StringUtils.equals(emplid, BCConstants.VACANT_EMPLID)){
235                workingAppointmentFunding.setAppointmentFundingDurationCode(BCConstants.AppointmentFundingDurationCodes.NONE.durationCode);
236            }
237            salarySettingService.recalculateDerivedInformation(workingAppointmentFunding);
238    
239            // validate the new appointment funding line
240            BudgetExpansionEvent addAppointmentFundingEvent = new AddAppointmentFundingEvent(KFSConstants.EMPTY_STRING, BCPropertyConstants.NEW_BCAF_LINE, document, appointmentFundings, workingAppointmentFunding);
241            boolean isValid = this.invokeRules(addAppointmentFundingEvent);
242            if (!isValid) {
243                return mapping.findForward(KFSConstants.MAPPING_BASIC);
244            }
245    
246            // set remaining flags
247            boolean vacatable = salarySettingService.canBeVacant(appointmentFundings, workingAppointmentFunding);
248            workingAppointmentFunding.setVacatable(vacatable);
249    
250            Integer fiscalYear = workingAppointmentFunding.getUniversityFiscalYear();
251            String chartCode = workingAppointmentFunding.getChartOfAccountsCode();
252            String objectCode = workingAppointmentFunding.getFinancialObjectCode();
253            boolean hourlyPaid = salarySettingService.isHourlyPaidObject(fiscalYear, chartCode, objectCode);
254            workingAppointmentFunding.setHourlyPaid(hourlyPaid);
255    
256            // update the access flags of the current funding line
257            boolean accessModeUpdated = salarySettingForm.updateAccessMode(workingAppointmentFunding, errorMap);
258            if (!accessModeUpdated) {
259                return mapping.findForward(KFSConstants.MAPPING_BASIC);
260            }
261    
262            // have no permission to do salary setting on the new line
263            if (workingAppointmentFunding.isDisplayOnlyMode()) {
264                errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_NO_SALARY_SETTING_PERMISSION, workingAppointmentFunding.getAppointmentFundingString());
265                return mapping.findForward(KFSConstants.MAPPING_BASIC);
266            }
267    
268            // acquire a lock for the new appointment funding line
269            boolean gotLocks = salarySettingForm.acquirePositionAndFundingLocks(workingAppointmentFunding, errorMap);
270            if (!gotLocks) {
271                return mapping.findForward(KFSConstants.MAPPING_BASIC);
272            }
273    
274            appointmentFundings.add(workingAppointmentFunding);
275            salarySettingForm.setNewBCAFLine(salarySettingForm.createNewAppointmentFundingLine());
276    
277            return mapping.findForward(KFSConstants.MAPPING_BASIC);
278        }
279    
280        // determine whether any active funding line is involved leave
281        protected boolean hasFundingLineInvolvedLeave(List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings) {
282            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : activeAppointmentFundings) {
283                String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();
284    
285                if (!StringUtils.equals(leaveDurationCode, NONE.durationCode)) {
286                    return true;
287                }
288            }
289            return false;
290        }
291    
292        // determine whether any active funding line is involved in leave without pay
293        protected boolean hasFundingLineInvolvedLeaveWithoutPay(List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings) {
294            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : activeAppointmentFundings) {
295                String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode();
296    
297                if (StringUtils.equals(leaveDurationCode, LWPA.durationCode) || StringUtils.equals(leaveDurationCode, LWPF.durationCode)) {
298                    return true;
299                }
300            }
301            return false;
302        }
303    
304        // apply the default values to the certain fields when the fields are empty
305        protected void applyDefaultValuesIfEmpty(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
306            if (StringUtils.isBlank(appointmentFunding.getSubAccountNumber())) {
307                appointmentFunding.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
308            }
309    
310            if (StringUtils.isBlank(appointmentFunding.getFinancialSubObjectCode())) {
311                appointmentFunding.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
312            }
313    
314            if (appointmentFunding.getAppointmentTotalIntendedAmount() == null) {
315                appointmentFunding.setAppointmentTotalIntendedAmount(KualiInteger.ZERO);
316                appointmentFunding.setAppointmentTotalIntendedFteQuantity(BigDecimal.ZERO);
317            }
318    
319            if (appointmentFunding.getAppointmentTotalIntendedFteQuantity() == null) {
320                appointmentFunding.setAppointmentTotalIntendedFteQuantity(BigDecimal.ZERO);
321            }
322        }
323    
324        // clear the appointment funding lines that have been purged
325        protected void clearPurgedAppointmentFundings(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings) {
326            List<PendingBudgetConstructionAppointmentFunding> purgedAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
327            for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) {
328                if (appointmentFunding.isPurged()) {
329                    purgedAppointmentFundings.add(appointmentFunding);
330                }
331            }
332            appointmentFundings.removeAll(purgedAppointmentFundings);
333        }
334    
335        /**
336         * unlock the position only, called as last action before a close or exit
337         * handling the case where there are no funding lines attached yet.
338         * 
339         */
340        protected void unlockPositionOnly(PositionSalarySettingForm positionSalarySettingForm){
341    
342            Integer universityFiscalYear = positionSalarySettingForm.getBudgetConstructionPosition().getUniversityFiscalYear();
343            String positionNumber = positionSalarySettingForm.getBudgetConstructionPosition().getPositionNumber();
344            String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId();
345    
346            // unlock position
347            SpringContext.getBean(LockService.class).unlockPosition(positionNumber, universityFiscalYear, principalId);
348            
349        }
350    }