/*
* 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.Organization;
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.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.BudgetConstructionProcessorService;
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.util.SalarySettingFieldsHolder;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.ObjectUtil;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.identity.KfsKimAttributes;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.MessageMap;
/**
* the base struts form for the detail salary setting: by position or by incumbent
*/
public abstract class DetailSalarySettingForm extends SalarySettingBaseForm {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DetailSalarySettingForm.class);
private PendingBudgetConstructionAppointmentFunding newBCAFLine;
private boolean addLine;
private String positionNumber;
private String emplid;
private String name;
private SalarySettingService salarySettingService = SpringContext.getBean(SalarySettingService.class);
private BudgetDocumentService budgetDocumentService = SpringContext.getBean(BudgetDocumentService.class);
private LockService lockService = SpringContext.getBean(LockService.class);
private BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
private static final List<String> comparableFields = getComparableFields();
/**
* Constructs a DetailSalarySettingForm.java.
*/
public DetailSalarySettingForm() {
super();
this.setEditingMode(new HashMap<String, String>());
this.setNewBCAFLine(new PendingBudgetConstructionAppointmentFunding());
}
/**
* @see org.kuali.rice.kns.web.struts.form.KualiForm#populate(javax.servlet.http.HttpServletRequest)
*/
@Override
public void populate(HttpServletRequest request) {
super.populate(request);
this.setSingleAccountMode(this.resetSingleAccountModeFlag());
this.populateBCAFLines();
}
/**
* @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseForm#populateBCAFLines()
*/
@Override
public void populateBCAFLines() {
super.populateBCAFLines();
this.refreshBCAFLine(this.getNewBCAFLine());
}
/**
* @see org.kuali.kfs.module.bc.document.web.struts.SalarySettingBaseForm#refreshBCAFLine(org.kuali.kfs.module.bc.businessobject.PendingBudgetConstructionAppointmentFunding)
*/
@Override
public void refreshBCAFLine(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
super.refreshBCAFLine(appointmentFunding);
appointmentFunding.refreshReferenceObject(BCPropertyConstants.BUDGET_CONSTRUCTION_INTENDED_INCUMBENT);
appointmentFunding.refreshReferenceObject(BCPropertyConstants.BUDGET_CONSTRUCTION_POSITION);
appointmentFunding.refreshReferenceObject(BCPropertyConstants.BUDGET_CONSTRUCTION_ADMINISTRATIVE_POST);
}
/**
* acquire position and funding locks for all appointment fundings
*/
public boolean acquirePositionAndFundingLocks(MessageMap errorMap) {
List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = this.getAppointmentFundings();
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) {
boolean gotLocks = this.acquirePositionAndFundingLocks(appointmentFunding, errorMap);
if (!gotLocks) {
return false;
}
}
return true;
}
/**
* acquire position and funding locks for the given appointment funding
*
* @param appointmentFunding the given appointment funding
* @return true if the position and funding locks for the given appointment funding are acquired successfully, otherwise, false
*/
public boolean acquirePositionAndFundingLocks(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
LOG.debug("acquirePositionAndFundingLocks() started");
try {
// not to acquire any lock for the display-only and non-budgetable funding line
if (appointmentFunding.isDisplayOnlyMode() || !appointmentFunding.isBudgetable()) {
return true;
}
// acquire position lock for the current funding line
BudgetConstructionPosition position = appointmentFunding.getBudgetConstructionPosition();
String positionNumber = position.getPositionNumber();
Integer universityFiscalYear = position.getUniversityFiscalYear();
String principalId = this.getPerson().getPrincipalId();
Boolean positionWasAlreadyLocked = lockService.isPositionLockedByUser(positionNumber, universityFiscalYear, principalId);
if (!positionWasAlreadyLocked) {
BudgetConstructionLockStatus positionLockingStatus = lockService.lockPosition(position, this.getPerson());
if (!LockStatus.SUCCESS.equals(positionLockingStatus.getLockStatus())) {
errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_FAIL_TO_LOCK_POSITION, "Position Number:"+position.getPositionNumber()+", Fiscal Year:"+position.getUniversityFiscalYear().toString()+", Desc:"+position.getPositionDescription()+", Locked By:"+position.getPositionLockUser().getPrincipalName());
// gwp - added if test, unlock all others only when initially loading the screen
// not during the add line action
if (!appointmentFunding.isNewLineIndicator()) {
this.releasePositionAndFundingLocks();
}
return false;
}
}
// acquire funding lock for the current funding line
BudgetConstructionLockStatus fundingLockingStatus = this.getLockStatusForBudgetByAccountMode(appointmentFunding);
if (fundingLockingStatus == null) {
fundingLockingStatus = lockService.lockFunding(appointmentFunding, this.getPerson());
}
if (!LockStatus.SUCCESS.equals(fundingLockingStatus.getLockStatus())) {
String lockUserName = SpringContext.getBean(PersonService.class).getPerson(fundingLockingStatus.getAccountLockOwner()).getPrincipalName();
errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_FAIL_TO_LOCK_FUNDING, appointmentFunding.getAppointmentFundingString()+", Document Locked By:"+lockUserName);
// gwp - added if test, unlock all others only when initially loading the screen
// not during the add line action
if (!appointmentFunding.isNewLineIndicator()) {
this.releasePositionAndFundingLocks();
}
else {
// adding a new line, just release the earlier position lock
// if we just issued it above, not from other line
if (!positionWasAlreadyLocked) {
lockService.unlockPosition(positionNumber, universityFiscalYear, principalId);
}
}
return false;
}
}
catch (Exception e) {
this.releasePositionAndFundingLocks();
String errorMessage = "Failed when acquiring position/funding lock for " + appointmentFunding;
LOG.error(errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
return true;
}
/**
* update the access modes of all appointment fundings
*/
public boolean updateAccessMode(MessageMap errorMap) {
List<PendingBudgetConstructionAppointmentFunding> appointmentFundings = this.getAppointmentFundings();
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : appointmentFundings) {
boolean accessModeUpdated = this.updateAccessMode(appointmentFunding, errorMap);
if (!accessModeUpdated) {
return false;
}
}
return true;
}
/**
* update the access mode of the given appointment funding
*
* @param appointmentFunding the given appointment funding
* @return true if the access mode of the given appointment funding are updated successfully, otherwise, false
*/
public boolean updateAccessMode(PendingBudgetConstructionAppointmentFunding appointmentFunding, MessageMap errorMap) {
LOG.debug("updateAccessMode() started");
try {
SalarySettingFieldsHolder fieldsHolder = this.getSalarySettingFieldsHolder();
// update the access flags of the current funding line
boolean updated = salarySettingService.updateAccessOfAppointmentFunding(appointmentFunding, fieldsHolder, this.isBudgetByAccountMode(), this.isEditAllowed(), this.getPerson());
if (!updated) {
errorMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_FAIL_TO_UPDATE_FUNDING_ACCESS, appointmentFunding.getAppointmentFundingString());
return false;
}
}
catch (Exception e) {
String errorMessage = "Failed to update the access mode of " + appointmentFunding + ".";
LOG.error(errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
return true;
}
/**
* acquire transaction locks for the savable appointment fundings
*
* @return true if the transaction locks for all savable appointment fundings are acquired successfully, otherwise, false
*/
public boolean acquireTransactionLocks(MessageMap messageMap) {
LOG.debug("acquireTransactionLocks() started");
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : this.getSavableAppointmentFundings()) {
try {
BudgetConstructionLockStatus transactionLockStatus = this.getLockStatusForBudgetByAccountMode(appointmentFunding);
if (transactionLockStatus == null) {
transactionLockStatus = lockService.lockTransaction(appointmentFunding, this.getPerson());
}
if (!LockStatus.SUCCESS.equals(transactionLockStatus.getLockStatus())) {
messageMap.putError(BCPropertyConstants.NEW_BCAF_LINE, BCKeyConstants.ERROR_FAIL_TO_ACQUIRE_TRANSACTION_LOCK, appointmentFunding.getAppointmentFundingString());
this.releaseTransactionLocks();
return false;
}
}
catch (Exception e) {
this.releaseTransactionLocks();
this.releasePositionAndFundingLocks();
LOG.error("Failed when acquiring transaction lock for " + appointmentFunding, e);
throw new RuntimeException("Failed when acquiring transaction lock for " + appointmentFunding, e);
}
}
return true;
}
/**
* release all position and funding locks acquired in current action by the current user
*/
public void releasePositionAndFundingLocks() {
LOG.debug("releasePositionAndFundingLocks() started");
List<PendingBudgetConstructionAppointmentFunding> releasableAppointmentFundings = this.getReleasableAppointmentFundings();
lockService.unlockFunding(releasableAppointmentFundings, this.getPerson());
Set<BudgetConstructionPosition> lockedPositionSet = new HashSet<BudgetConstructionPosition>();
for (PendingBudgetConstructionAppointmentFunding fundingLine : releasableAppointmentFundings) {
lockedPositionSet.add(fundingLine.getBudgetConstructionPosition());
LOG.info("fundingLine: " + fundingLine);
}
LOG.info("releasePositionAndFundingLocks()" + lockedPositionSet);
List<BudgetConstructionPosition> lockedPositions = new ArrayList<BudgetConstructionPosition>();
lockedPositions.addAll(lockedPositionSet);
lockService.unlockPostion(lockedPositions, this.getPerson());
for (BudgetConstructionPosition position : lockedPositionSet) {
LOG.info("fundingLine: " + position.getPositionLockUserIdentifier());
}
}
/**
* release all the transaction locks acquired in current action by the current user
*/
public void releaseTransactionLocks() {
LOG.debug("releaseTransactionLocks() started");
List<PendingBudgetConstructionAppointmentFunding> fundingsWithTransactionLocks = this.getReleasableAppointmentFundings();
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : fundingsWithTransactionLocks) {
lockService.unlockTransaction(appointmentFunding, this.getPerson());
}
}
/**
* get the appointment fundings that can be saved
*/
public List<PendingBudgetConstructionAppointmentFunding> getSavableAppointmentFundings() {
LOG.debug("getSavableAppointmentFundings() started");
// get the funding lines that can be saved
List<PendingBudgetConstructionAppointmentFunding> savableAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
for (PendingBudgetConstructionAppointmentFunding fundingLine : this.getAppointmentFundings()) {
// save-able line is one that is edit-able
// rules should catch attempts to save active, !purged, !isBudgetable lines
if (!fundingLine.isDisplayOnlyMode()) {
savableAppointmentFundings.add(fundingLine);
}
}
return savableAppointmentFundings;
}
/**
* get the appointment fundings for which the position or funding locks can be released
*/
public List<PendingBudgetConstructionAppointmentFunding> getReleasableAppointmentFundings() {
LOG.debug("getReleasableAppointmentFundings() started");
List<PendingBudgetConstructionAppointmentFunding> savableAppointmentFundings = this.getSavableAppointmentFundings();
List<PendingBudgetConstructionAppointmentFunding> releasableAppointmentFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
releasableAppointmentFundings.addAll(savableAppointmentFundings);
return releasableAppointmentFundings;
}
/**
* determine whether there is any active funding line in the given savable appointment funding lines
*/
public List<PendingBudgetConstructionAppointmentFunding> getActiveFundingLines() {
LOG.debug("getActiveFundingLines() started");
List<PendingBudgetConstructionAppointmentFunding> activeFundingLines = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : this.getSavableAppointmentFundings()) {
if (!appointmentFunding.isAppointmentFundingDeleteIndicator()) {
activeFundingLines.add(appointmentFunding);
}
}
return activeFundingLines;
}
/**
* sets the default fields not setable by the user for added lines and any other required initialization
*
* @param appointmentFunding the given appointment funding line
*/
public PendingBudgetConstructionAppointmentFunding createNewAppointmentFundingLine() {
PendingBudgetConstructionAppointmentFunding appointmentFunding = new PendingBudgetConstructionAppointmentFunding();
if (this.isAddLine() || this.isSingleAccountMode()) {
appointmentFunding.setChartOfAccountsCode(this.getChartOfAccountsCode());
appointmentFunding.setAccountNumber(this.getAccountNumber());
// empty field implies dashes and is fixed in add action
if (this.getSubAccountNumber().equals(KFSConstants.getDashSubAccountNumber())) {
appointmentFunding.setSubAccountNumber(KFSConstants.EMPTY_STRING);
}
else {
appointmentFunding.setSubAccountNumber(this.getSubAccountNumber());
}
appointmentFunding.setFinancialObjectCode(this.getFinancialObjectCode());
// empty field implies dashes and is fixed in add action
if (this.getFinancialSubObjectCode().equals(KFSConstants.getDashFinancialSubObjectCode())) {
appointmentFunding.setFinancialSubObjectCode(KFSConstants.EMPTY_STRING);
}
else {
appointmentFunding.setFinancialSubObjectCode(this.getFinancialSubObjectCode());
}
}
appointmentFunding.setUniversityFiscalYear(this.getUniversityFiscalYear());
appointmentFunding.setAppointmentFundingDeleteIndicator(false);
appointmentFunding.setNewLineIndicator(true);
appointmentFunding.setAppointmentFundingDurationCode(BCConstants.AppointmentFundingDurationCodes.NONE.durationCode);
return appointmentFunding;
}
/**
* pick up the appointment fundings belonging to the specified account from a collection of fundings that are associated with a
* position/incumbent
*/
public void pickAppointmentFundingsForSingleAccount() {
LOG.debug("pickAppointmentFundingsForSingleAccount() started");
List<PendingBudgetConstructionAppointmentFunding> excludedFundings = new ArrayList<PendingBudgetConstructionAppointmentFunding>();
for (PendingBudgetConstructionAppointmentFunding appointmentFunding : this.getAppointmentFundings()) {
if (!ObjectUtil.equals(appointmentFunding, this, comparableFields)) {
excludedFundings.add(appointmentFunding);
}
}
this.getAppointmentFundings().removeAll(excludedFundings);
}
/**
* Funding/transaction locks are not required for the lines associated with a document already open in budget by account mode
*/
private BudgetConstructionLockStatus getLockStatusForBudgetByAccountMode(PendingBudgetConstructionAppointmentFunding appointmentFunding) {
LOG.debug("getLockStatusForBudgetByAccountMode() started");
BudgetConstructionLockStatus transactionLockStatus = null;
if (this.isBudgetByAccountMode() && ObjectUtil.equals(appointmentFunding, this, comparableFields)) {
transactionLockStatus = new BudgetConstructionLockStatus();
transactionLockStatus.setLockStatus(LockStatus.SUCCESS);
}
return transactionLockStatus;
}
/**
* get the names of comparable fields that are considered to determine a single account, that is, the fundings are considered
* being assocated with the given account if they have the same values of the fields as specified.
*/
public static List<String> getComparableFields() {
List<String> comparableFields = new ArrayList<String>();
comparableFields.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
comparableFields.add(KFSPropertyConstants.ACCOUNT_NUMBER);
comparableFields.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER);
return comparableFields;
}
/**
* determine whether the editing mode for detail salary setting is in single account mode or not
*/
private boolean resetSingleAccountModeFlag() {
List<Organization> processorOrgs = SpringContext.getBean(BudgetConstructionProcessorService.class).getProcessorOrgs(this.getPerson());
Boolean isOrgApprover = processorOrgs != null && !processorOrgs.isEmpty();
if (this.isBudgetByAccountMode()) {
Account account = new Account();
account.setAccountNumber(this.getAccountNumber());
account.setChartOfAccountsCode(this.getChartOfAccountsCode());
account = (Account) businessObjectService.retrieve(account);
RoleService roleService = KimApiServiceLocator.getRoleService();
Map<String,String> qualification = new HashMap<String,String>();
qualification.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, getChartOfAccountsCode());
qualification.put(KfsKimAttributes.ACCOUNT_NUMBER, getAccountNumber());
qualification.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, BCConstants.BUDGET_CONSTRUCTION_DOCUMENT_NAME);
List<String> roleId = new ArrayList<String>();
roleId.add(roleService.getRoleIdByNamespaceCodeAndName(KFSConstants.ParameterNamespaces.KFS, KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME));
Boolean isFiscalOfficerOrDelegate = roleService.principalHasRole(getPerson().getPrincipalId(), roleId, qualification);
return (isFiscalOfficerOrDelegate && !isOrgApprover);
}
// instruct the detail salary setting by multiple account mode if current user is an organization level approver
if (isOrgApprover) {
return false;
}
else {
throw new RuntimeException("Access denied: not authorized to do the detail salary setting");
}
}
/**
* Gets the newBCAFLine attribute.
*
* @return Returns the newBCAFLine.
*/
public PendingBudgetConstructionAppointmentFunding getNewBCAFLine() {
return newBCAFLine;
}
/**
* Sets the newBCAFLine attribute value.
*
* @param newBCAFLine The newBCAFLine to set.
*/
public void setNewBCAFLine(PendingBudgetConstructionAppointmentFunding newBCAFLine) {
this.newBCAFLine = newBCAFLine;
}
/**
* Gets the emplid attribute.
*
* @return Returns the emplid.
*/
public String getEmplid() {
return emplid;
}
/**
* Sets the emplid attribute value.
*
* @param emplid The emplid to set.
*/
public void setEmplid(String emplid) {
this.emplid = emplid;
}
/**
* Gets the addLine attribute.
*
* @return Returns the addLine.
*/
public boolean isAddLine() {
return addLine;
}
/**
* Sets the addLine attribute value.
*
* @param addLine The addLine to set.
*/
public void setAddLine(boolean addLine) {
this.addLine = addLine;
}
/**
* Gets the positionNumber attribute.
*
* @return Returns the positionNumber.
*/
public String getPositionNumber() {
return positionNumber;
}
/**
* Sets the positionNumber attribute value.
*
* @param positionNumber The positionNumber to set.
*/
public void setPositionNumber(String positionNumber) {
this.positionNumber = positionNumber;
}
/**
* Gets the name attribute.
*
* @return Returns the name.
*/
public String name() {
return name;
}
/**
* Sets the name attribute value.
*
* @param name The name to set.
*/
public void setName(String name) {
this.name = name;
}
/**
* @see org.kuali.rice.kns.web.struts.form.KualiForm#shouldPropertyBePopulatedInForm(java.lang.String,
* javax.servlet.http.HttpServletRequest)
*/
@Override
public boolean shouldPropertyBePopulatedInForm(String requestParameterName, HttpServletRequest request) {
if (super.shouldPropertyBePopulatedInForm(requestParameterName, request)) {
return true;
}
else {
// make sure special disabled fields are allowed to be populated
if (requestParameterName.endsWith(BCPropertyConstants.APPOINTMENT_REQUESTED_CSF_AMOUNT) || requestParameterName.endsWith(BCPropertyConstants.APPOINTMENT_REQUESTED_CSF_TIME_PERCENT) || requestParameterName.endsWith(BCPropertyConstants.APPOINTMENT_FUNDING_REASON_AMOUNT)
|| requestParameterName.endsWith(BCPropertyConstants.APPOINTMENT_CHART_OF_ACCOUNT)) {
return true;
}
else {
return false;
}
}
}
}