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 }