/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.bc.document.web.struts; import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.NONE; import java.math.BigDecimal; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.kuali.kfs.module.bc.BCConstants; import org.kuali.kfs.module.bc.BCKeyConstants; import org.kuali.kfs.module.bc.BCPropertyConstants; import org.kuali.kfs.module.bc.BCConstants.LockStatus; import org.kuali.kfs.module.bc.BCConstants.SynchronizationCheckType; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionLockStatus; import org.kuali.kfs.module.bc.businessobject.BudgetConstructionPosition; import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; import org.kuali.kfs.module.bc.document.service.LockService; import org.kuali.kfs.module.bc.document.service.SalarySettingService; import org.kuali.kfs.module.bc.service.BudgetConstructionPositionService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.kns.util.KNSGlobalVariables; import org.kuali.rice.kns.util.MessageList; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.MessageMap; /** * the struts action for the salary setting for position */ public class PositionSalarySettingAction extends DetailSalarySettingAction { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PositionSalarySettingAction.class); private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class); private BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class); private BudgetConstructionPositionService budgetConstructionPositionService = SpringContext.getBean(BudgetConstructionPositionService.class); /** * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#loadExpansionScreen(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward loadExpansionScreen(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; MessageMap errorMap; if (positionSalarySettingForm.isBudgetByAccountMode()) { errorMap = positionSalarySettingForm.getCallBackErrors(); } else { errorMap = GlobalVariables.getMessageMap(); } // update the position record if required ActionForward resyncAction = this.resyncPositionBeforeSalarySetting(mapping, form, request, response); if (resyncAction != null) { return resyncAction; } Map<String, Object> fieldValues = positionSalarySettingForm.getKeyMapOfSalarySettingItem(); BudgetConstructionPosition budgetConstructionPosition = businessObjectService.findByPrimaryKey(BudgetConstructionPosition.class, fieldValues); if (budgetConstructionPosition == null) { String positionNumber = (String) fieldValues.get(KFSPropertyConstants.POSITION_NUMBER); String fiscalYear = (String) fieldValues.get(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR); errorMap.putError(KFSConstants.GLOBAL_MESSAGES, BCKeyConstants.ERROR_POSITION_NOT_FOUND, positionNumber, fiscalYear); if (positionSalarySettingForm.isBudgetByAccountMode()) { return this.returnToCaller(mapping, form, request, response); } else { this.cleanupAnySessionForm(mapping, request); return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); } } if (!positionSalarySettingForm.isViewOnlyEntry()) { // Lock the position even if there are no current funding lines attached Integer universityFiscalYear = budgetConstructionPosition.getUniversityFiscalYear(); String positionNumber = budgetConstructionPosition.getPositionNumber(); String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId(); // attempt to lock position BudgetConstructionLockStatus bcLockStatus = SpringContext.getBean(LockService.class).lockPosition(positionNumber, universityFiscalYear, principalId); if (!bcLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { errorMap.putError(KFSConstants.GLOBAL_ERRORS, BCKeyConstants.ERROR_FAIL_TO_LOCK_POSITION, "Position Number:"+budgetConstructionPosition.getPositionNumber()+", Fiscal Year:"+budgetConstructionPosition.getUniversityFiscalYear().toString()+", Desc:"+budgetConstructionPosition.getPositionDescription()+", Locked By:"+budgetConstructionPosition.getPositionLockUser().getPrincipalName()); if (positionSalarySettingForm.isBudgetByAccountMode()) { return this.returnToCaller(mapping, form, request, response); } else { this.cleanupAnySessionForm(mapping, request); return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); } } } positionSalarySettingForm.setBudgetConstructionPosition(budgetConstructionPosition); if (positionSalarySettingForm.isSingleAccountMode()) { positionSalarySettingForm.pickAppointmentFundingsForSingleAccount(); } // acquire position and funding locks for the associated funding lines if (!positionSalarySettingForm.isViewOnlyEntry()) { positionSalarySettingForm.postProcessBCAFLines(); positionSalarySettingForm.setNewBCAFLine(positionSalarySettingForm.createNewAppointmentFundingLine()); boolean accessModeUpdated = positionSalarySettingForm.updateAccessMode(errorMap); if (!accessModeUpdated) { this.unlockPositionOnly(positionSalarySettingForm); if (positionSalarySettingForm.isBudgetByAccountMode()) { return this.returnToCaller(mapping, form, request, response); } else { this.cleanupAnySessionForm(mapping, request); return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); } } boolean gotLocks = positionSalarySettingForm.acquirePositionAndFundingLocks(errorMap); if (!gotLocks) { this.unlockPositionOnly(positionSalarySettingForm); if (positionSalarySettingForm.isBudgetByAccountMode()) { return this.returnToCaller(mapping, form, request, response); } else { this.cleanupAnySessionForm(mapping, request); return mapping.findForward(BCConstants.MAPPING_ORGANIZATION_SALARY_SETTING_RETURNING); } } } return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * enable to send warning after saving * * @see org.kuali.kfs.module.bc.document.web.struts.DetailSalarySettingAction#save(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionForward saveAction = super.save(mapping, form, request, response); PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; this.sendWarnings(positionSalarySettingForm, KNSGlobalVariables.getMessageList()); return saveAction; } /** * send warning messsages back to the caller * * @param positionSalarySettingForm the given position salary setting form * @param warnings the warning list that can hold the warning messages if any */ public void sendWarnings(PositionSalarySettingForm positionSalarySettingForm, MessageList warnings) { List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings = positionSalarySettingForm.getActiveFundingLines(); if (activeAppointmentFundings == null || activeAppointmentFundings.isEmpty()) { return; } boolean hasFundingLineInvolveLeave = this.hasFundingLineInvolvedLeave(activeAppointmentFundings); BudgetConstructionPosition budgetConstructionPosition = positionSalarySettingForm.getBudgetConstructionPosition(); BigDecimal positionFte = budgetConstructionPosition.getPositionFullTimeEquivalency().setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal requestedFteQuantityTotal = positionSalarySettingForm.getAppointmentRequestedFteQuantityTotal().setScale(2, BigDecimal.ROUND_HALF_UP); if (!hasFundingLineInvolveLeave && requestedFteQuantityTotal.compareTo(positionFte) != 0) { warnings.add(BCKeyConstants.WARNING_FTE_NOT_EQUAL); } BigDecimal positionStandardHours = budgetConstructionPosition.getPositionStandardHoursDefault().setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal requestedStandardHoursTotal = positionSalarySettingForm.getAppointmentRequestedStandardHoursTotal().setScale(2, BigDecimal.ROUND_HALF_UP); if (!hasFundingLineInvolveLeave && requestedStandardHoursTotal.compareTo(positionStandardHours) != 0) { warnings.add(BCKeyConstants.WARNING_WORKING_HOUR_NOT_EQUAL); } if (positionSalarySettingForm.isPendingPositionSalaryChange()) { warnings.add(BCKeyConstants.WARNING_RECALCULATE_NEEDED); } } /** * Recalculates all rows where the position change flags are set and the row is edit-able and active. Sets funding months, FTE, * CSF FTE, and normalizes biweekly request amounts, where appropriate This action is called from the global calculate button. * * @param mapping * @param form * @param request * @param response * @return * @throws Exception */ public ActionForward recalculateAllSalarySettingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = positionSalarySettingForm.getActiveFundingLines(); for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) { if (!appointmentFunding.isDisplayOnlyMode()) { this.recalculateSalarySettingLine(appointmentFunding); } } return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * Recalculates a single row where the position change flags are set and the row is edit-able and active. Sets funding months, * FTE, CSF FTE, and normalizes biweekly request amounts, where appropriate This action is called from the row action calculate * button. * * @param mapping * @param form * @param request * @param response * @return * @throws Exception */ public ActionForward recalculateSalarySettingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; PendingBudgetConstructionAppointmentFunding appointmentFunding = this.getSelectedFundingLine(request, positionSalarySettingForm); this.recalculateSalarySettingLine(appointmentFunding); return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * Recalculates a PendingBudgetConstructionAppointmentFunding. Sets funding months, FTE, CSF FTE, and normalizes biweekly * request amounts, where appropriate * * @param appointmentFunding */ protected void recalculateSalarySettingLine(PendingBudgetConstructionAppointmentFunding appointmentFunding) { // do the recalc on the passed line and reset the indicator // there are no rule checks involved in this operation if (appointmentFunding.isPositionSalaryChangeIndicator()) { appointmentFunding.setPositionSalaryChangeIndicator(Boolean.FALSE); // check if months needs reset if (appointmentFunding.getAppointmentFundingDurationCode().equals(NONE.durationCode)) { if (!appointmentFunding.getAppointmentFundingMonth().equals(appointmentFunding.getBudgetConstructionPosition().getIuNormalWorkMonths())) { appointmentFunding.setAppointmentFundingMonth(appointmentFunding.getBudgetConstructionPosition().getIuNormalWorkMonths()); } } // recalc request fte and if hourly, normalize request amount and hourly rate salarySettingService.recalculateDerivedInformation(appointmentFunding); } // doing this just to do cleanup of the default object change flag // the rules will force the user to mark the line delete // if the default object doesn't match with the line object when saving if (appointmentFunding.isPositionObjectChangeIndicator()) { appointmentFunding.setPositionObjectChangeIndicator(Boolean.FALSE); } } /** * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#getFundingAwareObjectName() */ @Override protected String getFundingAwareObjectName() { return BCPropertyConstants.BUDGET_CONSTRUCTION_POSITION; } /** * resyn position brefore performing salary setting */ private ActionForward resyncPositionBeforeSalarySetting(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { PositionSalarySettingForm positionSalarySettingForm = (PositionSalarySettingForm) form; if (!positionSalarySettingForm.isRefreshPositionBeforeSalarySetting()) { return null; } Integer universityFiscalYear = positionSalarySettingForm.getUniversityFiscalYear(); String positionNumber = positionSalarySettingForm.getPositionNumber(); String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId(); // attempt to lock position and associated funding BudgetConstructionLockStatus bcLockStatus = SpringContext.getBean(LockService.class).lockPositionAndActiveFunding(universityFiscalYear, positionNumber, principalId); if (!bcLockStatus.getLockStatus().equals(BCConstants.LockStatus.SUCCESS)) { GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, BCKeyConstants.ERROR_POSITION_LOCK_NOT_OBTAINED, new String[] { universityFiscalYear.toString(), positionNumber }); return this.returnToCaller(mapping, form, request, response); } try { budgetConstructionPositionService.refreshPositionFromExternal(universityFiscalYear, positionNumber); } finally { // release locks LockStatus lockStatus = SpringContext.getBean(LockService.class).unlockPositionAndActiveFunding(universityFiscalYear, positionNumber, principalId); if (!lockStatus.equals(BCConstants.LockStatus.SUCCESS)) { LOG.error(String.format("unable to unlock position and active funding records: %s, %s, %s", universityFiscalYear, positionNumber, principalId)); throw new RuntimeException(String.format("unable to unlock position and active funding records: %s, %s, %s", universityFiscalYear, positionNumber, principalId)); } } return null; } /** * @see org.kuali.kfs.module.bc.document.web.struts.DetailSalarySettingAction#getSynchronizationCheckType() */ @Override public SynchronizationCheckType getSynchronizationCheckType() { return SynchronizationCheckType.EID; } }