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 java.text.MessageFormat;
019    import java.util.List;
020    
021    import javax.servlet.http.HttpServletRequest;
022    import javax.servlet.http.HttpServletResponse;
023    import javax.servlet.http.HttpSession;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.struts.action.ActionForm;
027    import org.apache.struts.action.ActionForward;
028    import org.apache.struts.action.ActionMapping;
029    import org.kuali.kfs.fp.service.FiscalYearFunctionControlService;
030    import org.kuali.kfs.module.bc.BCConstants;
031    import org.kuali.kfs.module.bc.BCKeyConstants;
032    import org.kuali.kfs.module.bc.BCPropertyConstants;
033    import org.kuali.kfs.module.bc.businessobject.BudgetConstructionAuthorizationStatus;
034    import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding;
035    import org.kuali.kfs.module.bc.document.BudgetConstructionDocument;
036    import org.kuali.kfs.module.bc.document.service.BudgetDocumentService;
037    import org.kuali.kfs.module.bc.document.service.SalarySettingService;
038    import org.kuali.kfs.module.bc.document.validation.event.AdjustSalarySettingLinePercentEvent;
039    import org.kuali.kfs.module.bc.document.validation.event.BudgetExpansionEvent;
040    import org.kuali.kfs.module.bc.document.validation.event.NormalizePayrateAndAmountEvent;
041    import org.kuali.kfs.sys.KFSConstants;
042    import org.kuali.kfs.sys.KFSKeyConstants;
043    import org.kuali.kfs.sys.context.SpringContext;
044    import org.kuali.rice.kim.bo.Person;
045    import org.kuali.rice.kim.service.IdentityManagementService;
046    import org.kuali.rice.kns.exception.AuthorizationException;
047    import org.kuali.rice.kns.question.ConfirmationQuestion;
048    import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
049    import org.kuali.rice.kns.service.BusinessObjectService;
050    import org.kuali.rice.kns.service.KualiConfigurationService;
051    import org.kuali.rice.kns.service.KualiRuleService;
052    import org.kuali.rice.kns.util.GlobalVariables;
053    import org.kuali.rice.kns.util.KNSConstants;
054    
055    /**
056     * the base action class for salary setting, which provides the implementations of common actions of the salary setting screens
057     */
058    public abstract class SalarySettingBaseAction extends BudgetExpansionAction {
059        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SalarySettingBaseAction.class);
060    
061        private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class);
062        private BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
063        private KualiConfigurationService kualiConfiguration = SpringContext.getBean(KualiConfigurationService.class);
064        private BudgetDocumentService budgetDocumentService = SpringContext.getBean(BudgetDocumentService.class);
065        private KualiRuleService kualiRuleService = SpringContext.getBean(KualiRuleService.class);
066    
067        /**
068         * loads the data for the expansion screen based on the passed in url parameters
069         */
070        public abstract ActionForward loadExpansionScreen(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception;
071    
072        /**
073         * @see org.kuali.rice.kns.web.struts.action.KualiAction#execute(org.apache.struts.action.ActionMapping,
074         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
075         */
076        @Override
077        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
078            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
079    
080            // if org sal setting we need to initialize authorization
081            if (!salarySettingForm.isBudgetByAccountMode() && salarySettingForm.getMethodToCall().equals(BCConstants.LOAD_EXPANSION_SCREEN_METHOD)) {
082                initAuthorization(salarySettingForm);
083            }
084    
085            populateAuthorizationFields(salarySettingForm);
086    
087            ActionForward forward = super.execute(mapping, form, request, response);
088            if (!salarySettingForm.isLostSession()) {
089                salarySettingForm.postProcessBCAFLines();
090    
091                // re-init the session form if session scoped
092                if (salarySettingForm.getMethodToCall().equals("refresh")) {
093                    if (BCConstants.MAPPING_SCOPE_SESSION.equals(mapping.getScope())) {
094                        HttpSession sess = request.getSession(Boolean.FALSE);
095                        String formName = mapping.getAttribute();
096                        sess.setAttribute(formName, salarySettingForm);
097                    }
098                }
099            }
100            return forward;
101        }
102    
103        protected void populateAuthorizationFields(SalarySettingBaseForm salarySettingForm) {
104            BudgetConstructionAuthorizationStatus authorizationStatus = (BudgetConstructionAuthorizationStatus) GlobalVariables.getUserSession().retrieveObject(BCConstants.BC_DOC_AUTHORIZATION_STATUS_SESSIONKEY);
105    
106            if (authorizationStatus == null) {
107                // just return, BudgetExpansionAction.execute() will see the session time out
108                // and redirect back to BudgetConstructionSelection
109                return;
110            }
111    
112            salarySettingForm.setDocumentActions(authorizationStatus.getDocumentActions());
113            salarySettingForm.setEditingMode(authorizationStatus.getEditingMode());
114        }
115    
116        protected void initAuthorization(SalarySettingBaseForm salarySettingForm) {
117            Person user = GlobalVariables.getUserSession().getPerson();
118    
119            boolean isAuthorized = SpringContext.getBean(IdentityManagementService.class).isAuthorized(user.getPrincipalId(), BCConstants.BUDGET_CONSTRUCTION_NAMESPACE, BCConstants.KimConstants.USE_ORG_SALARY_SETTING_PERMISSION_NAME, null, null);
120            if (isAuthorized) {
121                salarySettingForm.getDocumentActions().put(KNSConstants.KUALI_ACTION_CAN_EDIT, KNSConstants.KUALI_DEFAULT_TRUE_VALUE);
122            }
123            else {
124                throw new AuthorizationException(user.getName(), "view", salarySettingForm.getAccountNumber() + ", " + salarySettingForm.getSubAccountNumber());
125            }
126    
127            if (!SpringContext.getBean(FiscalYearFunctionControlService.class).isBudgetUpdateAllowed(salarySettingForm.getUniversityFiscalYear())) {
128                salarySettingForm.getEditingMode().put(BCConstants.EditModes.SYSTEM_VIEW_ONLY, KNSConstants.KUALI_DEFAULT_TRUE_VALUE);
129            }
130    
131            BudgetConstructionAuthorizationStatus editStatus = new BudgetConstructionAuthorizationStatus();
132            editStatus.setDocumentActions(salarySettingForm.getDocumentActions());
133            editStatus.setEditingMode(salarySettingForm.getEditingMode());
134    
135            GlobalVariables.getUserSession().addObject(BCConstants.BC_DOC_AUTHORIZATION_STATUS_SESSIONKEY, editStatus);
136        }
137    
138        /**
139         * save the information in the current form into underlying data store
140         */
141        public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
142            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_MESSAGES, KFSKeyConstants.ERROR_UNIMPLEMENTED, "Save For Salary Setting by Incumbent");
143            return mapping.findForward(KFSConstants.MAPPING_BASIC);
144        }
145    
146        /**
147         * @see org.kuali.kfs.module.bc.document.web.struts.BudgetExpansionAction#close(org.apache.struts.action.ActionMapping,
148         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
149         */
150        @Override
151        public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
152            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
153    
154            // ask a question before closing unless it has been answered
155            String question = request.getParameter(KFSConstants.QUESTION_INST_ATTRIBUTE_NAME);
156            if (StringUtils.isBlank(question)) {
157                String questionText = kualiConfiguration.getPropertyString(KFSKeyConstants.QUESTION_SAVE_BEFORE_CLOSE);
158                return this.performQuestionWithoutInput(mapping, salarySettingForm, request, response, KFSConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION, questionText, KFSConstants.CONFIRMATION_QUESTION, KFSConstants.MAPPING_CLOSE, "");
159            }
160    
161            // save the salary setting if the user answers to the question with "Yes" (save and close)
162            String buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON);
163            if (StringUtils.equals(KFSConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION, question) && StringUtils.equals(ConfirmationQuestion.YES, buttonClicked)) {
164                ActionForward saveAction = this.save(mapping, salarySettingForm, request, response);
165    
166                return saveAction;
167            }
168    
169            // indicate the salary setting has been closed
170            salarySettingForm.setSalarySettingClosed(true);
171    
172            return this.returnAfterClose(salarySettingForm, mapping, request, response);
173        }
174    
175        /**
176         * vacate the specified appointment funding line
177         */
178        public ActionForward vacateSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
179            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
180            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
181            PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, salarySettingForm);
182    
183            salarySettingService.vacateAppointmentFunding(appointmentFundings, appointmentFunding);
184    
185            return mapping.findForward(KFSConstants.MAPPING_BASIC);
186        }
187    
188        /**
189         * mark the selected salary setting line as purged
190         */
191        public ActionForward purgeSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
192            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
193    
194            this.getSelectedFundingLine(request, salarySettingForm).setPurged(true);
195    
196            return mapping.findForward(KFSConstants.MAPPING_BASIC);
197        }
198    
199        /**
200         * restore the selected salary setting line if it is marked as purged
201         */
202        public ActionForward restorePurgedSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
203            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
204    
205            this.getSelectedFundingLine(request, salarySettingForm).setPurged(false);
206    
207            return mapping.findForward(KFSConstants.MAPPING_BASIC);
208        }
209    
210        /**
211         * mark the selected salary setting line as deleted
212         */
213        public ActionForward deleteSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
214            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
215            PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, salarySettingForm);
216    
217            salarySettingService.markAsDelete(appointmentFunding);
218    
219            return mapping.findForward(KFSConstants.MAPPING_BASIC);
220        }
221    
222        /**
223         * unmark the selected salary setting line that has been marked as deleted
224         */
225        public ActionForward undeleteSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
226            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
227    
228            this.getSelectedFundingLine(request, salarySettingForm).setAppointmentFundingDeleteIndicator(false);
229    
230            return mapping.findForward(KFSConstants.MAPPING_BASIC);
231        }
232    
233        /**
234         * revert the selected salary setting line that just has been marked as deleted
235         */
236        public ActionForward revertSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
237            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
238            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
239            PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, salarySettingForm);
240    
241            salarySettingService.revert(appointmentFundings, appointmentFunding);
242    
243            return mapping.findForward(KFSConstants.MAPPING_BASIC);
244        }
245    
246        /**
247         * adjust the salary amount of the specified funding line
248         */
249        public ActionForward adjustSalarySettingLinePercent(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
250            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
251            PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, salarySettingForm);
252            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
253    
254            String errorKeyPrefix = this.getErrorKeyPrefixOfAppointmentFundingLine(appointmentFundings, appointmentFunding);
255    
256            // retrieve corresponding document in advance in order to use the rule framework
257            BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(appointmentFunding);
258            if (document == null) {
259                GlobalVariables.getMessageMap().putError(errorKeyPrefix, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, appointmentFunding.getAppointmentFundingString());
260                return mapping.findForward(KFSConstants.MAPPING_BASIC);
261            }
262    
263            return this.adjustSalarySettingLinePercent(mapping, salarySettingForm, appointmentFunding, document, errorKeyPrefix);
264        }
265    
266        /**
267         * adjust the requested salary amount of the given appointment funding line by pecent or given amount
268         */
269        public ActionForward adjustSalarySettingLinePercent(ActionMapping mapping, ActionForm form, PendingBudgetConstructionAppointmentFunding appointmentFunding, BudgetConstructionDocument document, String errorKeyPrefix) {
270            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
271    
272            // validate the new appointment funding line
273            BudgetExpansionEvent adjustPercentEvent = new AdjustSalarySettingLinePercentEvent(KFSConstants.EMPTY_STRING, errorKeyPrefix, document, appointmentFunding);
274            boolean isValid = this.invokeRules(adjustPercentEvent);
275            if (!isValid) {
276                return mapping.findForward(KFSConstants.MAPPING_BASIC);
277            }
278    
279            this.adjustSalary(appointmentFunding);
280            return mapping.findForward(KFSConstants.MAPPING_BASIC);
281        }
282    
283        /**
284         * adjust the requested salary amount of the given appointment funding line
285         */
286        protected void adjustSalary(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
287            String adjustmentMeasurement = appointmentFunding.getAdjustmentMeasurement();
288            if (BCConstants.SalaryAdjustmentMeasurement.PERCENT.measurement.equals(adjustmentMeasurement)) {
289                salarySettingService.adjustRequestedSalaryByPercent(appointmentFunding);
290            }
291            else if (BCConstants.SalaryAdjustmentMeasurement.AMOUNT.measurement.equals(adjustmentMeasurement)) {
292                salarySettingService.adjustRequestedSalaryByAmount(appointmentFunding);
293            }
294        }
295    
296        /**
297         * normalize the hourly pay rate and annual pay amount
298         */
299        public ActionForward normalizePayRateAndAmount(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
300            SalarySettingBaseForm salarySettingForm = (SalarySettingBaseForm) form;
301            PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, salarySettingForm);
302            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
303            String errorKeyPrefix = this.getErrorKeyPrefixOfAppointmentFundingLine(appointmentFundings, appointmentFunding);
304    
305            // retrieve corresponding document in advance in order to use the rule framework
306            BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(appointmentFunding);
307            if (document == null) {
308                GlobalVariables.getMessageMap().putError(errorKeyPrefix, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, appointmentFunding.getAppointmentFundingString());
309                return mapping.findForward(KFSConstants.MAPPING_BASIC);
310            }
311    
312            // validate the new appointment funding line
313            BudgetExpansionEvent normalizePayRateAndAmountEvent = new NormalizePayrateAndAmountEvent(KFSConstants.EMPTY_STRING, errorKeyPrefix, document, appointmentFunding);
314            boolean isValid = this.invokeRules(normalizePayRateAndAmountEvent);
315            if (!isValid) {
316                return mapping.findForward(KFSConstants.MAPPING_BASIC);
317            }
318    
319            salarySettingService.normalizePayRateAndAmount(appointmentFunding);
320            return mapping.findForward(KFSConstants.MAPPING_BASIC);
321        }
322    
323        /**
324         * get the selected appointment funding line
325         */
326        protected PendingBudgetConstructionAppointmentFunding getSelectedFundingLine(HttpServletRequest request, SalarySettingBaseForm salarySettingForm) {
327            List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings();
328    
329            int indexOfSelectedLine = this.getSelectedLine(request);
330            return appointmentFundings.get(indexOfSelectedLine);
331        }
332    
333        /**
334         * execute the rules associated with the given event
335         * 
336         * @param event the event that just occured
337         * @return true if the rules associated with the given event pass; otherwise, false
338         */
339        protected boolean invokeRules(KualiDocumentEvent event) {
340            return kualiRuleService.applyRules(event);
341        }
342    
343        /**
344         * build the error key prefix based on the given information
345         * 
346         * @param fundingAwareObjectName the name of object that holds the given set of appointment funding lines
347         * @param appointmentFundings the given set of appointment funding lines
348         * @param appointmentFunding the given appointment funding line
349         * @return the error key prefix built from the given information
350         */
351        protected String getErrorKeyPrefixOfAppointmentFundingLine(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings, PendingBudgetConstructionAppointmentFunding appointmentFunding) {
352            int indexOfFundingLine = appointmentFundings.indexOf(appointmentFunding);
353            String pattern = "{0}.{1}[{2}]";
354    
355            return MessageFormat.format(pattern, this.getFundingAwareObjectName(), BCPropertyConstants.PENDING_BUDGET_CONSTRUCTION_APPOINTMENT_FUNDING, indexOfFundingLine);
356        }
357    
358        /**
359         * return after salary setting is closed
360         */
361        protected ActionForward returnAfterClose(SalarySettingBaseForm salarySettingForm, ActionMapping mapping, HttpServletRequest request, HttpServletResponse response) throws Exception {
362            if (salarySettingForm.isBudgetByAccountMode()) {
363                salarySettingForm.getCallBackMessages().add(BCKeyConstants.MESSAGE_BUDGET_SUCCESSFUL_CLOSE);
364                return this.returnToCaller(mapping, salarySettingForm, request, response);
365            }
366    
367            this.cleanupAnySessionForm(mapping, request);
368            GlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_BUDGET_SUCCESSFUL_CLOSE);
369            return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING);
370        }
371    
372        /**
373         * get the name of object that holds a set of appointment funding lines
374         */
375        protected abstract String getFundingAwareObjectName();
376    }