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.NONE; 019 020 import java.math.BigDecimal; 021 import java.util.List; 022 import java.util.Map; 023 024 import javax.servlet.http.HttpServletRequest; 025 import javax.servlet.http.HttpServletResponse; 026 027 import org.apache.struts.action.ActionForm; 028 import org.apache.struts.action.ActionForward; 029 import org.apache.struts.action.ActionMapping; 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.BCConstants.LockStatus; 034 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus; 035 import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition; 036 import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; 037 import org.kuali.kfs.module.bc.document.service.LockService; 038 import org.kuali.kfs.module.bc.document.service.SalarySettingService; 039 import org.kuali.kfs.module.bc.service.BudgetConstructionPositionService; 040 import org.kuali.kfs.sys.KFSConstants; 041 import org.kuali.kfs.sys.KFSPropertyConstants; 042 import org.kuali.kfs.sys.context.SpringContext; 043 import org.kuali.rice.kns.service.BusinessObjectService; 044 import org.kuali.rice.kns.util.GlobalVariables; 045 import org.kuali.rice.kns.util.MessageList; 046 import org.kuali.rice.kns.util.MessageMap; 047 048 /** 049 * the struts action for the salary setting for position 050 */ 051 public class PositionSalarySettingAction extends DetailSalarySettingAction { 052 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PositionSalarySettingAction.class); 053 054 private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class); 055 private BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class); 056 private BudgetConstructionPositionService budgetConstructionPositionService = SpringContext.getBean(BudgetConstructionPositionService.class); 057 058 /** 059 * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#loadExpansionScreen(org.apache.struts.action.ActionMapping, 060 * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 061 */ 062 @Override 063 public ActionForward loadExpansionScreen(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 064 PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; 065 MessageMap errorMap; 066 if (positionSalarySettingForm.isBudgetByAccountMode()) { 067 errorMap = positionSalarySettingForm.getCallBackErrors(); 068 } 069 else { 070 errorMap = GlobalVariables.getMessageMap(); 071 } 072 073 // update the position record if required 074 ActionForward resyncAction = this.resyncPositionBeforeSalarySetting(mapping, form, request, response); 075 if (resyncAction != null) { 076 return resyncAction; 077 } 078 079 Map<String, Object> fieldValues = positionSalarySettingForm.getKeyMapOfSalarySettingItem(); 080 BudgetConstructionPosition budgetConstructionPosition = (BudgetConstructionPosition) businessObjectService.findByPrimaryKey(BudgetConstructionPosition.class, fieldValues); 081 if (budgetConstructionPosition == null) { 082 String positionNumber = (String) fieldValues.get(KFSPropertyConstants.POSITION_NUMBER); 083 String fiscalYear = (String) fieldValues.get(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR); 084 errorMap.putError(KFSConstants.GLOBAL_MESSAGES, BCKeyConstants.ERROR_POSITION_NOT_FOUND, positionNumber, fiscalYear); 085 086 if (positionSalarySettingForm.isBudgetByAccountMode()) { 087 return this.returnToCaller(mapping, form, request, response); 088 } 089 else { 090 this.cleanupAnySessionForm(mapping, request); 091 return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); 092 } 093 } 094 095 // Lock the position even if there are no current funding lines attached 096 Integer universityFiscalYear = budgetConstructionPosition.getUniversityFiscalYear(); 097 String positionNumber = budgetConstructionPosition.getPositionNumber(); 098 String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId(); 099 100 // attempt to lock position 101 BudgetConstructionLockStatus bcLockStatus = SpringContext.getBean(LockService.class).lockPosition(positionNumber, universityFiscalYear, principalId); 102 if (!bcLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { 103 errorMap.putError(KFSConstants.GLOBAL_ERRORS, BCKeyConstants.ERROR_FAIL_TO_LOCK_POSITION, budgetConstructionPosition.toString()); 104 if (positionSalarySettingForm.isBudgetByAccountMode()) { 105 return this.returnToCaller(mapping, form, request, response); 106 } 107 else { 108 this.cleanupAnySessionForm(mapping, request); 109 return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); 110 } 111 } 112 113 114 positionSalarySettingForm.setBudgetConstructionPosition(budgetConstructionPosition); 115 if (positionSalarySettingForm.isSingleAccountMode()) { 116 positionSalarySettingForm.pickAppointmentFundingsForSingleAccount(); 117 } 118 119 // acquire position and funding locks for the associated funding lines 120 if (!positionSalarySettingForm.isViewOnlyEntry()) { 121 positionSalarySettingForm.postProcessBCAFLines(); 122 positionSalarySettingForm.setNewBCAFLine(positionSalarySettingForm.createNewAppointmentFundingLine()); 123 124 boolean accessModeUpdated = positionSalarySettingForm.updateAccessMode(errorMap); 125 if (!accessModeUpdated) { 126 this.unlockPositionOnly(positionSalarySettingForm); 127 if (positionSalarySettingForm.isBudgetByAccountMode()) { 128 return this.returnToCaller(mapping, form, request, response); 129 } 130 else { 131 this.cleanupAnySessionForm(mapping, request); 132 return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); 133 } 134 } 135 136 boolean gotLocks = positionSalarySettingForm.acquirePositionAndFundingLocks(errorMap); 137 if (!gotLocks) { 138 this.unlockPositionOnly(positionSalarySettingForm); 139 if (positionSalarySettingForm.isBudgetByAccountMode()) { 140 return this.returnToCaller(mapping, form, request, response); 141 } 142 else { 143 this.cleanupAnySessionForm(mapping, request); 144 return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); 145 } 146 } 147 } 148 149 return mapping.findForward(KFSConstants.MAPPING_BASIC); 150 } 151 152 /** 153 * enable to send warning after saving 154 * 155 * @see org.kuali.kfs.module.bc.document.web.struts.DetailSalarySettingAction#save(org.apache.struts.action.ActionMapping, 156 * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 157 */ 158 @Override 159 public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 160 ActionForward saveAction = super.save(mapping, form, request, response); 161 162 PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; 163 this.sendWarnings(positionSalarySettingForm, GlobalVariables.getMessageList()); 164 165 return saveAction; 166 } 167 168 /** 169 * send warning messsages back to the caller 170 * 171 * @param positionSalarySettingForm the given position salary setting form 172 * @param warnings the warning list that can hold the warning messages if any 173 */ 174 public void sendWarnings(PositionSalarySettingForm positionSalarySettingForm, MessageList warnings) { 175 List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings = positionSalarySettingForm.getActiveFundingLines(); 176 if (activeAppointmentFundings == null || activeAppointmentFundings.isEmpty()) { 177 return; 178 } 179 180 boolean hasFundingLineInvolveLeave = this.hasFundingLineInvolvedLeave(activeAppointmentFundings); 181 BudgetConstructionPosition budgetConstructionPosition = positionSalarySettingForm.getBudgetConstructionPosition(); 182 183 BigDecimal positionFte = budgetConstructionPosition.getPositionFullTimeEquivalency().setScale(2, BigDecimal.ROUND_HALF_UP); 184 BigDecimal requestedFteQuantityTotal = positionSalarySettingForm.getAppointmentRequestedFteQuantityTotal().setScale(2, BigDecimal.ROUND_HALF_UP); 185 if (!hasFundingLineInvolveLeave && requestedFteQuantityTotal.compareTo(positionFte) != 0) { 186 warnings.add(BCKeyConstants.WARNING_FTE_NOT_EQUAL); 187 } 188 189 BigDecimal positionStandardHours = budgetConstructionPosition.getPositionStandardHoursDefault().setScale(2, BigDecimal.ROUND_HALF_UP); 190 BigDecimal requestedStandardHoursTotal = positionSalarySettingForm.getAppointmentRequestedStandardHoursTotal().setScale(2, BigDecimal.ROUND_HALF_UP); 191 if (!hasFundingLineInvolveLeave && requestedStandardHoursTotal.compareTo(positionStandardHours) != 0) { 192 warnings.add(BCKeyConstants.WARNING_WORKING_HOUR_NOT_EQUAL); 193 } 194 195 if (positionSalarySettingForm.isPendingPositionSalaryChange()) { 196 warnings.add(BCKeyConstants.WARNING_RECALCULATE_NEEDED); 197 } 198 } 199 200 /** 201 * Recalculates all rows where the position change flags are set and the row is edit-able and active. Sets funding months, FTE, 202 * CSF FTE, and normalizes biweekly request amounts, where appropriate This action is called from the global calculate button. 203 * 204 * @param mapping 205 * @param form 206 * @param request 207 * @param response 208 * @return 209 * @throws Exception 210 */ 211 public ActionForward recalculateAllSalarySettingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 212 PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; 213 214 List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = positionSalarySettingForm.getActiveFundingLines(); 215 for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) { 216 if (!appointmentFunding.isDisplayOnlyMode()) { 217 this.recalculateSalarySettingLine(appointmentFunding); 218 } 219 } 220 return mapping.findForward(KFSConstants.MAPPING_BASIC); 221 } 222 223 /** 224 * Recalculates a single row where the position change flags are set and the row is edit-able and active. Sets funding months, 225 * FTE, CSF FTE, and normalizes biweekly request amounts, where appropriate This action is called from the row action calculate 226 * button. 227 * 228 * @param mapping 229 * @param form 230 * @param request 231 * @param response 232 * @return 233 * @throws Exception 234 */ 235 public ActionForward recalculateSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 236 PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; 237 PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, positionSalarySettingForm); 238 239 this.recalculateSalarySettingLine(appointmentFunding); 240 241 return mapping.findForward(KFSConstants.MAPPING_BASIC); 242 } 243 244 /** 245 * Recalculates a PendingBudgetConstructionAppointmentFunding. Sets funding months, FTE, CSF FTE, and normalizes biweekly 246 * request amounts, where appropriate 247 * 248 * @param appointmentFunding 249 */ 250 protected void recalculateSalarySettingLine(PendingBudgetConstructionAppointmentFunding appointmentFunding) { 251 252 // do the recalc on the passed line and reset the indicator 253 // there are no rule checks involved in this operation 254 if (appointmentFunding.isPositionSalaryChangeIndicator()) { 255 appointmentFunding.setPositionSalaryChangeIndicator(Boolean.FALSE); 256 257 // check if months needs reset 258 if (appointmentFunding.getAppointmentFundingDurationCode().equals(NONE.durationCode)) { 259 if (!appointmentFunding.getAppointmentFundingMonth().equals(appointmentFunding.getBudgetConstructionPosition().getIuNormalWorkMonths())) { 260 appointmentFunding.setAppointmentFundingMonth(appointmentFunding.getBudgetConstructionPosition().getIuNormalWorkMonths()); 261 } 262 } 263 264 // recalc request fte and if hourly, normalize request amount and hourly rate 265 salarySettingService.recalculateDerivedInformation(appointmentFunding); 266 267 } 268 269 270 // doing this just to do cleanup of the default object change flag 271 // the rules will force the user to mark the line delete 272 // if the default object doesn't match with the line object when saving 273 if (appointmentFunding.isPositionObjectChangeIndicator()) { 274 appointmentFunding.setPositionObjectChangeIndicator(Boolean.FALSE); 275 } 276 277 } 278 279 /** 280 * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#getFundingAwareObjectName() 281 */ 282 @Override 283 protected String getFundingAwareObjectName() { 284 return BCPropertyConstants.BUDGET_CONSTRUCTION_POSITION; 285 } 286 287 /** 288 * resyn position brefore performing salary setting 289 */ 290 private ActionForward resyncPositionBeforeSalarySetting(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 291 PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; 292 293 if (!positionSalarySettingForm.isRefreshPositionBeforeSalarySetting()) { 294 return null; 295 } 296 297 Integer universityFiscalYear = positionSalarySettingForm.getUniversityFiscalYear(); 298 String positionNumber = positionSalarySettingForm.getPositionNumber(); 299 String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId(); 300 301 // attempt to lock position and associated funding 302 BudgetConstructionLockStatus bcLockStatus = SpringContext.getBean(LockService.class).lockPositionAndActiveFunding(universityFiscalYear, positionNumber, principalId); 303 if (!bcLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { 304 GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, BCKeyConstants.ERROR_POSITION_LOCK_NOT_OBTAINED, new String[] { universityFiscalYear.toString(), positionNumber }); 305 return this.returnToCaller(mapping, form, request, response); 306 } 307 308 try { 309 budgetConstructionPositionService.refreshPositionFromExternal(universityFiscalYear, positionNumber); 310 } 311 finally { 312 // release locks 313 LockStatus lockStatus = SpringContext.getBean(LockService.class).unlockPositionAndActiveFunding(universityFiscalYear, positionNumber, principalId); 314 if (!lockStatus.equals(BCConstants.LockStatus.SUCCESS)) { 315 LOG.error(String.format("unable to unlock position and active funding records: %s, %s, %s", universityFiscalYear, positionNumber, principalId)); 316 throw new RuntimeException(String.format("unable to unlock position and active funding records: %s, %s, %s", universityFiscalYear, positionNumber, principalId)); 317 } 318 } 319 320 return null; 321 } 322 }