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 }