/* * 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.LWPA; import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.LWPF; import static org.kuali.kfs.module.bc.BCConstants.AppointmentFundingDurationCodes.NONE; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; 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.SynchronizationCheckType; import org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding; import org.kuali.kfs.module.bc.document.BudgetConstructionDocument; import org.kuali.kfs.module.bc.document.service.BudgetDocumentService; import org.kuali.kfs.module.bc.document.service.LockService; import org.kuali.kfs.module.bc.document.service.SalarySettingService; import org.kuali.kfs.module.bc.document.validation.event.AddAppointmentFundingEvent; import org.kuali.kfs.module.bc.document.validation.event.BudgetExpansionEvent; import org.kuali.kfs.module.bc.document.validation.event.SaveSalarySettingEvent; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.ObjectUtil; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.core.api.util.type.KualiInteger; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kns.question.ConfirmationQuestion; import org.kuali.rice.kns.service.BusinessObjectDictionaryService; import org.kuali.rice.kns.util.KNSGlobalVariables; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.MessageMap; /** * the base struts action for the detail salary setting */ public abstract class DetailSalarySettingAction extends SalarySettingBaseAction { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DetailSalarySettingAction.class); private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class); private BudgetDocumentService budgetDocumentService = SpringContext.getBean(BudgetDocumentService.class); private Person currentUser = GlobalVariables.getUserSession().getPerson(); /** * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#execute(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionForward executeAction = null; try { executeAction = super.execute(mapping, form, request, response); } catch (Exception e) { // release all locks when encountering runtime exception DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form; if (!salarySettingForm.isViewOnlyEntry()) { salarySettingForm.releaseTransactionLocks(); salarySettingForm.releasePositionAndFundingLocks(); } LOG.fatal("Unexpected errors occurred.", e); // re-throw the exception throw new ServletException(e); } return executeAction; } /** * @see org.kuali.kfs.module.bc.document.web.struts.BudgetExpansionAction#close(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form; String buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON); boolean isClose = StringUtils.equals(ConfirmationQuestion.YES, buttonClicked) || StringUtils.equals(ConfirmationQuestion.NO, buttonClicked); ActionForward closeActionForward; if (salarySettingForm.isViewOnlyEntry() || salarySettingForm.isSalarySettingClosed()) { closeActionForward = this.returnAfterClose(salarySettingForm, mapping, request, response); } else { closeActionForward = super.close(mapping, salarySettingForm, request, response); } // release all locks before closing the current expansion screen if (isClose && !salarySettingForm.isViewOnlyEntry() && salarySettingForm.isSalarySettingClosed()) { salarySettingForm.releasePositionAndFundingLocks(); if (form instanceof PositionSalarySettingForm) { // handle case where there are no funding lines attached to position this.unlockPositionOnly((PositionSalarySettingForm) form); } } return closeActionForward; } /** * @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { super.refresh(mapping, form, request, response); DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form; String refreshCaller = request.getParameter(KFSConstants.REFRESH_CALLER); if (refreshCaller != null && refreshCaller.endsWith(KFSConstants.LOOKUPABLE_SUFFIX)) { // user could return from a lookup using a different fiscal year - do not allow it to change the funding line if (!salarySettingForm.getNewBCAFLine().getUniversityFiscalYear().equals(salarySettingForm.getUniversityFiscalYear())){ salarySettingForm.getNewBCAFLine().setUniversityFiscalYear(salarySettingForm.getUniversityFiscalYear()); } if (salarySettingForm instanceof PositionSalarySettingForm){ // check that the object code is consistent with the position default object PositionSalarySettingForm ssPosForm = (PositionSalarySettingForm) salarySettingForm; if (!ssPosForm.getNewBCAFLine().getFinancialObjectCode().equals(ssPosForm.getBudgetConstructionPosition().getIuDefaultObjectCode())){ ssPosForm.getNewBCAFLine().setFinancialObjectCode(ssPosForm.getBudgetConstructionPosition().getIuDefaultObjectCode()); } } salarySettingForm.refreshBCAFLine(salarySettingForm.getNewBCAFLine()); } return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseAction#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 { DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form; List<PendingBudgetConstructionAppointmentFunding> savableAppointmentFundings = salarySettingForm.getSavableAppointmentFundings(); List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings(); if (savableAppointmentFundings == null || savableAppointmentFundings.isEmpty()) { KNSGlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_SALARY_SETTING_SAVED); return mapping.findForward(KFSConstants.MAPPING_BASIC); } MessageMap errorMap = GlobalVariables.getMessageMap(); for (PendingBudgetConstructionAppointmentFunding savableFunding : savableAppointmentFundings) { String errorKeyPrefix = this.getErrorKeyPrefixOfAppointmentFundingLine(appointmentFundings, savableFunding); // retrieve corresponding document in advance in order to use the rule framework BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(savableFunding); if (document == null) { errorMap.putError(errorKeyPrefix, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, savableFunding.getAppointmentFundingString()); return mapping.findForward(KFSConstants.MAPPING_BASIC); } // bypass validation rules if the funding line has been marked as purged or deleted if (savableFunding.isPurged() || savableFunding.isAppointmentFundingDeleteIndicator()) { continue; } salarySettingService.recalculateDerivedInformation(savableFunding); // validate the savable appointment funding lines boolean isValid = this.invokeRules(new SaveSalarySettingEvent(KFSConstants.EMPTY_STRING, errorKeyPrefix, document, savableFunding, this.getSynchronizationCheckType())); if (!isValid) { return mapping.findForward(KFSConstants.MAPPING_BASIC); } } // acquire transaction locks for all funding lines boolean transactionLocked = salarySettingForm.acquireTransactionLocks(GlobalVariables.getMessageMap()); if (!transactionLocked) { return mapping.findForward(KFSConstants.MAPPING_BASIC); } // test if Form is IncumbentSS and call saveSalarySetting() with a isSalarySettingByIncumbent true // so it knows when to remove purged funding position locks when it is the last line for the position if (form instanceof IncumbentSalarySettingForm) { salarySettingService.saveSalarySetting(savableAppointmentFundings, Boolean.TRUE); } else { salarySettingService.saveSalarySetting(savableAppointmentFundings, Boolean.FALSE); } // release all transaction locks salarySettingForm.releaseTransactionLocks(); this.clearPurgedAppointmentFundings(appointmentFundings); KNSGlobalVariables.getMessageList().add(BCKeyConstants.MESSAGE_SALARY_SETTING_SAVED); return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * adds an appointment funding line to the set of existing funding lines */ public ActionForward addAppointmentFundingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { DetailSalarySettingForm salarySettingForm = (DetailSalarySettingForm) form; List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = salarySettingForm.getAppointmentFundings(); PendingBudgetConstructionAppointmentFunding newAppointmentFunding = salarySettingForm.getNewBCAFLine(); SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(newAppointmentFunding); salarySettingForm.refreshBCAFLine(newAppointmentFunding); // setup a working appointment funding so that the default values can be applied PendingBudgetConstructionAppointmentFunding workingAppointmentFunding = new PendingBudgetConstructionAppointmentFunding(); ObjectUtil.buildObject(workingAppointmentFunding, newAppointmentFunding); this.applyDefaultValuesIfEmpty(workingAppointmentFunding); MessageMap errorMap = GlobalVariables.getMessageMap(); // retrieve corresponding document in advance in order to use the rule framework BudgetConstructionDocument document = budgetDocumentService.getBudgetConstructionDocument(workingAppointmentFunding); if (document == null) { errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_BUDGET_DOCUMENT_NOT_FOUND, workingAppointmentFunding.getAppointmentFundingString()); return mapping.findForward(KFSConstants.MAPPING_BASIC); } // check special case where emplid is vacant and force funding duration to none String emplid = workingAppointmentFunding.getEmplid(); if (StringUtils.isNotEmpty(emplid) && StringUtils.equals(emplid, BCConstants.VACANT_EMPLID)) { workingAppointmentFunding.setAppointmentFundingDurationCode(BCConstants.AppointmentFundingDurationCodes.NONE.durationCode); } salarySettingService.recalculateDerivedInformation(workingAppointmentFunding); // validate the new appointment funding line BudgetExpansionEvent addAppointmentFundingEvent = new AddAppointmentFundingEvent(KFSConstants.EMPTY_STRING, BCPropertyConstants.NEW_BCAF_LINE, document, appointmentFundings, workingAppointmentFunding, this.getSynchronizationCheckType()); boolean isValid = this.invokeRules(addAppointmentFundingEvent); if (!isValid) { return mapping.findForward(KFSConstants.MAPPING_BASIC); } // set remaining flags boolean vacatable = salarySettingService.canBeVacant(appointmentFundings, workingAppointmentFunding); workingAppointmentFunding.setVacatable(vacatable); Integer fiscalYear = workingAppointmentFunding.getUniversityFiscalYear(); String chartCode = workingAppointmentFunding.getChartOfAccountsCode(); String objectCode = workingAppointmentFunding.getFinancialObjectCode(); boolean hourlyPaid = salarySettingService.isHourlyPaidObject(fiscalYear, chartCode, objectCode); workingAppointmentFunding.setHourlyPaid(hourlyPaid); // update the access flags of the current funding line boolean accessModeUpdated = salarySettingForm.updateAccessMode(workingAppointmentFunding, errorMap); if (!accessModeUpdated) { return mapping.findForward(KFSConstants.MAPPING_BASIC); } // have no permission to do salary setting on the new line if (workingAppointmentFunding.isDisplayOnlyMode()) { errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_NO_SALARY_SETTING_PERMISSION, workingAppointmentFunding.getAppointmentFundingString()); return mapping.findForward(KFSConstants.MAPPING_BASIC); } // acquire a lock for the new appointment funding line boolean gotLocks = salarySettingForm.acquirePositionAndFundingLocks(workingAppointmentFunding, errorMap); if (!gotLocks) { return mapping.findForward(KFSConstants.MAPPING_BASIC); } appointmentFundings.add(workingAppointmentFunding); salarySettingForm.setNewBCAFLine(salarySettingForm.createNewAppointmentFundingLine()); return mapping.findForward(KFSConstants.MAPPING_BASIC); } // determine whether any active funding line is involved leave protected boolean hasFundingLineInvolvedLeave(List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings) { for (PendingBudgetConstructionAppointmentFunding appointmentFunding : activeAppointmentFundings) { String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode(); if (!StringUtils.equals(leaveDurationCode, NONE.durationCode)) { return true; } } return false; } // determine whether any active funding line is involved in leave without pay protected boolean hasFundingLineInvolvedLeaveWithoutPay(List<PendingBudgetConstructionAppointmentFunding> activeAppointmentFundings) { for (PendingBudgetConstructionAppointmentFunding appointmentFunding : activeAppointmentFundings) { String leaveDurationCode = appointmentFunding.getAppointmentFundingDurationCode(); if (StringUtils.equals(leaveDurationCode, LWPA.durationCode) || StringUtils.equals(leaveDurationCode, LWPF.durationCode)) { return true; } } return false; } // apply the default values to the certain fields when the fields are empty protected void applyDefaultValuesIfEmpty(PendingBudgetConstructionAppointmentFunding appointmentFunding) { if (StringUtils.isBlank(appointmentFunding.getSubAccountNumber())) { appointmentFunding.setSubAccountNumber(KFSConstants.getDashSubAccountNumber()); } if (StringUtils.isBlank(appointmentFunding.getFinancialSubObjectCode())) { appointmentFunding.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode()); } if (appointmentFunding.getAppointmentTotalIntendedAmount() == null) { appointmentFunding.setAppointmentTotalIntendedAmount(KualiInteger.ZERO); appointmentFunding.setAppointmentTotalIntendedFteQuantity(BigDecimal.ZERO); } if (appointmentFunding.getAppointmentTotalIntendedFteQuantity() == null) { appointmentFunding.setAppointmentTotalIntendedFteQuantity(BigDecimal.ZERO); } } // clear the appointment funding lines that have been purged protected void clearPurgedAppointmentFundings(List<PendingBudgetConstructionAppointmentFunding> appointmentFundings) { List<PendingBudgetConstructionAppointmentFunding> purgedAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>(); for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) { if (appointmentFunding.isPurged()) { purgedAppointmentFundings.add(appointmentFunding); } } appointmentFundings.removeAll(purgedAppointmentFundings); } /** * unlock the position only, called as last action before a close or exit handling the case where there are no funding lines * attached yet. */ protected void unlockPositionOnly(PositionSalarySettingForm positionSalarySettingForm) { Integer universityFiscalYear = positionSalarySettingForm.getBudgetConstructionPosition().getUniversityFiscalYear(); String positionNumber = positionSalarySettingForm.getBudgetConstructionPosition().getPositionNumber(); String principalId = GlobalVariables.getUserSession().getPerson().getPrincipalId(); // unlock position SpringContext.getBean(LockService.class).unlockPosition(positionNumber, universityFiscalYear, principalId); } /** * This should return the SynchronizationCheckType based on the context. SynchronizationCheckType.POSN is used in * IncumbentSalarySetting and SynchronizationCheckType.EID is used in PositionSalarySetting * * @return */ public abstract SynchronizationCheckType getSynchronizationCheckType(); }