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.document.service.impl;
017    
018    import java.beans.PropertyDescriptor;
019    import java.lang.reflect.InvocationTargetException;
020    import java.lang.reflect.Method;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.HashMap;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.regex.Pattern;
027    
028    import org.apache.commons.beanutils.PropertyUtils;
029    import org.apache.commons.lang.StringUtils;
030    import org.kuali.kfs.coa.businessobject.ObjectCode;
031    import org.kuali.kfs.coa.service.ObjectCodeService;
032    import org.kuali.kfs.module.cam.CamsConstants;
033    import org.kuali.kfs.module.cam.CamsKeyConstants;
034    import org.kuali.kfs.module.cam.CamsPropertyConstants;
035    import org.kuali.kfs.module.cam.businessobject.Asset;
036    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
037    import org.kuali.kfs.module.cam.businessobject.AssetPayment;
038    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
039    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
040    import org.kuali.kfs.module.cam.document.AssetPaymentDocument;
041    import org.kuali.kfs.module.cam.document.dataaccess.AssetPaymentDao;
042    import org.kuali.kfs.module.cam.document.service.AssetGlobalService;
043    import org.kuali.kfs.module.cam.document.service.AssetPaymentService;
044    import org.kuali.kfs.module.cam.document.service.AssetRetirementService;
045    import org.kuali.kfs.module.cam.document.service.AssetService;
046    import org.kuali.kfs.module.cam.util.AssetPaymentDistributor;
047    import org.kuali.kfs.sys.KFSPropertyConstants;
048    import org.kuali.kfs.sys.businessobject.UniversityDate;
049    import org.kuali.kfs.sys.context.SpringContext;
050    import org.kuali.kfs.sys.service.UniversityDateService;
051    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
052    import org.kuali.rice.kns.bo.PersistableBusinessObject;
053    import org.kuali.rice.kns.service.BusinessObjectService;
054    import org.kuali.rice.kns.service.ParameterService;
055    import org.kuali.rice.kns.util.GlobalVariables;
056    import org.kuali.rice.kns.util.KualiDecimal;
057    import org.kuali.rice.kns.util.ObjectUtils;
058    import org.springframework.transaction.annotation.Transactional;
059    
060    @Transactional
061    public class AssetPaymentServiceImpl implements AssetPaymentService {
062        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetPaymentServiceImpl.class);
063    
064        private BusinessObjectService businessObjectService;
065        private AssetPaymentDao assetPaymentDao;
066        private ParameterService parameterService;
067        private UniversityDateService universityDateService;
068        private ObjectCodeService objectCodeService;
069        private AssetRetirementService assetRetirementService;
070        private AssetService assetService;
071        private AssetGlobalService assetGlobalService;
072    
073        /**
074         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#getMaxSequenceNumber(org.kuali.kfs.module.cam.businessobject.AssetPayment)
075         */
076        public Integer getMaxSequenceNumber(Long capitalAssetNumber) {
077            return this.getAssetPaymentDao().getMaxSquenceNumber(capitalAssetNumber);
078        }
079    
080        /**
081         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentFederalOwned(org.kuali.kfs.module.cam.businessobject.AssetPayment)
082         */
083        public boolean isPaymentFederalOwned(AssetPayment assetPayment) {
084            assetPayment.refreshReferenceObject(CamsPropertyConstants.AssetPayment.FINANCIAL_OBJECT);
085            if (ObjectUtils.isNotNull(assetPayment.getFinancialObject())) {
086                return this.getParameterService().getParameterValues(Asset.class, CamsConstants.Parameters.FEDERAL_OWNED_OBJECT_SUB_TYPES).contains(assetPayment.getFinancialObject().getFinancialObjectSubTypeCode());
087            }
088            return false;
089        }
090    
091    
092        /**
093         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForAccumDeprGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment)
094         */
095        public boolean isPaymentEligibleForAccumDeprGLPosting(AssetPayment assetPayment) {
096            KualiDecimal accumlatedDepreciationAmount = assetPayment.getAccumulatedPrimaryDepreciationAmount();
097            return accumlatedDepreciationAmount == null ? false : !accumlatedDepreciationAmount.isZero();
098        }
099    
100        /**
101         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForCapitalizationGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment)
102         */
103        public boolean isPaymentEligibleForCapitalizationGLPosting(AssetPayment assetPayment) {
104            KualiDecimal accountChargeAmount = assetPayment.getAccountChargeAmount();
105            return accountChargeAmount == null ? false : !accountChargeAmount.isZero();
106        }
107    
108        /**
109         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForOffsetGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment)
110         */
111        public boolean isPaymentEligibleForOffsetGLPosting(AssetPayment assetPayment) {
112            KualiDecimal accountChargeAmount = assetPayment.getAccountChargeAmount();
113            if (assetPayment.getAccumulatedPrimaryDepreciationAmount() == null) {
114                assetPayment.setAccumulatedPrimaryDepreciationAmount(KualiDecimal.ZERO);
115            }
116            KualiDecimal accumlatedDepreciationAmount = assetPayment.getAccumulatedPrimaryDepreciationAmount();
117            return accountChargeAmount == null ? false : !accountChargeAmount.subtract(accumlatedDepreciationAmount).isZero();
118        }
119    
120        /**
121         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentFinancialObjectActive(org.kuali.kfs.module.cam.businessobject.AssetPayment)
122         */
123        public boolean isPaymentFinancialObjectActive(AssetPayment assetPayment) {
124            ObjectCode financialObjectCode = new ObjectCode();
125            financialObjectCode.setUniversityFiscalYear(getUniversityDateService().getCurrentFiscalYear());
126            financialObjectCode.setChartOfAccountsCode(assetPayment.getChartOfAccountsCode());
127            financialObjectCode.setFinancialObjectCode(assetPayment.getFinancialObjectCode());
128            financialObjectCode = (ObjectCode) getBusinessObjectService().retrieve(financialObjectCode);
129            if (ObjectUtils.isNotNull(financialObjectCode)) {
130                return financialObjectCode.isActive();
131            }
132            return false;
133        }
134    
135    
136        /**
137         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#processApprovedAssetPayment(org.kuali.kfs.module.cam.document.AssetPaymentDocument)
138         */
139        public void processApprovedAssetPayment(AssetPaymentDocument document) {
140            // Creating new asset payment records
141            processPayments(document);
142        }
143    
144        
145        /**
146         * Creates a new asset payment record for each new asset payment detail record and then save them
147         * 
148         * @param document
149         */
150        protected void processPayments(AssetPaymentDocument document) {
151            List<AssetPaymentDetail> assetPaymentDetailLines = document.getSourceAccountingLines();
152            List<AssetPaymentAssetDetail> assetPaymentAssetDetails = document.getAssetPaymentAssetDetail();
153            List<PersistableBusinessObject> assetPayments = new ArrayList<PersistableBusinessObject>();
154            Integer maxSequenceNo = new Integer(0);
155    
156            //instantiate asset payment distributor
157            AssetPaymentDistributor paymentDistributor = document.getAssetPaymentDistributor();
158            
159            // Calculating the asset payments distributions for each individual asset on the list
160            Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> assetPaymentDistributionMap = paymentDistributor.getAssetPaymentDistributions();
161    
162            try {
163                // Creating a new payment record for each asset that has payments.
164                for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) {
165    
166                    maxSequenceNo = getMaxSequenceNumber(assetPaymentAssetDetail.getCapitalAssetNumber());
167    
168                    KualiDecimal totalAmount = KualiDecimal.ZERO;
169                    for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetailLines) {
170    
171                        // Retrieve the asset payment from the distribution map
172                        KualiDecimal amount = assetPaymentDistributionMap.get(assetPaymentDetail.getAssetPaymentDetailKey()).get(assetPaymentAssetDetail);
173                        totalAmount = totalAmount.add(amount);
174    
175                        AssetPayment assetPayment = new AssetPayment(assetPaymentDetail);
176                        assetPayment.setCapitalAssetNumber(assetPaymentAssetDetail.getCapitalAssetNumber());
177                        assetPayment.setTransferPaymentCode(CamsConstants.AssetPayment.TRANSFER_PAYMENT_CODE_N);
178                        assetPayment.setPaymentSequenceNumber(++maxSequenceNo);
179                        if (StringUtils.isBlank(assetPayment.getDocumentNumber())) {
180                            assetPayment.setDocumentNumber(document.getDocumentNumber());
181                        }
182                        assetPayment.setAccountChargeAmount(amount);
183    
184                        KualiDecimal baseAmount = KualiDecimal.ZERO;
185    
186                        // If the object sub type is not in the list of federally owned object sub types, then...
187                        ObjectCode objectCode = this.getObjectCodeService().getByPrimaryId(assetPaymentDetail.getPostingYear(), assetPaymentDetail.getChartOfAccountsCode(), assetPaymentDetail.getFinancialObjectCode());
188    
189                        // Depreciation Base Amount will be assigned to each payment only when the object code's sub type code is not a
190                        // federally owned one
191                        if (!this.isNonDepreciableFederallyOwnedObjSubType(objectCode.getFinancialObjectSubTypeCode())) {
192                            baseAmount = baseAmount.add(amount);
193                        }
194                        assetPayment.setPrimaryDepreciationBaseAmount(baseAmount);
195    
196                        // Resetting each period field its value with nulls
197                        this.adjustPaymentAmounts(assetPayment, false, true);
198    
199                        // add new payment
200                        assetPayments.add(assetPayment);
201                    }
202                    // *********************BEGIN - Updating Asset ***********************************************************
203                    // Retrieving the asset that will have its cost updated
204                    HashMap<String, Long> keys = new HashMap<String, Long>();
205                    keys.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, assetPaymentAssetDetail.getCapitalAssetNumber());
206                    Asset asset = (Asset) getBusinessObjectService().findByPrimaryKey(Asset.class, keys);
207    
208                    // Set previous total cost 
209                    if (assetPaymentAssetDetail.getPreviousTotalCostAmount() == null) {
210                        assetPaymentAssetDetail.setPreviousTotalCostAmount(new KualiDecimal(0));
211                    }
212                    
213                    // Setting the asset's new cost.
214                    asset.setTotalCostAmount(assetPaymentAssetDetail.getPreviousTotalCostAmount().add(totalAmount));
215    
216                    // Setting the asset's financial object sub-type Code. Only when the asset doesn't have one.
217                    if (asset.getFinancialObjectSubTypeCode() == null || asset.getFinancialObjectSubTypeCode().trim().equals("")) {
218                        asset.setFinancialObjectSubTypeCode(assetPaymentDetailLines.get(0).getObjectCode().getFinancialObjectSubTypeCode());
219                    }
220                    // Saving changes
221                    getBusinessObjectService().save(asset);
222                    // *********************END - Updating Asset ***********************************************************
223                }
224            }
225            catch (Exception e) {
226                throw new RuntimeException(e);
227            }
228            // Finally, saving all the asset payment records.
229            this.getBusinessObjectService().save(assetPayments);
230        }
231    
232        
233        /**
234         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#adjustPaymentAmounts(org.kuali.kfs.module.cam.businessobject.AssetPayment,
235         *      boolean, boolean)
236         */
237        public void adjustPaymentAmounts(AssetPayment assetPayment, boolean reverseAmount, boolean nullPeriodDepreciation) throws IllegalAccessException, InvocationTargetException {
238            LOG.debug("Starting - adjustAmounts() ");
239            PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(AssetPayment.class);
240            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
241                Method readMethod = propertyDescriptor.getReadMethod();
242                if (readMethod != null && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
243                    KualiDecimal amount = (KualiDecimal) readMethod.invoke(assetPayment);
244                    Method writeMethod = propertyDescriptor.getWriteMethod();
245                    if (writeMethod != null && amount != null) {
246                        // Reset periodic depreciation expenses
247                        if (nullPeriodDepreciation && Pattern.matches(CamsConstants.SET_PERIOD_DEPRECIATION_AMOUNT_REGEX, writeMethod.getName().toLowerCase())) {
248                            Object[] nullVal = new Object[] { null };
249                            writeMethod.invoke(assetPayment, nullVal);
250                        }
251                        else if (reverseAmount) {
252                            // reverse the amounts
253                            writeMethod.invoke(assetPayment, (amount.negated()));
254                        }
255                    }
256    
257                }
258            }
259            LOG.debug("Finished - adjustAmounts()");
260        }
261    
262    
263        /**
264         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#isPaymentEligibleForGLPosting(org.kuali.kfs.module.cam.businessobject.AssetPayment)
265         */
266        public boolean isPaymentEligibleForGLPosting(AssetPayment assetPayment) {
267            // Transfer payment code flag is not Y
268            boolean isEligible = !CamsConstants.AssetPayment.TRANSFER_PAYMENT_CODE_Y.equals(assetPayment.getTransferPaymentCode());
269            // Financial object code is currently active
270            isEligible &= isPaymentFinancialObjectActive(assetPayment);
271            // Payment is not federally funded
272            isEligible &= !isPaymentFederalOwned(assetPayment);
273            return isEligible;
274        }
275    
276        /**
277         * Checks if object sub type is a non-depreciable federally owned object sub type
278         * 
279         * @param string objectSubType2
280         * @return true if is NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES
281         */
282        public boolean isNonDepreciableFederallyOwnedObjSubType(String objectSubType) {
283            List<String> federallyOwnedObjectSubTypes = new ArrayList<String>();
284            if (this.getParameterService().parameterExists(KfsParameterConstants.CAPITAL_ASSETS_BATCH.class, CamsConstants.Parameters.NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES)) {
285                federallyOwnedObjectSubTypes = this.getParameterService().getParameterValues(KfsParameterConstants.CAPITAL_ASSETS_BATCH.class, CamsConstants.Parameters.NON_DEPRECIABLE_FEDERALLY_OWNED_OBJECT_SUB_TYPES);
286            }
287            return federallyOwnedObjectSubTypes.contains(objectSubType);
288        }
289    
290        /**
291         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#extractPostedDatePeriod(org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail)
292         */
293        public boolean extractPostedDatePeriod(AssetPaymentDetail assetPaymentDetail) {
294            boolean valid = true;
295            Map<String, Object> primaryKeys = new HashMap<String, Object>();
296            primaryKeys.put(KFSPropertyConstants.UNIVERSITY_DATE, assetPaymentDetail.getExpenditureFinancialDocumentPostedDate());
297            UniversityDate universityDate = (UniversityDate) businessObjectService.findByPrimaryKey(UniversityDate.class, primaryKeys);
298            if (universityDate != null) {
299                assetPaymentDetail.setPostingYear(universityDate.getUniversityFiscalYear());
300                assetPaymentDetail.setPostingPeriodCode(universityDate.getUniversityFiscalAccountingPeriod());
301                return true;
302            }
303            else {
304                return false;
305            }
306        }
307    
308        /**
309         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#getAssetPaymentDetailQuantity(org.kuali.kfs.module.cam.businessobject.AssetGlobal)
310         */
311        public Integer getAssetPaymentDetailQuantity(AssetGlobal assetGlobal) {
312            Integer assetPaymentDetailQuantity = 0;
313            for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
314                assetPaymentDetailQuantity++;
315            }
316            return assetPaymentDetailQuantity;
317        }
318    
319    
320        /**
321         * @see org.kuali.kfs.module.cam.document.service.AssetPaymentService#validateAssets(java.lang.String,
322         *      org.kuali.kfs.module.cam.businessobject.Asset)
323         */
324        public boolean validateAssets(String errorPath, Asset asset) {
325            boolean valid = true;
326    
327            // Validating the asset is a capital asset
328            if (!this.getAssetService().isCapitalAsset(asset)) {
329                GlobalVariables.getMessageMap().putError(errorPath, CamsKeyConstants.Payment.ERROR_NON_CAPITAL_ASSET, asset.getCapitalAssetNumber().toString());
330                valid &= false;
331            }
332    
333            // Validating the asset hasn't been retired
334            if (this.getAssetService().isAssetRetired(asset)) {
335                GlobalVariables.getMessageMap().putError(errorPath, CamsKeyConstants.Retirement.ERROR_NON_ACTIVE_ASSET_RETIREMENT, asset.getCapitalAssetNumber().toString());
336                valid &= false;
337            }
338            return valid;
339        }
340    
341    
342        /**
343         * This method determines whether or not an asset has different object sub type codes in its documents.
344         * 
345         * @return true when the asset has payments with object codes that point to different object sub type codes
346         */
347        public boolean hasDifferentObjectSubTypes(AssetPaymentDocument document) {
348            List<String> subTypes = new ArrayList<String>();
349            subTypes = SpringContext.getBean(ParameterService.class).getParameterValues(Asset.class, CamsConstants.Parameters.OBJECT_SUB_TYPE_GROUPS);
350    
351            List<AssetPaymentDetail> assetPaymentDetails = document.getSourceAccountingLines();
352            List<String> validObjectSubTypes = new ArrayList<String>();
353    
354            String objectSubTypeCode = null;
355    
356            /*
357             * Expected system parameter elements (object sub types). [BD,BF] [CM,CF,CO] [UC,UF,UO] [LI,LF]
358             */
359    
360            // Getting the list of object sub type codes from the asset payments on the jsp.
361            List<String> objectSubTypeList = new ArrayList<String>();
362            for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetails) {
363                assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.OBJECT_CODE);
364                if (ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) {
365                    return false;
366                }
367                objectSubTypeList.add(assetPaymentDetail.getObjectCode().getFinancialObjectSubTypeCode());
368            }
369    
370            if (!SpringContext.getBean(AssetService.class).isObjectSubTypeCompatible(objectSubTypeList)) {
371                return true;
372            }
373    
374            List<AssetPaymentAssetDetail> assetPaymentAssetDetails = document.getAssetPaymentAssetDetail();
375            for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) {
376                assetPaymentAssetDetail.getAsset().refreshReferenceObject(CamsPropertyConstants.Asset.ASSET_PAYMENTS);
377                List<AssetPayment> assetPayments = assetPaymentAssetDetail.getAsset().getAssetPayments();
378    
379                // Comparing against the already approved asset payments
380                if (!assetPayments.isEmpty()) {
381                    for (AssetPayment assetPayment : assetPayments) {
382                        String paymentSubObjectType = assetPayment.getFinancialObject().getFinancialObjectSubTypeCode();
383    
384                        validObjectSubTypes = new ArrayList<String>();
385                        for (String subType : subTypes) {
386                            validObjectSubTypes = Arrays.asList(StringUtils.split(subType, ","));
387                            if (validObjectSubTypes.contains(paymentSubObjectType)) {
388                                break;
389                            }
390                            validObjectSubTypes = new ArrayList<String>();
391                        }
392                        if (validObjectSubTypes.isEmpty())
393                            validObjectSubTypes.add(paymentSubObjectType);
394    
395                        // Comparing the same asset payment document
396                        for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetails) {
397                            if (!validObjectSubTypes.contains(assetPaymentDetail.getObjectCode().getFinancialObjectSubTypeCode())) {
398                                // Differences where found.
399                                return true;
400                            }
401                        }
402                    }
403                }
404            }
405            // If none object sub types are different...
406            return false;
407        }
408    
409        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
410            this.businessObjectService = businessObjectService;
411        }
412    
413        public BusinessObjectService getBusinessObjectService() {
414            return businessObjectService;
415        }
416    
417        public ParameterService getParameterService() {
418            return parameterService;
419        }
420    
421    
422        public void setParameterService(ParameterService parameterService) {
423            this.parameterService = parameterService;
424        }
425    
426    
427        public AssetPaymentDao getAssetPaymentDao() {
428            return assetPaymentDao;
429        }
430    
431    
432        public void setAssetPaymentDao(AssetPaymentDao assetPaymentDao) {
433            this.assetPaymentDao = assetPaymentDao;
434        }
435    
436        public ObjectCodeService getObjectCodeService() {
437            return objectCodeService;
438        }
439    
440        public void setObjectCodeService(ObjectCodeService objectCodeService) {
441            this.objectCodeService = objectCodeService;
442        }
443    
444        public UniversityDateService getUniversityDateService() {
445            return universityDateService;
446        }
447    
448        public void setUniversityDateService(UniversityDateService universityDateService) {
449            this.universityDateService = universityDateService;
450        }
451    
452        public AssetRetirementService getAssetRetirementService() {
453            return assetRetirementService;
454        }
455    
456        public void setAssetRetirementService(AssetRetirementService assetRetirementService) {
457            this.assetRetirementService = assetRetirementService;
458        }
459    
460        public AssetService getAssetService() {
461            return assetService;
462        }
463    
464        public void setAssetService(AssetService assetService) {
465            this.assetService = assetService;
466        }
467    
468    }