/* * 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.cam.service.impl; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.kfs.module.cab.CabConstants; import org.kuali.kfs.module.cab.CabPropertyConstants; import org.kuali.kfs.module.cam.CamsConstants; import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName; import org.kuali.kfs.module.cam.CamsKeyConstants; import org.kuali.kfs.module.cam.businessobject.AssetLock; import org.kuali.kfs.module.cam.dataaccess.CapitalAssetLockDao; import org.kuali.kfs.module.cam.service.AssetLockService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.UrlFactory; import org.springframework.transaction.annotation.Transactional; @Transactional public class AssetLockServiceImpl implements AssetLockService { private static Logger LOG = Logger.getLogger(AssetLockService.class); private CapitalAssetLockDao capitalAssetLockDao; // FP document types includes: // CashReceipt,DistributionOfIncomeAndExpense,GeneralErrorCorrection,InternalBilling,ServiceBilling,YearEndDistributionOfIncomeAndExpense,YearEndGeneralErrorCorrection,ProcurementCard private static final Map<String, String> FINANCIAL_DOC_TYPE_MAP = new HashMap<String, String>(); static { FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT, KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT, KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.CREDIT_CARD_RECEIPT, KFSConstants.FinancialDocumentTypeCodes.CREDIT_CARD_RECEIPT); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE, KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.GENERAL_ERROR_CORRECTION, KFSConstants.FinancialDocumentTypeCodes.GENERAL_ERROR_CORRECTION); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.INTERNAL_BILLING, KFSConstants.FinancialDocumentTypeCodes.INTERNAL_BILLING); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.SERVICE_BILLING, KFSConstants.FinancialDocumentTypeCodes.SERVICE_BILLING); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.YEAR_END_DISTRIBUTION_OF_INCOME_AND_EXPENSE, KFSConstants.FinancialDocumentTypeCodes.YEAR_END_DISTRIBUTION_OF_INCOME_AND_EXPENSE); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.YEAR_END_GENERAL_ERROR_CORRECTION, KFSConstants.FinancialDocumentTypeCodes.YEAR_END_GENERAL_ERROR_CORRECTION); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD, KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD); FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.INTRA_ACCOUNT_ADJUSTMENT, KFSConstants.FinancialDocumentTypeCodes.INTRA_ACCOUNT_ADJUSTMENT); } // CAMS document types for maintain asset: AssetMaintenance, AssetFabrication, Asset Global, Asset Location Global, // LoanAndReturn private static final Map<String, String> ASSET_MAINTAIN_DOC_TYPE_MAP = new HashMap<String, String>(); static { ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_EDIT, DocumentTypeName.ASSET_EDIT); ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_LOCATION_GLOBAL, DocumentTypeName.ASSET_LOCATION_GLOBAL); ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_EQUIPMENT_LOAN_OR_RETURN, DocumentTypeName.ASSET_EQUIPMENT_LOAN_OR_RETURN); ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_BARCODE_INVENTORY_ERROR, DocumentTypeName.ASSET_BARCODE_INVENTORY_ERROR); ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_PAYMENT_FROM_CAB, DocumentTypeName.ASSET_PAYMENT_FROM_CAB); } // CAMS document types relating payment changes: AssetRetirement and Merge, AssetTransfer, AssetPayment, Asset Separate private static final Map<String, String> ASSET_PMT_CHG_DOC_TYPE_MAP = new HashMap<String, String>(); static { ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_RETIREMENT_GLOBAL, DocumentTypeName.ASSET_RETIREMENT_GLOBAL); ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_TRANSFER, DocumentTypeName.ASSET_TRANSFER); ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_PAYMENT, DocumentTypeName.ASSET_PAYMENT); ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_SEPARATE, DocumentTypeName.ASSET_SEPARATE); } protected boolean isPurApDocument(String documentTypeName) { return CabConstants.PREQ.equals(documentTypeName) || CabConstants.CM.equals(documentTypeName); } /** * Gets the capitalAssetLockDao attribute. * * @return Returns the capitalAssetLockDao. */ public CapitalAssetLockDao getCapitalAssetLockDao() { return capitalAssetLockDao; } /** * Sets the capitalAssetLockDao attribute value. * * @param capitalAssetLockDao The capitalAssetLockDao to set. */ public void setCapitalAssetLockDao(CapitalAssetLockDao capitalAssetLockDao) { this.capitalAssetLockDao = capitalAssetLockDao; } /** * @param assetLocks * All asset locks must be owned by the same documentNumber and having the same * documentTypeName * @param ignoreLockingInfoForDeletion * Indicate whether or not to ignore asset locking information * when deleting existing asset locks granted to document. This * will be used to update asset locks if locking info updated as * well. * @return Return false without any of the asset being locked. Return true * when all assets can be locked. * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#checkAndLockForDocument(java.util.Collection) */ public synchronized boolean checkAndSetAssetLocks(List<AssetLock> assetLocks, boolean ignoreLockingInfoForDeletion) { if (assetLocks == null || assetLocks.isEmpty() || !assetLocks.iterator().hasNext()) { return true; } AssetLock lock = assetLocks.iterator().next(); String documentTypeName = lock.getDocumentTypeName(); String documentNumber = lock.getDocumentNumber(); // build asset number collection for lock checking. List assetNumbers = new ArrayList<Long>(); for (AssetLock assetLock : assetLocks) { assetNumbers.add(assetLock.getCapitalAssetNumber()); } // check each assetNumber is not locked by other document(s). PurAp document will ignore the locks since CAB batch will // set the lock anyway. if (isAssetLocked(assetNumbers, documentTypeName, documentNumber)) { return false; } for (AssetLock assetLock : assetLocks) { deleteAssetLocks(documentNumber, ignoreLockingInfoForDeletion ? "" : assetLock.getLockingInformation()); } getBusinessObjectService().save(assetLocks); return true; } /** * To get blocking document types for given document type. If given document type is FP, blocking documents will be CAMS payment * change documents. If given document type is CAMs maintain related, the blocking documents are all CAMs doc excluding FP and * PURAP. For other cases, returning null which will block all other documents. * * @param documentTypeName * @return */ protected Collection getBlockingDocumentTypes(String documentTypeName) { // FP document should be blocked by CAMS Payment change documents. if (FINANCIAL_DOC_TYPE_MAP.containsKey(documentTypeName)) { return ASSET_PMT_CHG_DOC_TYPE_MAP.values(); } // CAMS maintain docs else if (ASSET_MAINTAIN_DOC_TYPE_MAP.containsKey(documentTypeName)) { List financialDocTypes = new ArrayList<String>(); financialDocTypes.addAll(ASSET_MAINTAIN_DOC_TYPE_MAP.values()); financialDocTypes.addAll(ASSET_PMT_CHG_DOC_TYPE_MAP.values()); return financialDocTypes; } // FP blocking documents if (CamsConstants.DocumentTypeName.ASSET_FP_INQUIRY.equals(documentTypeName)) { return FINANCIAL_DOC_TYPE_MAP.values(); } // PREQ blocking documents if (CamsConstants.DocumentTypeName.ASSET_PREQ_INQUIRY.equals(documentTypeName)) { List fpAndPurApDocTypes = new ArrayList<String>(); fpAndPurApDocTypes.add(CabConstants.PREQ); fpAndPurApDocTypes.add(CabConstants.CM); return fpAndPurApDocTypes; } // For CAMs payment change document, any doc type can be the blocker, return null for this case return null; } /** * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#deleteLocks(java.lang.String, java.lang.String) */ public void deleteAssetLocks(String documentNumber, String lockingInformation) { if (StringUtils.isBlank(documentNumber)) { return; } Map<String, Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(CabPropertyConstants.CapitalAssetLock.DOCUMENT_NUMBER, documentNumber); if (StringUtils.isNotBlank(lockingInformation)) { fieldValues.put(CabPropertyConstants.CapitalAssetLock.LOCKING_INFORMATION, lockingInformation); } getBusinessObjectService().deleteMatching(AssetLock.class, fieldValues); } /** * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#generateAssetLocks(java.util.Collection, * java.lang.String, java.lang.String, java.lang.String) */ public List<AssetLock> buildAssetLockHelper(List<Long> assetNumbers, String documentNumber, String documentType, String lockingInformation) { List<AssetLock> assetLocks = new ArrayList<AssetLock>(); for (Long assetNumber : assetNumbers) { if (assetNumber != null) { AssetLock newLock = new AssetLock(documentNumber, assetNumber, lockingInformation, documentType); assetLocks.add(newLock); } } return assetLocks; } /** * Generating error messages and doc links for blocking documents. * * @param blockingDocuments */ protected void addBlockingDocumentErrorMessage(Collection<String> blockingDocuments, String documentTypeName) { for (String blockingDocId : blockingDocuments) { // build the link URL for the blocking document. Better to use DocHandler because this could be // a maintenance document or tDoc. Properties parameters = new Properties(); parameters.put(KRADConstants.PARAMETER_DOC_ID, blockingDocId); parameters.put(KRADConstants.PARAMETER_COMMAND, KRADConstants.METHOD_DISPLAY_DOC_SEARCH_VIEW); String blockingUrl = UrlFactory.parameterizeUrl(SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSConstants.WORKFLOW_URL_KEY) + "/" + KRADConstants.DOC_HANDLER_ACTION, parameters); if (LOG.isDebugEnabled()) { LOG.debug("blockingUrl = '" + blockingUrl + "'"); LOG.debug("Record: " + blockingDocId + "is locked."); } // post an error about the locked document String[] errorParameters = { blockingUrl, blockingDocId }; if (FINANCIAL_DOC_TYPE_MAP.containsKey(documentTypeName)) { // display a different error message for lock request from FP document. GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, CamsKeyConstants.AssetLock.ERROR_ASSET_LOCKED, errorParameters); } else { GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, CamsKeyConstants.AssetLock.ERROR_ASSET_MAINTENANCE_LOCKED, errorParameters); } } } protected BusinessObjectService getBusinessObjectService() { return SpringContext.getBean(BusinessObjectService.class); } /** * @see org.kuali.kfs.module.cam.service.AssetLockService#isAssetLockedByDocument(java.lang.String, java.lang.String) */ public boolean isAssetLockedByCurrentDocument(String documentNumber, String lockingInformation) { if (StringUtils.isBlank(documentNumber)) { return false; } Map<String, Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(CabPropertyConstants.CapitalAssetLock.DOCUMENT_NUMBER, documentNumber); if (StringUtils.isNotBlank(lockingInformation)) { fieldValues.put(CabPropertyConstants.CapitalAssetLock.LOCKING_INFORMATION, lockingInformation); } Collection<AssetLock> assetLocks = getBusinessObjectService().findMatching(AssetLock.class, fieldValues); return assetLocks != null && !assetLocks.isEmpty(); } /** * Based on the given documentTypeName, it decides what document types could block it. * * @see org.kuali.kfs.module.cam.service.AssetLockService#isAssetLocked(java.util.List, java.lang.String, java.lang.String) */ public boolean isAssetLocked(List<Long> assetNumbers, String documentTypeName, String excludingDocumentNumber) { if (assetNumbers == null || assetNumbers.isEmpty()) { return false; } if (!isPurApDocument(documentTypeName)) { List<String> lockingDocumentNumbers = getAssetLockingDocuments(assetNumbers, documentTypeName, excludingDocumentNumber); if (lockingDocumentNumbers != null && !lockingDocumentNumbers.isEmpty()) { addBlockingDocumentErrorMessage(lockingDocumentNumbers, documentTypeName); return true; } } return false; } /** * @see org.kuali.kfs.module.cam.service.AssetLockService#getAssetLockingDocuments(java.util.List, java.lang.String, * java.lang.String) */ public List<String> getAssetLockingDocuments(List<Long> assetNumbers, String documentTypeName, String excludingDocumentNumber) { Collection blockingDocumentTypes = getBlockingDocumentTypes(documentTypeName); List<String> lockingDocumentNumbers = getCapitalAssetLockDao().getLockingDocumentNumbers(assetNumbers, blockingDocumentTypes, excludingDocumentNumber); return lockingDocumentNumbers; } }