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 }