/* * 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.document.service.impl; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.coa.service.ObjectCodeService; import org.kuali.kfs.module.cam.CamsConstants; import org.kuali.kfs.module.cam.CamsKeyConstants; import org.kuali.kfs.module.cam.CamsPropertyConstants; import org.kuali.kfs.module.cam.businessobject.Asset; import org.kuali.kfs.module.cam.businessobject.AssetGlobal; import org.kuali.kfs.module.cam.businessobject.AssetPayment; import org.kuali.kfs.module.cam.businessobject.AssetPaymentAllocationType; import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail; import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail; import org.kuali.kfs.module.cam.document.AssetPaymentDocument; import org.kuali.kfs.module.cam.document.dataaccess.AssetPaymentDao; import org.kuali.kfs.module.cam.document.service.AssetGlobalService; import org.kuali.kfs.module.cam.document.service.AssetPaymentService; import org.kuali.kfs.module.cam.document.service.AssetRetirementService; import org.kuali.kfs.module.cam.document.service.AssetService; import org.kuali.kfs.module.cam.util.distribution.AssetDistribution; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.UniversityDate; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.service.UniversityDateService; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; import org.springframework.transaction.annotation.Transactional; @Transactional public class AssetPaymentServiceImpl implements AssetPaymentService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetPaymentServiceImpl.class); private BusinessObjectService businessObjectService; private AssetPaymentDao assetPaymentDao; private ParameterService parameterService; private UniversityDateService universityDateService; private ObjectCodeService objectCodeService; private AssetRetirementService assetRetirementService; private AssetService assetService; private AssetGlobalService assetGlobalService; /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#getMaxSequenceNumber(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public Integer getMaxSequenceNumber(Long capitalAssetNumber) { return this.getAssetPaymentDao().getMaxSquenceNumber(capitalAssetNumber); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentFederalOwned(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentFederalOwned(AssetPayment assetPayment) { assetPayment.refreshReferenceObject(CamsPropertyConstants.AssetPayment.OBJECT_CODE_CURRENT); if (ObjectUtils.isNotNull(assetPayment.getObjectCodeCurrent())) { String subTypeCode = assetPayment.getObjectCodeCurrent().getFinancialObjectSubTypeCode(); return this.getParameterService().getParameterValuesAsString(Asset.class, CamsConstants.Parameters.FEDERAL_OWNED_OBJECT_SUB_TYPES).contains(subTypeCode); } return false; } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForAccumDeprGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentEligibleForAccumDeprGLPosting(AssetPayment assetPayment) { KualiDecimal accumlatedDepreciationAmount = assetPayment.getAccumulatedPrimaryDepreciationAmount(); return accumlatedDepreciationAmount == null ? false : !accumlatedDepreciationAmount.isZero(); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForCapitalizationGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentEligibleForCapitalizationGLPosting(AssetPayment assetPayment) { KualiDecimal accountChargeAmount = assetPayment.getAccountChargeAmount(); return accountChargeAmount == null ? false : !accountChargeAmount.isZero(); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForOffsetGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentEligibleForOffsetGLPosting(AssetPayment assetPayment) { KualiDecimal accountChargeAmount = assetPayment.getAccountChargeAmount(); if (assetPayment.getAccumulatedPrimaryDepreciationAmount() == null) { assetPayment.setAccumulatedPrimaryDepreciationAmount(KualiDecimal.ZERO); } KualiDecimal accumlatedDepreciationAmount = assetPayment.getAccumulatedPrimaryDepreciationAmount(); return accountChargeAmount == null ? false : !accountChargeAmount.subtract(accumlatedDepreciationAmount).isZero(); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentFinancialObjectActive(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentFinancialObjectActive(AssetPayment assetPayment) { ObjectCode financialObjectCode = new ObjectCode(); financialObjectCode.setUniversityFiscalYear(getUniversityDateService().getCurrentFiscalYear()); financialObjectCode.setChartOfAccountsCode(assetPayment.getChartOfAccountsCode()); financialObjectCode.setFinancialObjectCode(assetPayment.getFinancialObjectCode()); financialObjectCode = (ObjectCode) getBusinessObjectService().retrieve(financialObjectCode); if (ObjectUtils.isNotNull(financialObjectCode)) { return financialObjectCode.isActive(); } return false; } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#processApprovedAssetPayment(org.kuali.kfs.module.cam.document.AssetPaymentDocument) */ public void processApprovedAssetPayment(AssetPaymentDocument document) { // Creating new asset payment records processPayments(document); } /** * Creates a new asset payment record for each new asset payment detail record and then save them * * @param document */ protected void processPayments(AssetPaymentDocument document) { List<AssetPaymentDetail> assetPaymentDetailLines = document.getSourceAccountingLines(); List<AssetPaymentAssetDetail> assetPaymentAssetDetails = document.getAssetPaymentAssetDetail(); List<PersistableBusinessObject> assetPayments = new ArrayList<PersistableBusinessObject>(); Integer maxSequenceNo = new Integer(0); //instantiate asset payment distributor AssetDistribution paymentDistributor = document.getAssetPaymentDistributor(); // Calculating the asset payments distributions for each individual asset on the list Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> assetPaymentDistributionMap = paymentDistributor.getAssetPaymentDistributions(); try { // Creating a new payment record for each asset that has payments. for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) { maxSequenceNo = getMaxSequenceNumber(assetPaymentAssetDetail.getCapitalAssetNumber()); KualiDecimal totalAmount = KualiDecimal.ZERO; for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetailLines) { // Retrieve the asset payment from the distribution map KualiDecimal amount = assetPaymentDistributionMap.get(assetPaymentDetail.getAssetPaymentDetailKey()).get(assetPaymentAssetDetail); totalAmount = totalAmount.add(amount); AssetPayment assetPayment = new AssetPayment(assetPaymentDetail); assetPayment.setCapitalAssetNumber(assetPaymentAssetDetail.getCapitalAssetNumber()); assetPayment.setTransferPaymentCode(CamsConstants.AssetPayment.TRANSFER_PAYMENT_CODE_N); assetPayment.setPaymentSequenceNumber(++maxSequenceNo); if (StringUtils.isBlank(assetPayment.getDocumentNumber())) { assetPayment.setDocumentNumber(document.getDocumentNumber()); } assetPayment.setAccountChargeAmount(amount); KualiDecimal baseAmount = KualiDecimal.ZERO; // If the object sub type is not in the list of federally owned object sub types, then... ObjectCode objectCode = this.getObjectCodeService().getByPrimaryId(assetPaymentDetail.getPostingYear(), assetPaymentDetail.getChartOfAccountsCode(), assetPaymentDetail.getFinancialObjectCode()); // Depreciation Base Amount will be assigned to each payment only when the object code's sub type code is not a // federally owned one if (!this.isNonDepreciableFederallyOwnedObjSubType(objectCode.getFinancialObjectSubTypeCode())) { baseAmount = baseAmount.add(amount); } assetPayment.setPrimaryDepreciationBaseAmount(baseAmount); // Resetting each period field its value with nulls this.adjustPaymentAmounts(assetPayment, false, true); // add new payment assetPayments.add(assetPayment); } // *********************BEGIN - Updating Asset *********************************************************** // Retrieving the asset that will have its cost updated HashMap<String, Long> keys = new HashMap<String, Long>(); keys.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, assetPaymentAssetDetail.getCapitalAssetNumber()); Asset asset = (Asset) getBusinessObjectService().findByPrimaryKey(Asset.class, keys); // Set previous total cost if (assetPaymentAssetDetail.getPreviousTotalCostAmount() == null) { assetPaymentAssetDetail.setPreviousTotalCostAmount(new KualiDecimal(0)); } // Setting the asset's new cost. asset.setTotalCostAmount(assetPaymentAssetDetail.getPreviousTotalCostAmount().add(totalAmount)); // Setting the asset's financial object sub-type Code. Only when the asset doesn't have one. if (asset.getFinancialObjectSubTypeCode() == null || asset.getFinancialObjectSubTypeCode().trim().equals("")) { asset.setFinancialObjectSubTypeCode(assetPaymentDetailLines.get(0).getObjectCode().getFinancialObjectSubTypeCode()); } // Saving changes getBusinessObjectService().save(asset); // *********************END - Updating Asset *********************************************************** } } catch (Exception e) { throw new RuntimeException(e); } // Finally, saving all the asset payment records. this.getBusinessObjectService().save(assetPayments); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#adjustPaymentAmounts(org.kuali.kfs.module.cam.businessobject.AssetPayment, * boolean, boolean) */ public void adjustPaymentAmounts(AssetPayment assetPayment, boolean reverseAmount, boolean nullPeriodDepreciation) throws IllegalAccessException, InvocationTargetException { LOG.debug("Starting - adjustAmounts() "); PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(AssetPayment.class); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { Method readMethod = propertyDescriptor.getReadMethod(); if (readMethod != null && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { KualiDecimal amount = (KualiDecimal) readMethod.invoke(assetPayment); Method writeMethod = propertyDescriptor.getWriteMethod(); if (writeMethod != null && amount != null) { // Reset periodic depreciation expenses if (nullPeriodDepreciation && Pattern.matches(CamsConstants.SET_PERIOD_DEPRECIATION_AMOUNT_REGEX, writeMethod.getName().toLowerCase())) { Object[] nullVal = new Object[] { null }; writeMethod.invoke(assetPayment, nullVal); } else if (reverseAmount) { // reverse the amounts writeMethod.invoke(assetPayment, (amount.negated())); } } } } LOG.debug("Finished - adjustAmounts()"); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment) */ public boolean isPaymentEligibleForGLPosting(AssetPayment assetPayment) { // Transfer payment code flag is not Y boolean isEligible = !CamsConstants.AssetPayment.TRANSFER_PAYMENT_CODE_Y.equals(assetPayment.getTransferPaymentCode()); // Financial object code is currently active isEligible &= isPaymentFinancialObjectActive(assetPayment); // Payment is not federally funded isEligible &= !isPaymentFederalOwned(assetPayment); return isEligible; } /** * Checks if object sub type is a non-depreciable federally owned object sub type * * @param string objectSubType2 * @return true if is NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES */ public boolean isNonDepreciableFederallyOwnedObjSubType(String objectSubType) { List<String> federallyOwnedObjectSubTypes = new ArrayList<String>(); if (this.getParameterService().parameterExists(KfsParameterConstants.CAPITAL_ASSETS_BATCH.class, CamsConstants.Parameters.NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES)) { federallyOwnedObjectSubTypes = new ArrayList<String>( this.getParameterService().getParameterValuesAsString(KfsParameterConstants.CAPITAL_ASSETS_BATCH.class, CamsConstants.Parameters.NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES) ); } return federallyOwnedObjectSubTypes.contains(objectSubType); } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#extractPostedDatePeriod(org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail) */ public boolean extractPostedDatePeriod(AssetPaymentDetail assetPaymentDetail) { boolean valid = true; Map<String, Object> primaryKeys = new HashMap<String, Object>(); primaryKeys.put(KFSPropertyConstants.UNIVERSITY_DATE, assetPaymentDetail.getExpenditureFinancialDocumentPostedDate()); UniversityDate universityDate = (UniversityDate) businessObjectService.findByPrimaryKey(UniversityDate.class, primaryKeys); if (universityDate != null) { assetPaymentDetail.setPostingYear(universityDate.getUniversityFiscalYear()); assetPaymentDetail.setPostingPeriodCode(universityDate.getUniversityFiscalAccountingPeriod()); return true; } else { return false; } } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#getAssetPaymentDetailQuantity(org.kuali.kfs.module.cam.businessobject.AssetGlobal) */ public Integer getAssetPaymentDetailQuantity(AssetGlobal assetGlobal) { Integer assetPaymentDetailQuantity = 0; for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) { assetPaymentDetailQuantity++; } return assetPaymentDetailQuantity; } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#validateAssets(java.lang.String, * org.kuali.kfs.module.cam.businessobject.Asset) */ public boolean validateAssets(String errorPath, Asset asset) { boolean valid = true; // Validating the asset is a capital asset if (!this.getAssetService().isCapitalAsset(asset)) { GlobalVariables.getMessageMap().putError(errorPath, CamsKeyConstants.Payment.ERROR_NON_CAPITAL_ASSET, asset.getCapitalAssetNumber().toString()); valid &= false; } // Validating the asset hasn't been retired if (this.getAssetService().isAssetRetired(asset)) { GlobalVariables.getMessageMap().putError(errorPath, CamsKeyConstants.Retirement.ERROR_NON_ACTIVE_ASSET_RETIREMENT, asset.getCapitalAssetNumber().toString()); valid &= false; } return valid; } /** * This method determines whether or not an asset has different object sub type codes in its documents. * * @return true when the asset has payments with object codes that point to different object sub type codes */ public boolean hasDifferentObjectSubTypes(AssetPaymentDocument document) { List<String> subTypes = new ArrayList<String>(); subTypes = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(Asset.class, CamsConstants.Parameters.OBJECT_SUB_TYPE_GROUPS) ); List<AssetPaymentDetail> assetPaymentDetails = document.getSourceAccountingLines(); List<String> validObjectSubTypes = new ArrayList<String>(); String objectSubTypeCode = null; /* * Expected system parameter elements (object sub types). [BD,BF] [CM,CF,CO] [UC,UF,UO] [LI,LF] */ // Getting the list of object sub type codes from the asset payments on the jsp. List<String> objectSubTypeList = new ArrayList<String>(); for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetails) { assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.OBJECT_CODE); if (ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) { return false; } objectSubTypeList.add(assetPaymentDetail.getObjectCode().getFinancialObjectSubTypeCode()); } if (!SpringContext.getBean(AssetService.class).isObjectSubTypeCompatible(objectSubTypeList)) { return true; } List<AssetPaymentAssetDetail> assetPaymentAssetDetails = document.getAssetPaymentAssetDetail(); for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) { assetPaymentAssetDetail.getAsset().refreshReferenceObject(CamsPropertyConstants.Asset.ASSET_PAYMENTS); List<AssetPayment> assetPayments = assetPaymentAssetDetail.getAsset().getAssetPayments(); // Comparing against the already approved asset payments if (!assetPayments.isEmpty()) { for (AssetPayment assetPayment : assetPayments) { String paymentSubObjectType = assetPayment.getFinancialObject().getFinancialObjectSubTypeCode(); validObjectSubTypes = new ArrayList<String>(); for (String subType : subTypes) { validObjectSubTypes = Arrays.asList(StringUtils.split(subType, ",")); if (validObjectSubTypes.contains(paymentSubObjectType)) { break; } validObjectSubTypes = new ArrayList<String>(); } if (validObjectSubTypes.isEmpty()) validObjectSubTypes.add(paymentSubObjectType); // Comparing the same asset payment document for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetails) { if (!validObjectSubTypes.contains(assetPaymentDetail.getObjectCode().getFinancialObjectSubTypeCode())) { // Differences where found. return true; } } } } } // If none object sub types are different... return false; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } public BusinessObjectService getBusinessObjectService() { return businessObjectService; } public ParameterService getParameterService() { return parameterService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public AssetPaymentDao getAssetPaymentDao() { return assetPaymentDao; } public void setAssetPaymentDao(AssetPaymentDao assetPaymentDao) { this.assetPaymentDao = assetPaymentDao; } public ObjectCodeService getObjectCodeService() { return objectCodeService; } public void setObjectCodeService(ObjectCodeService objectCodeService) { this.objectCodeService = objectCodeService; } public UniversityDateService getUniversityDateService() { return universityDateService; } public void setUniversityDateService(UniversityDateService universityDateService) { this.universityDateService = universityDateService; } public AssetRetirementService getAssetRetirementService() { return assetRetirementService; } public void setAssetRetirementService(AssetRetirementService assetRetirementService) { this.assetRetirementService = assetRetirementService; } public AssetService getAssetService() { return assetService; } public void setAssetService(AssetService assetService) { this.assetService = assetService; } /** * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#getAssetDistributionTypeColumnName(java.lang.String) */ public AssetPaymentAllocationType getAssetDistributionType(String distributionCode) { HashMap<String, String> keys = new HashMap<String, String>(); keys.put("distributionCode", distributionCode); AssetPaymentAllocationType d = (AssetPaymentAllocationType) getBusinessObjectService().findByPrimaryKey(AssetPaymentAllocationType.class, keys); return d; } }