/* * 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.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.kuali.kfs.coa.businessobject.Organization; import org.kuali.kfs.coa.service.OrganizationService; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.Account; 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.CamsPropertyConstants; import org.kuali.kfs.module.cam.businessobject.Asset; import org.kuali.kfs.module.cam.businessobject.AssetGlpeSourceDetail; import org.kuali.kfs.module.cam.businessobject.AssetObjectCode; import org.kuali.kfs.module.cam.businessobject.AssetPayment; import org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal; import org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobalDetail; import org.kuali.kfs.module.cam.document.gl.CamsGeneralLedgerPendingEntrySourceBase; import org.kuali.kfs.module.cam.document.service.AssetObjectCodeService; 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.ObjectValueUtils; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.authorization.FinancialSystemMaintenanceDocumentAuthorizerBase; import org.kuali.kfs.sys.service.UniversityDateService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentDictionaryService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; public class AssetRetirementServiceImpl implements AssetRetirementService { protected enum AmountCategory { CAPITALIZATION { @Override void setParams(AssetGlpeSourceDetail postable, AssetPayment assetPayment, AssetObjectCode assetObjectCode) { postable.setCapitalization(true); ParameterService parameterService = SpringContext.getBean(ParameterService.class); String lineDescription = parameterService.getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.AssetRetirementGlobal.CAPITALIZATION_LINE_DESCRIPTION); postable.setFinancialDocumentLineDescription(lineDescription); postable.setAmount(assetPayment.getAccountChargeAmount()); postable.setFinancialObjectCode(assetObjectCode.getCapitalizationFinancialObjectCode()); postable.setObjectCode(assetObjectCode.getCapitalizationFinancialObject()); }; }, ACCUMMULATE_DEPRECIATION { @Override void setParams(AssetGlpeSourceDetail postable, AssetPayment assetPayment, AssetObjectCode assetObjectCode) { postable.setAccumulatedDepreciation(true); ParameterService parameterService = SpringContext.getBean(ParameterService.class); String lineDescription = parameterService.getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.AssetRetirementGlobal.ACCUMULATED_DEPRECIATION_LINE_DESCRIPTION); postable.setFinancialDocumentLineDescription(lineDescription); postable.setAmount(assetPayment.getAccumulatedPrimaryDepreciationAmount()); postable.setFinancialObjectCode(assetObjectCode.getAccumulatedDepreciationFinancialObjectCode()); postable.setObjectCode(assetObjectCode.getAccumulatedDepreciationFinancialObject()); }; }, OFFSET_AMOUNT { @Override void setParams(AssetGlpeSourceDetail postable, AssetPayment assetPayment, AssetObjectCode assetObjectCode) { postable.setCapitalizationOffset(true); ParameterService parameterService = SpringContext.getBean(ParameterService.class); String lineDescription = parameterService.getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.AssetRetirementGlobal.OFFSET_AMOUNT_LINE_DESCRIPTION); postable.setFinancialDocumentLineDescription(lineDescription); KualiDecimal accumlatedDepreciationAmount = (assetPayment.getAccumulatedPrimaryDepreciationAmount() == null ? new KualiDecimal(0) : assetPayment.getAccumulatedPrimaryDepreciationAmount()); postable.setAmount(assetPayment.getAccountChargeAmount().subtract(accumlatedDepreciationAmount)); postable.setFinancialObjectCode(SpringContext.getBean(ParameterService.class).getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.Parameters.DEFAULT_GAIN_LOSS_DISPOSITION_OBJECT_CODE).trim()); Map pkMap = new HashMap(); UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class); String gainDispositionObjectCode = parameterService.getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.Parameters.DEFAULT_GAIN_LOSS_DISPOSITION_OBJECT_CODE); pkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, universityDateService.getCurrentFiscalYear()); pkMap.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, assetPayment.getAsset().getOrganizationOwnerChartOfAccountsCode()); pkMap.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, gainDispositionObjectCode); ObjectCode offsetFinancialObject = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(ObjectCode.class, pkMap); postable.setObjectCode(offsetFinancialObject); }; }; abstract void setParams(AssetGlpeSourceDetail postable, AssetPayment assetPayment, AssetObjectCode assetObjectCode); } protected UniversityDateService universityDateService; protected AssetObjectCodeService assetObjectCodeService; protected BusinessObjectService businessObjectService; protected AssetPaymentService assetPaymentService; protected ParameterService parameterService; protected AssetService assetService; protected OrganizationService organizationService; protected ObjectCodeService objectCodeService; public ObjectCodeService getObjectCodeService(){ return objectCodeService; } public void setObjectCodeService(ObjectCodeService serv){ this.objectCodeService = serv; } public OrganizationService getOrganizationService(){ return this.organizationService; } public void setOrganizationService(OrganizationService serv){ this.organizationService = serv; } public ParameterService getParameterService() { return parameterService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public AssetService getAssetService() { return assetService; } public void setAssetService(AssetService assetService) { this.assetService = assetService; } public UniversityDateService getUniversityDateService() { return universityDateService; } public void setUniversityDateService(UniversityDateService universityDateService) { this.universityDateService = universityDateService; } public AssetObjectCodeService getAssetObjectCodeService() { return assetObjectCodeService; } public void setAssetObjectCodeService(AssetObjectCodeService assetObjectCodeService) { this.assetObjectCodeService = assetObjectCodeService; } public BusinessObjectService getBusinessObjectService() { return businessObjectService; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } public AssetPaymentService getAssetPaymentService() { return assetPaymentService; } public void setAssetPaymentService(AssetPaymentService assetPaymentService) { this.assetPaymentService = assetPaymentService; } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAssetRetiredBySoldOrAuction(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public boolean isAssetRetiredByAuction(AssetRetirementGlobal assetRetirementGlobal) { return CamsConstants.AssetRetirementReasonCode.AUCTION.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAssetRetiredBySold(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public boolean isAssetRetiredBySold(AssetRetirementGlobal assetRetirementGlobal) { return CamsConstants.AssetRetirementReasonCode.SOLD.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAssetRetiredByExternalTransferOrGift(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public boolean isAssetRetiredByExternalTransferOrGift(AssetRetirementGlobal assetRetirementGlobal) { return CamsConstants.AssetRetirementReasonCode.EXTERNAL_TRANSFER.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()) || CamsConstants.AssetRetirementReasonCode.GIFT.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAssetRetiredByMerged(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public boolean isAssetRetiredByMerged(AssetRetirementGlobal assetRetirementGlobal) { return CamsConstants.AssetRetirementReasonCode.MERGED.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAssetRetiredByTheft(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public boolean isAssetRetiredByTheft(AssetRetirementGlobal assetRetirementGlobal) { return CamsConstants.AssetRetirementReasonCode.THEFT.equalsIgnoreCase(assetRetirementGlobal.getRetirementReasonCode()); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#getAssetRetirementReasonName(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal) */ @Override public String getAssetRetirementReasonName(AssetRetirementGlobal assetRetirementGlobal) { return assetRetirementGlobal.getRetirementReason() == null ? new String() : assetRetirementGlobal.getRetirementReason().getRetirementReasonName(); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#generateOffsetPaymentsForEachSource(org.kuali.kfs.module.cam.businessobject.Asset, * java.util.List, java.lang.String) */ @Override public void generateOffsetPaymentsForEachSource(Asset sourceAsset, List<PersistableBusinessObject> persistables, String currentDocumentNumber) { List<AssetPayment> offsetPayments = new ArrayList<AssetPayment>(); Integer maxSequenceNo = assetPaymentService.getMaxSequenceNumber(sourceAsset.getCapitalAssetNumber()); try { for (AssetPayment sourcePayment : sourceAsset.getAssetPayments()) { AssetPayment offsetPayment = new AssetPayment(); ObjectValueUtils.copySimpleProperties(sourcePayment, offsetPayment); offsetPayment.setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_RETIREMENT_MERGE); offsetPayment.setDocumentNumber(currentDocumentNumber); offsetPayment.setPaymentSequenceNumber(++maxSequenceNo); assetPaymentService.adjustPaymentAmounts(offsetPayment, true, false); offsetPayments.add(offsetPayment); } } catch (Exception e) { throw new RuntimeException("Error occured while creating offset payment in retirement", e); } persistables.addAll(offsetPayments); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#generateNewPaymentForTarget(org.kuali.kfs.module.cam.businessobject.Asset, * org.kuali.kfs.module.cam.businessobject.Asset, java.util.List, java.lang.Integer, java.lang.String) */ @Override public Integer generateNewPaymentForTarget(Asset targetAsset, Asset sourceAsset, List<PersistableBusinessObject> persistables, Integer maxSequenceNo, String currentDocumentNumber) { List<AssetPayment> newPayments = new ArrayList<AssetPayment>(); try { for (AssetPayment sourcePayment : sourceAsset.getAssetPayments()) { AssetPayment newPayment = new AssetPayment(); ObjectValueUtils.copySimpleProperties(sourcePayment, newPayment); newPayment.setCapitalAssetNumber(targetAsset.getCapitalAssetNumber()); newPayment.setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_RETIREMENT_MERGE); newPayment.setPaymentSequenceNumber(++maxSequenceNo); newPayment.setDocumentNumber(currentDocumentNumber); assetPaymentService.adjustPaymentAmounts(newPayment, false, false); newPayments.add(newPayment); } } catch (Exception e) { throw new RuntimeException("Error occured while creating new payment in retirement", e); } persistables.addAll(newPayments); return maxSequenceNo; } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isRetirementReasonCodeInGroup(java.lang.String, * java.lang.String) */ @Override public boolean isRetirementReasonCodeInGroup(String reasonCodeGroup, String reasonCode) { if (StringUtils.isBlank(reasonCodeGroup) || StringUtils.isBlank(reasonCode)) { return false; } return Arrays.asList(reasonCodeGroup.split(";")).contains(reasonCode); } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#isAllowedRetireMultipleAssets(java.lang.String) */ @Override public boolean isAllowedRetireMultipleAssets(MaintenanceDocument maintenanceDocument) { FinancialSystemMaintenanceDocumentAuthorizerBase documentAuthorizer = (FinancialSystemMaintenanceDocumentAuthorizerBase) SpringContext.getBean(DocumentDictionaryService.class).getDocumentAuthorizer(maintenanceDocument); boolean isAuthorized = documentAuthorizer.isAuthorized(maintenanceDocument, CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.RETIRE_MULTIPLE, GlobalVariables.getUserSession().getPerson().getPrincipalId()); return isAuthorized; } /** * @see org.kuali.kfs.module.cam.document.service.AssetRetirementService#createGLPostables(org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobal, * org.kuali.module.cams.gl.CamsGlPosterBase) */ @Override public void createGLPostables(AssetRetirementGlobal assetRetirementGlobal, CamsGeneralLedgerPendingEntrySourceBase assetRetirementGlPoster) { List<AssetRetirementGlobalDetail> assetRetirementGlobalDetails = assetRetirementGlobal.getAssetRetirementGlobalDetails(); for (AssetRetirementGlobalDetail assetRetirementGlobalDetail : assetRetirementGlobalDetails) { Asset asset = assetRetirementGlobalDetail.getAsset(); for (AssetPayment assetPayment : asset.getAssetPayments()) { if (!getAssetPaymentService().isPaymentFederalOwned(assetPayment) && !("Y".equals(assetPayment.getTransferPaymentCode()))) { List<GeneralLedgerPendingEntrySourceDetail> postables = generateGlPostablesForOnePayment(assetRetirementGlobal.getDocumentNumber(), assetRetirementGlPoster, asset, assetPayment); assetRetirementGlPoster.getPostables().addAll(postables); } } } } /** * Generate a collection of Postables for each payment. * * @param documentNumber * @param assetRetirementGlPoster * @param asset * @param assetPayment * @return */ protected List<GeneralLedgerPendingEntrySourceDetail> generateGlPostablesForOnePayment(String documentNumber, CamsGeneralLedgerPendingEntrySourceBase assetRetirementGlPoster, Asset asset, AssetPayment assetPayment) { List<GeneralLedgerPendingEntrySourceDetail> postables = new ArrayList<GeneralLedgerPendingEntrySourceDetail>(); Account plantAccount = getPlantFundAccount(assetPayment); if (ObjectUtils.isNotNull(plantAccount)) { if (assetPaymentService.isPaymentEligibleForCapitalizationGLPosting(assetPayment)) { createNewPostable(AmountCategory.CAPITALIZATION, asset, assetPayment, documentNumber, plantAccount, postables); } if (assetPaymentService.isPaymentEligibleForAccumDeprGLPosting(assetPayment)) { createNewPostable(AmountCategory.ACCUMMULATE_DEPRECIATION, asset, assetPayment, documentNumber, plantAccount, postables); } if (assetPaymentService.isPaymentEligibleForOffsetGLPosting(assetPayment)) { createNewPostable(AmountCategory.OFFSET_AMOUNT, asset, assetPayment, documentNumber, plantAccount, postables); } } return postables; } /** * This method creates one postable and sets the values. * * @param category * @param asset * @param assetPayment * @param documentNumber * @param plantAccount * @return */ protected void createNewPostable(AmountCategory category, Asset asset, AssetPayment assetPayment, String documentNumber, Account plantAccount, List<GeneralLedgerPendingEntrySourceDetail> postables) { boolean success = true; AssetGlpeSourceDetail postable = new AssetGlpeSourceDetail(); AssetObjectCode assetObjectCode = getAssetObjectCode(asset, assetPayment); category.setParams(postable, assetPayment, assetObjectCode); // Set Postable attributes which are common among Capitalized, Accumulated Depreciation and gain/loss disposition . postable.setDocumentNumber(documentNumber); postable.setAccount(plantAccount); postable.setAccountNumber(plantAccount.getAccountNumber()); postable.setBalanceTypeCode(CamsConstants.Postable.GL_BALANCE_TYPE_CODE_AC); postable.setChartOfAccountsCode(plantAccount.getChartOfAccountsCode()); postable.setPostingYear(universityDateService.getCurrentFiscalYear()); // Fields copied from payment postable.setFinancialSubObjectCode(assetPayment.getFinancialSubObjectCode()); postable.setProjectCode(assetPayment.getProjectCode()); postable.setSubAccountNumber(assetPayment.getSubAccountNumber()); postable.setOrganizationReferenceId(assetPayment.getOrganizationReferenceId()); postables.add(postable); } protected AssetObjectCode getAssetObjectCode(Asset asset, AssetPayment assetPayment) { ObjectCodeService objectCodeService = getObjectCodeService(); ObjectCode objectCode = objectCodeService.getByPrimaryIdForCurrentYear(assetPayment.getChartOfAccountsCode(), assetPayment.getFinancialObjectCode()); AssetObjectCode assetObjectCode = assetObjectCodeService.findAssetObjectCode(asset.getOrganizationOwnerChartOfAccountsCode(), objectCode.getFinancialObjectSubTypeCode()); return assetObjectCode; } /** * Get the offset Object Code. * * @param asset * @return */ @Override public ObjectCode getOffsetFinancialObject(String chartOfAccountsCode) { Map pkMap = new HashMap(); UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class); ParameterService parameterService = SpringContext.getBean(ParameterService.class); String gainDispositionObjectCode = parameterService.getParameterValueAsString(AssetRetirementGlobal.class, CamsConstants.Parameters.DEFAULT_GAIN_LOSS_DISPOSITION_OBJECT_CODE); pkMap.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, universityDateService.getCurrentFiscalYear()); pkMap.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode); pkMap.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, gainDispositionObjectCode); return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(ObjectCode.class, pkMap); } /** * Get the corresponding Plant Fund Account object based on the payment's financialObjectSubTypeCode. * * @param asset * @param payment * @return */ protected Account getPlantFundAccount(AssetPayment payment) { Account plantFundAccount = null; /*Don't check for null. If this fails, we are without hope.*/ payment.refreshReferenceObject(CamsPropertyConstants.AssetPayment.FINANCIAL_OBJECT); String finObjectCode = payment.getFinancialObjectCode(); if (StringUtils.isNotEmpty(finObjectCode)){ ObjectCodeService obj = getObjectCodeService(); ObjectCode objectCode = obj.getByPrimaryIdForCurrentYear(payment.getChartOfAccountsCode(), finObjectCode); String financialObjectSubTypeCode = objectCode.getFinancialObjectSubTypeCode(); /*KFSCNTRB-1629 the plant fund account should be that from the payment's object code, * not the asset's object code.*/ String coaCode = payment.getChartOfAccountsCode(); Account tempAcct = payment.getAccount(); String orgCode = tempAcct.getOrganizationCode(); Organization org = getOrganizationService().getByPrimaryId(coaCode, orgCode); if (assetService.isAssetMovableCheckByPayment(financialObjectSubTypeCode)) { plantFundAccount = org.getOrganizationPlantAccount(); } else { plantFundAccount = org.getCampusPlantAccount(); } } return plantFundAccount; } /** * This method generates the calculatedTotal amount based on salePrice + handlingFeeAmount + preventiveMaintenanceAmount. * * @param salePrice * @param handlingFeeAmount * @param preventiveMaintenanceAmount * @return */ @Override public String generateCalculatedTotal(String salePrice, String handlingFeeAmount, String preventiveMaintenanceAmount) { KualiDecimal calculatedTotal = KualiDecimal.ZERO; if (!salePrice.isEmpty()) { KualiDecimal testAmount = toKualiDecimal(salePrice); if(testAmount.isZero()){ return "Please enter Sale Price in 1,234,567.00 Format"; } calculatedTotal = calculatedTotal.add(testAmount); } if (!handlingFeeAmount.isEmpty()) { KualiDecimal testAmount = toKualiDecimal(handlingFeeAmount); if(testAmount.isZero()){ return "Please enter Handling Fee Amount in 1,234,567.00 Format"; } calculatedTotal = calculatedTotal.add(testAmount); } if (!preventiveMaintenanceAmount.isEmpty()) { KualiDecimal testAmount = toKualiDecimal(preventiveMaintenanceAmount); if(testAmount.isZero()){ return "Please enter Preventive Maintenance Amount in 1,234,567.00 Format"; } calculatedTotal = calculatedTotal.add(testAmount); } return calculatedTotal.toString(); } /** * This method converts a String to a KualiDecimal via Double. Or else returns a Zero to invoke proper error message and to avoid breaking DWR call * * @param amount * @return */ protected KualiDecimal toKualiDecimal(String amount) { KualiDecimal newAmount; try{ newAmount = new KualiDecimal(java.lang.Double.parseDouble(amount)); }catch(NumberFormatException e){ return KualiDecimal.ZERO; } return newAmount; } }