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.cam.service.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.Properties;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.log4j.Logger;
027    import org.kuali.kfs.module.cab.CabConstants;
028    import org.kuali.kfs.module.cab.CabPropertyConstants;
029    import org.kuali.kfs.module.cam.CamsConstants;
030    import org.kuali.kfs.module.cam.CamsKeyConstants;
031    import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName;
032    import org.kuali.kfs.module.cam.businessobject.AssetLock;
033    import org.kuali.kfs.module.cam.dataaccess.CapitalAssetLockDao;
034    import org.kuali.kfs.module.cam.service.AssetLockService;
035    import org.kuali.kfs.sys.KFSConstants;
036    import org.kuali.kfs.sys.context.SpringContext;
037    import org.kuali.rice.kns.service.BusinessObjectService;
038    import org.kuali.rice.kns.service.KualiConfigurationService;
039    import org.kuali.rice.kns.util.GlobalVariables;
040    import org.kuali.rice.kns.util.KNSConstants;
041    import org.kuali.rice.kns.util.RiceKeyConstants;
042    import org.kuali.rice.kns.util.UrlFactory;
043    import org.springframework.transaction.annotation.Transactional;
044    
045    @Transactional
046    public class AssetLockServiceImpl implements AssetLockService {
047        private static Logger LOG = Logger.getLogger(AssetLockService.class);
048    
049        private CapitalAssetLockDao capitalAssetLockDao;
050    
051        // FP document types includes:
052        // CashReceipt,DistributionOfIncomeAndExpense,GeneralErrorCorrection,InternalBilling,ServiceBilling,YearEndDistributionOfIncomeAndExpense,YearEndGeneralErrorCorrection,ProcurementCard
053        private static final Map<String, String> FINANCIAL_DOC_TYPE_MAP = new HashMap<String, String>();
054        static {
055            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT, KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT);
056            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT, KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT);
057            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.CREDIT_CARD_RECEIPT, KFSConstants.FinancialDocumentTypeCodes.CREDIT_CARD_RECEIPT);
058            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE, KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE);
059            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.GENERAL_ERROR_CORRECTION, KFSConstants.FinancialDocumentTypeCodes.GENERAL_ERROR_CORRECTION);
060            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.INTERNAL_BILLING, KFSConstants.FinancialDocumentTypeCodes.INTERNAL_BILLING);
061            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.SERVICE_BILLING, KFSConstants.FinancialDocumentTypeCodes.SERVICE_BILLING);
062            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.YEAR_END_DISTRIBUTION_OF_INCOME_AND_EXPENSE, KFSConstants.FinancialDocumentTypeCodes.YEAR_END_DISTRIBUTION_OF_INCOME_AND_EXPENSE);
063            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.YEAR_END_GENERAL_ERROR_CORRECTION, KFSConstants.FinancialDocumentTypeCodes.YEAR_END_GENERAL_ERROR_CORRECTION);
064            FINANCIAL_DOC_TYPE_MAP.put(KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD, KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD);
065        }
066    
067        // CAMS document types for maintain asset: AssetMaintenance, AssetFabrication, Asset Global, Asset Location Global,
068        // LoanAndReturn
069        private static final Map<String, String> ASSET_MAINTAIN_DOC_TYPE_MAP = new HashMap<String, String>();
070        static {
071            ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_EDIT, DocumentTypeName.ASSET_EDIT);
072            ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_LOCATION_GLOBAL, DocumentTypeName.ASSET_LOCATION_GLOBAL);
073            ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_EQUIPMENT_LOAN_OR_RETURN, DocumentTypeName.ASSET_EQUIPMENT_LOAN_OR_RETURN);
074            ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_BARCODE_INVENTORY_ERROR, DocumentTypeName.ASSET_BARCODE_INVENTORY_ERROR);
075            ASSET_MAINTAIN_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_PAYMENT_FROM_CAB, DocumentTypeName.ASSET_PAYMENT_FROM_CAB);
076        }
077    
078        // CAMS document types relating payment changes: AssetRetirement and Merge, AssetTransfer, AssetPayment, Asset Separate
079        private static final Map<String, String> ASSET_PMT_CHG_DOC_TYPE_MAP = new HashMap<String, String>();
080        static {
081            ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_RETIREMENT_GLOBAL, DocumentTypeName.ASSET_RETIREMENT_GLOBAL);
082            ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_TRANSFER, DocumentTypeName.ASSET_TRANSFER);
083            ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_PAYMENT, DocumentTypeName.ASSET_PAYMENT);
084            ASSET_PMT_CHG_DOC_TYPE_MAP.put(DocumentTypeName.ASSET_SEPARATE, DocumentTypeName.ASSET_SEPARATE);
085        }
086    
087        protected boolean isPurApDocument(String documentTypeName) {
088            return CabConstants.PREQ.equals(documentTypeName) || CabConstants.CM.equals(documentTypeName);
089        }
090    
091        /**
092         * Gets the capitalAssetLockDao attribute.
093         * 
094         * @return Returns the capitalAssetLockDao.
095         */
096        public CapitalAssetLockDao getCapitalAssetLockDao() {
097            return capitalAssetLockDao;
098        }
099    
100    
101        /**
102         * Sets the capitalAssetLockDao attribute value.
103         * 
104         * @param capitalAssetLockDao The capitalAssetLockDao to set.
105         */
106        public void setCapitalAssetLockDao(CapitalAssetLockDao capitalAssetLockDao) {
107            this.capitalAssetLockDao = capitalAssetLockDao;
108        }
109    
110    
111        /**
112         * @param assetLocks must be from the same documentNumber and have the same documentTypeName
113         * @return Return false without any of the asset being locked. Return true when all assets can be locked.
114         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#checkAndLockForDocument(java.util.Collection)
115         */
116        public synchronized boolean checkAndSetAssetLocks(List<AssetLock> assetLocks) {
117            if (assetLocks == null || assetLocks.isEmpty() || !assetLocks.iterator().hasNext()) {
118                return true;
119            }
120    
121            AssetLock lock = assetLocks.iterator().next();
122            String documentTypeName = lock.getDocumentTypeName();
123            String documentNumber = lock.getDocumentNumber();
124    
125            // build asset number collection for lock checking.
126            List assetNumbers = new ArrayList<Long>();
127            for (AssetLock assetLock : assetLocks) {
128                assetNumbers.add(assetLock.getCapitalAssetNumber());
129            }
130    
131            // check each assetNumber is not locked by other document(s). PurAp document will ignore the locks since CAB batch will
132            // set the lock anyway.
133            if (isAssetLocked(assetNumbers, documentTypeName, documentNumber)) {
134                return false;
135            }
136            // locking. here we got to delete and save again...it looks clumsy but it is constrained by the framework, see
137            // MaintenanceDocumentBase.postProcessSave() for example.
138            deleteAssetLocks(documentNumber, lock.getLockingInformation());
139            getBusinessObjectService().save(assetLocks);
140    
141            return true;
142        }
143    
144        /**
145         * To get blocking document types for given document type. If given document type is FP, blocking documents will be CAMS payment
146         * change documents. If given document type is CAMs maintain related, the blocking documents are all CAMs doc excluding FP and
147         * PURAP. For other cases, returning null which will block all other documents.
148         * 
149         * @param documentTypeName
150         * @return
151         */
152        protected Collection getBlockingDocumentTypes(String documentTypeName) {
153            // FP document should be blocked by CAMS Payment change documents.
154            if (FINANCIAL_DOC_TYPE_MAP.containsKey(documentTypeName)) {
155                return ASSET_PMT_CHG_DOC_TYPE_MAP.values();
156            }
157            // CAMS maintain docs
158            else if (ASSET_MAINTAIN_DOC_TYPE_MAP.containsKey(documentTypeName)) {
159                List financialDocTypes = new ArrayList<String>();
160                financialDocTypes.addAll(ASSET_MAINTAIN_DOC_TYPE_MAP.values());
161                financialDocTypes.addAll(ASSET_PMT_CHG_DOC_TYPE_MAP.values());
162                return financialDocTypes;
163            }
164            // FP blocking documents
165            if (CamsConstants.DocumentTypeName.ASSET_FP_INQUIRY.equals(documentTypeName)) {
166                return FINANCIAL_DOC_TYPE_MAP.values();
167            }
168    
169            // PREQ blocking documents
170            if (CamsConstants.DocumentTypeName.ASSET_PREQ_INQUIRY.equals(documentTypeName)) {
171                List fpAndPurApDocTypes = new ArrayList<String>();
172                fpAndPurApDocTypes.add(CabConstants.PREQ);
173                fpAndPurApDocTypes.add(CabConstants.CM);
174                return fpAndPurApDocTypes;
175            }
176    
177            // For CAMs payment change document, any doc type can be the blocker, return null for this case
178            return null;
179        }
180    
181        /**
182         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#deleteLocks(java.lang.String, java.lang.String)
183         */
184        public void deleteAssetLocks(String documentNumber, String lockingInformation) {
185            if (StringUtils.isBlank(documentNumber)) {
186                return;
187            }
188            Map<String, Object> fieldValues = new HashMap<String, Object>();
189            fieldValues.put(CabPropertyConstants.CapitalAssetLock.DOCUMENT_NUMBER, documentNumber);
190            if (StringUtils.isNotBlank(lockingInformation)) {
191                fieldValues.put(CabPropertyConstants.CapitalAssetLock.LOCKING_INFORMATION, lockingInformation);
192            }
193            getBusinessObjectService().deleteMatching(AssetLock.class, fieldValues);
194        }
195    
196        /**
197         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#generateAssetLocks(java.util.Collection,
198         *      java.lang.String, java.lang.String, java.lang.String)
199         */
200        public List<AssetLock> buildAssetLockHelper(List<Long> assetNumbers, String documentNumber, String documentType, String lockingInformation) {
201            List<AssetLock> assetLocks = new ArrayList<AssetLock>();
202    
203            for (Long assetNumber : assetNumbers) {
204                if (assetNumber != null) {
205                    AssetLock newLock = new AssetLock(documentNumber, assetNumber, lockingInformation, documentType);
206                    assetLocks.add(newLock);
207                }
208            }
209            return assetLocks;
210        }
211    
212    
213        /**
214         * Generating error messages and doc links for blocking documents.
215         * 
216         * @param blockingDocuments
217         */
218        protected void addBlockingDocumentErrorMessage(Collection<String> blockingDocuments, String documentTypeName) {
219            for (String blockingDocId : blockingDocuments) {
220                // build the link URL for the blocking document. Better to use DocHandler because this could be
221                // a maintenance document or tDoc.
222                Properties parameters = new Properties();
223                parameters.put(KNSConstants.PARAMETER_DOC_ID, blockingDocId);
224                parameters.put(KNSConstants.PARAMETER_COMMAND, KNSConstants.METHOD_DISPLAY_DOC_SEARCH_VIEW);
225    
226                String blockingUrl = UrlFactory.parameterizeUrl(SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSConstants.WORKFLOW_URL_KEY) + "/" + KNSConstants.DOC_HANDLER_ACTION, parameters);
227                if (LOG.isDebugEnabled()) {
228                    LOG.debug("blockingUrl = '" + blockingUrl + "'");
229                    LOG.debug("Record: " + blockingDocId + "is locked.");
230                }
231    
232                // post an error about the locked document
233                String[] errorParameters = { blockingUrl, blockingDocId };
234                if (FINANCIAL_DOC_TYPE_MAP.containsKey(documentTypeName)) {
235                    // display a different error message for lock request from FP document.
236                    GlobalVariables.getMessageMap().putError(KNSConstants.GLOBAL_ERRORS, CamsKeyConstants.AssetLock.ERROR_ASSET_LOCKED, errorParameters);
237                }
238                else {
239                    GlobalVariables.getMessageMap().putError(KNSConstants.GLOBAL_ERRORS, CamsKeyConstants.AssetLock.ERROR_ASSET_MAINTENANCE_LOCKED, errorParameters);
240                }
241            }
242        }
243    
244        protected BusinessObjectService getBusinessObjectService() {
245            return SpringContext.getBean(BusinessObjectService.class);
246        }
247    
248        /**
249         * @see org.kuali.kfs.module.cam.service.AssetLockService#isAssetLockedByDocument(java.lang.String, java.lang.String)
250         */
251        public boolean isAssetLockedByCurrentDocument(String documentNumber, String lockingInformation) {
252            if (StringUtils.isBlank(documentNumber)) {
253                return false;
254            }
255            Map<String, Object> fieldValues = new HashMap<String, Object>();
256            fieldValues.put(CabPropertyConstants.CapitalAssetLock.DOCUMENT_NUMBER, documentNumber);
257            if (StringUtils.isNotBlank(lockingInformation)) {
258                fieldValues.put(CabPropertyConstants.CapitalAssetLock.LOCKING_INFORMATION, lockingInformation);
259            }
260            Collection<AssetLock> assetLocks = getBusinessObjectService().findMatching(AssetLock.class, fieldValues);
261            return assetLocks != null && !assetLocks.isEmpty();
262        }
263    
264        /**
265         * Based on the given documentTypeName, it decides what document types could block it.
266         * 
267         * @see org.kuali.kfs.module.cam.service.AssetLockService#isAssetLocked(java.util.List, java.lang.String, java.lang.String)
268         */
269        public boolean isAssetLocked(List<Long> assetNumbers, String documentTypeName, String excludingDocumentNumber) {
270            if (assetNumbers == null || assetNumbers.isEmpty()) {
271                return false;
272            }
273            if (!isPurApDocument(documentTypeName)) {
274                List<String> lockingDocumentNumbers = getAssetLockingDocuments(assetNumbers, documentTypeName, excludingDocumentNumber);
275                if (lockingDocumentNumbers != null && !lockingDocumentNumbers.isEmpty()) {
276                    addBlockingDocumentErrorMessage(lockingDocumentNumbers, documentTypeName);
277                    return true;
278                }
279            }
280            return false;
281        }
282    
283        /**
284         * @see org.kuali.kfs.module.cam.service.AssetLockService#getAssetLockingDocuments(java.util.List, java.lang.String,
285         *      java.lang.String)
286         */
287        public List<String> getAssetLockingDocuments(List<Long> assetNumbers, String documentTypeName, String excludingDocumentNumber) {
288            Collection blockingDocumentTypes = getBlockingDocumentTypes(documentTypeName);
289            List<String> lockingDocumentNumbers = getCapitalAssetLockDao().getLockingDocumentNumbers(assetNumbers, blockingDocumentTypes, excludingDocumentNumber);
290            return lockingDocumentNumbers;
291        }
292    
293    
294    }