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.purap.util;
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    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.commons.lang.builder.ToStringBuilder;
026    import org.apache.commons.lang.enums.Enum;
027    import org.apache.log4j.Logger;
028    import org.kuali.kfs.module.purap.PurapConstants;
029    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
030    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
031    import org.kuali.kfs.module.purap.businessobject.ReceivingThreshold;
032    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
033    import org.kuali.kfs.module.purap.document.service.ThresholdService;
034    import org.kuali.kfs.module.purap.service.PurapAccountingService;
035    import org.kuali.kfs.sys.KFSPropertyConstants;
036    import org.kuali.kfs.sys.context.SpringContext;
037    import org.kuali.rice.kns.util.KualiDecimal;
038    
039    /**
040     * A helper class to decide whether to set the receiving document required flag for a purchasing document or not.
041     */
042    public class ThresholdHelper {
043    
044        ////////////////////////////////////////////////////////////////////////
045        //CLASS VARIABLES
046        ////////////////////////////////////////////////////////////////////////
047        private static Logger LOG = Logger.getLogger(ThresholdHelper.class);
048        
049        public static final ThresholdCriteria CHART = new ThresholdCriteria("CHART");
050        public static final ThresholdCriteria CHART_AND_ACCOUNTTYPE = new ThresholdCriteria("CHART_AND_ACCOUNT-TYPE");
051        public static final ThresholdCriteria CHART_AND_SUBFUND = new ThresholdCriteria("CHART_AND_SUB-FUND");
052        public static final ThresholdCriteria CHART_AND_COMMODITYCODE = new ThresholdCriteria("CHART_AND_COMMODITY-CODE");
053        public static final ThresholdCriteria CHART_AND_OBJECTCODE = new ThresholdCriteria("CHART_AND_OBJECT-CODE");
054        public static final ThresholdCriteria CHART_AND_ORGANIZATIONCODE = new ThresholdCriteria("CHART_AND_ORGANIZATION-CODE");
055        public static final ThresholdCriteria CHART_AND_VENDOR = new ThresholdCriteria("CHART_AND_VENDOR");
056        
057        ////////////////////////////////////////////////////////////////////////
058        //INSTANCE VARIABLES
059        ////////////////////////////////////////////////////////////////////////
060        private PurapAccountingService purapAccountingService;
061        private ThresholdService thresholdService;
062        
063        private List<ThresholdSummary> chartCodeSummary = new ArrayList();
064        private List<ThresholdSummary> chartCodeAndFundSummary = new ArrayList();
065        private List<ThresholdSummary> chartCodeAndSubFundSummary = new ArrayList();
066        private List<ThresholdSummary> chartCodeAndCommodityCodeSummary = new ArrayList();
067        private List<ThresholdSummary> chartCodeAndObjectCodeSummary = new ArrayList();
068        private List<ThresholdSummary> chartCodeAndOrgCodeSummary = new ArrayList();
069        private List<ThresholdSummary> chartCodeAndVendorSummary = new ArrayList();
070        
071        private ThresholdSummary thresholdSummary;
072        private ReceivingThreshold receivingThreshold;
073    
074        private boolean allItemsNonQty;
075        
076        public ThresholdHelper(PurchaseOrderDocument document){
077            purapAccountingService = SpringContext.getBean(PurapAccountingService.class);
078            thresholdService = SpringContext.getBean(ThresholdService.class);
079            setupForThresholdCheck(document);
080        }
081        
082        private void setupForThresholdCheck(PurchaseOrderDocument document){
083            
084            allItemsNonQty = checkForNonQtyItems(document);
085            
086            if (allItemsNonQty){
087                return;
088            }
089            
090            List<SummaryAccount> accounts = purapAccountingService.generateSummaryAccounts(document);
091            
092            if (accounts != null){
093    
094                for (SummaryAccount account : accounts) {
095                    
096                    updateThresholdSummary(CHART,account,null);
097                    updateThresholdSummary(CHART_AND_ACCOUNTTYPE,account,null);
098                    updateThresholdSummary(CHART_AND_SUBFUND,account,null);
099                    updateThresholdSummary(CHART_AND_OBJECTCODE,account,null);
100                    updateThresholdSummary(CHART_AND_ORGANIZATIONCODE,account,document.getTotalDollarAmount());
101                    
102                    processVendorForThresholdSummary(account,
103                                                     document.getVendorHeaderGeneratedIdentifier().toString(),
104                                                     document.getVendorDetailAssignedIdentifier().toString());
105                    
106                }
107            }
108            
109            processCommodityCodeForThreshold(document.getItems());
110            
111        }
112        
113        private boolean checkForNonQtyItems(PurchaseOrderDocument document){
114            List<PurchaseOrderItem> items = document.getItems();
115            
116            for (int i = 0; i < items.size(); i++) {
117                if (!items.get(i).getItemType().isAdditionalChargeIndicator() &&
118                    !StringUtils.equals(items.get(i).getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_SERVICE_CODE)){
119                    return false;
120                }
121            }
122            return true;
123        }
124        
125        private void updateThresholdSummary(ThresholdCriteria thresholdCriteria,
126                                            SummaryAccount account,
127                                            KualiDecimal documentAmount){
128            
129            if (thresholdCriteria != CHART_AND_COMMODITYCODE && 
130                thresholdCriteria != CHART_AND_VENDOR){
131                
132                ThresholdSummary thresholdSummary = new ThresholdSummary(thresholdCriteria);
133                thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE,
134                                             account.getAccount().getChartOfAccountsCode());
135                
136                if (thresholdCriteria == CHART_AND_ACCOUNTTYPE){
137                    account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
138                    if (StringUtils.isEmpty(account.getAccount().getAccount().getAccountTypeCode())){
139                        return;
140                    }
141                    thresholdSummary.setProperty(ThresholdField.ACCOUNT_TYPE_CODE,
142                                                 account.getAccount().getAccount().getAccountTypeCode());
143                    
144                }else if (thresholdCriteria == CHART_AND_SUBFUND){
145                    account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
146                    if (StringUtils.isEmpty(account.getAccount().getAccount().getSubFundGroupCode())){
147                        return;
148                    }
149                    thresholdSummary.setProperty(ThresholdField.SUBFUND_GROUP_CODE,
150                                                 account.getAccount().getAccount().getSubFundGroupCode());
151                }else if (thresholdCriteria == CHART_AND_OBJECTCODE){
152                    if (StringUtils.isEmpty(account.getAccount().getFinancialObjectCode())){
153                        return;
154                    }
155                    thresholdSummary.setProperty(ThresholdField.FINANCIAL_OBJECT_CODE,
156                                                 account.getAccount().getFinancialObjectCode());
157                }else if (thresholdCriteria == CHART_AND_ORGANIZATIONCODE){
158                    account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
159                    if (StringUtils.isEmpty(account.getAccount().getAccount().getOrganizationCode())){
160                        return;
161                    }
162                    thresholdSummary.setProperty(ThresholdField.ORGANIZATION_CODE,
163                                                 account.getAccount().getAccount().getOrganizationCode());
164                    thresholdSummary.addTotalAmount(documentAmount);
165                }
166                
167                if (thresholdCriteria != CHART_AND_ORGANIZATIONCODE){
168                    thresholdSummary.addTotalAmount(account.getAccount().getAmount());
169                }
170                addToSummaryList(thresholdSummary);
171            }
172        }
173        
174        private void addToSummaryList(ThresholdSummary thresholdSummary){
175            
176            List<ThresholdSummary> summaryList = getThresholdSummaryCollection(thresholdSummary.getThresholdCriteria());
177            
178            boolean matchFound = false;
179            for (int i = 0; i < summaryList.size(); i++) {
180                if (thresholdSummary.equals(summaryList.get(i))){
181                    summaryList.get(i).addTotalAmount(thresholdSummary.getTotalAmount());
182                    matchFound = true;
183                    break;
184                }
185            }
186            
187            if (!matchFound){
188                summaryList.add(thresholdSummary);
189            }
190        }
191        
192        private List<ThresholdSummary> getThresholdSummaryCollection(ThresholdCriteria thresholdCriteria){
193            
194            if (thresholdCriteria == CHART){
195                return chartCodeSummary;
196            }else if (thresholdCriteria == CHART_AND_ACCOUNTTYPE){
197                return chartCodeAndFundSummary;
198            }else if (thresholdCriteria == CHART_AND_SUBFUND){
199                return chartCodeAndSubFundSummary;
200            }else if (thresholdCriteria == CHART_AND_COMMODITYCODE){
201                return chartCodeAndCommodityCodeSummary;
202            }else if (thresholdCriteria == CHART_AND_OBJECTCODE){
203                return chartCodeAndObjectCodeSummary;
204            }else if (thresholdCriteria == CHART_AND_ORGANIZATIONCODE){
205                return chartCodeAndOrgCodeSummary;
206            }else if (thresholdCriteria == CHART_AND_VENDOR){
207                return chartCodeAndVendorSummary;
208            }
209            
210            throw new RuntimeException("Invalid ThresholdCriteria Enum - " + thresholdCriteria);
211        }
212        
213        private void processVendorForThresholdSummary(SummaryAccount account,
214                                                      String vendorHeaderGeneratedIdentifier,
215                                                      String vendorDetailAssignedIdentifier){
216            
217            ThresholdSummary thresholdSummary = new ThresholdSummary(CHART_AND_VENDOR);
218            thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE,account.getAccount().getChartOfAccountsCode());
219            thresholdSummary.setProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID,vendorHeaderGeneratedIdentifier);
220            thresholdSummary.setProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID,vendorDetailAssignedIdentifier);
221            thresholdSummary.addTotalAmount(account.getAccount().getAmount());
222            
223            addToSummaryList(thresholdSummary);
224            
225        }
226        
227        private void processCommodityCodeForThreshold(List<PurchaseOrderItem> items){
228            if (items != null){
229                for (PurchaseOrderItem item : items) {
230                    if (item.isItemActiveIndicator()){
231                        List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines();
232                        for (int i = 0; i < accountingLines.size(); i++) {
233                            if (StringUtils.isNotBlank(item.getPurchasingCommodityCode())){
234                                ThresholdSummary thresholdSummary = new ThresholdSummary(CHART_AND_COMMODITYCODE);
235                                thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE,accountingLines.get(i).getChartOfAccountsCode());
236                                thresholdSummary.setProperty(ThresholdField.COMMODITY_CODE,item.getPurchasingCommodityCode());
237                                thresholdSummary.addTotalAmount(item.getExtendedPrice());
238                                addToSummaryList(thresholdSummary);
239                            }
240                        }
241                    }
242                }
243            }
244        }
245     
246        public boolean isReceivingDocumentRequired() {
247            
248            // From spec - 7. If all the line items are non-quantity do not do the threshold check. 
249            if (allItemsNonQty){
250                return false;
251            }
252            
253            for (ThresholdCriteria thresholdEnum : ThresholdCriteria.getEnumList()) {
254                boolean result = isReceivingDocumentRequired(thresholdEnum);
255                if (result){
256                    return true;
257                }
258            }
259            
260            return false;
261        }
262        
263        /**
264         * This method is public since it's required in the ThresholdTest class. To know the receiving required doc status for a PO,
265         * it's always better to call isReceivingDocumentRequired() instead of this method.
266         */
267        public boolean isReceivingDocumentRequired(ThresholdCriteria thresholdEnum) {
268            
269            List<ThresholdSummary> summaryList = getThresholdSummaryCollection(thresholdEnum);
270            
271            if (summaryList != null){
272                for (ThresholdSummary summary : summaryList) {
273                    Collection collection = null;
274                    
275                    if (thresholdEnum == CHART){
276                        collection = thresholdService.findByChart(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE));
277                    }else if (thresholdEnum == CHART_AND_ACCOUNTTYPE){
278                        collection = thresholdService.findByChartAndFund(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
279                                                                         summary.getProperty(ThresholdField.ACCOUNT_TYPE_CODE));
280                    }else if (thresholdEnum == CHART_AND_SUBFUND){
281                        collection = thresholdService.findByChartAndSubFund(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
282                                                                            summary.getProperty(ThresholdField.SUBFUND_GROUP_CODE));
283                    }else if (thresholdEnum == CHART_AND_COMMODITYCODE){
284                        collection = thresholdService.findByChartAndCommodity(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
285                                                                            summary.getProperty(ThresholdField.COMMODITY_CODE));
286                    }else if (thresholdEnum == CHART_AND_OBJECTCODE){
287                        collection = thresholdService.findByChartAndObjectCode(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
288                                                                               summary.getProperty(ThresholdField.FINANCIAL_OBJECT_CODE));
289                    }else if (thresholdEnum == CHART_AND_ORGANIZATIONCODE){
290                        collection = thresholdService.findByChartAndOrg(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
291                                                                        summary.getProperty(ThresholdField.ORGANIZATION_CODE));
292                    }else if (thresholdEnum == CHART_AND_VENDOR){
293                        collection = thresholdService.findByChartAndVendor(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE),
294                                                                           summary.getProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID),
295                                                                           summary.getProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID));
296                    }
297                    
298                    if (collection != null){
299                        for (ReceivingThreshold threshold :(List<ReceivingThreshold>) collection){
300                            if (threshold.getThresholdAmount() == null || threshold.getThresholdAmount().isLessThan(summary.getTotalAmount())){
301                                thresholdSummary = summary;
302                                receivingThreshold = threshold;
303                                return true;
304                            }
305                        }
306                    }
307                }
308            }
309            
310            return false;
311            
312        }
313        
314        public ThresholdSummary getThresholdSummary() {
315            return thresholdSummary;
316        }
317    
318        public ReceivingThreshold getReceivingThreshold() {
319            return receivingThreshold;
320        }    
321    
322        public class ThresholdSummary {
323            
324            private ThresholdCriteria thresholdCriteria;
325            private Map<ThresholdField,String> property2Value = new HashMap<ThresholdField,String>(); 
326            private KualiDecimal totalAmount = KualiDecimal.ZERO;
327            
328            ThresholdSummary(ThresholdCriteria thresholdCriteria){
329                this.thresholdCriteria = thresholdCriteria;
330            }
331            
332            void setProperty(ThresholdField thresholdField,
333                             String fieldValue){
334                if (!isValidProperty(thresholdField)){
335                    throw new RuntimeException("Property[" + thresholdField + "] not allowed for the threshold criteria[" + thresholdCriteria + "]");
336                }
337                
338                property2Value.put(thresholdField,fieldValue);
339            }
340            
341            String getProperty(ThresholdField thresholdEnum){
342                return property2Value.get(thresholdEnum);
343            }
344    
345            public ThresholdCriteria getThresholdCriteria() {
346                return thresholdCriteria;
347            }
348    
349            public String getThresholdCriteriaName() {
350                return thresholdCriteria.getName();
351            }
352    
353            public KualiDecimal getTotalAmount() {
354                return totalAmount;
355            }
356    
357            void addTotalAmount(KualiDecimal totalAmount) {
358                if (totalAmount != null){
359                    this.totalAmount = this.totalAmount.add(totalAmount);
360                }
361            }
362            
363            boolean isValidProperty(ThresholdField thresholdField){
364                
365                if (getThresholdCriteria() == CHART && 
366                    ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField){
367                    return true;
368                }else if ((getThresholdCriteria() == CHART_AND_ACCOUNTTYPE) && 
369                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
370                           ThresholdField.ACCOUNT_TYPE_CODE == thresholdField)){
371                    return true;
372                }else if ((getThresholdCriteria() == CHART_AND_SUBFUND) && 
373                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
374                           ThresholdField.SUBFUND_GROUP_CODE == thresholdField)){
375                    return true;
376                }else if ((getThresholdCriteria() == CHART_AND_COMMODITYCODE) && 
377                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
378                           ThresholdField.COMMODITY_CODE == thresholdField)){
379                    return true;
380                }else if ((getThresholdCriteria() == CHART_AND_OBJECTCODE) && 
381                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
382                           ThresholdField.FINANCIAL_OBJECT_CODE == thresholdField)){
383                    return true;
384                }else if ((getThresholdCriteria() == CHART_AND_ORGANIZATIONCODE) && 
385                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
386                           ThresholdField.ORGANIZATION_CODE == thresholdField)){
387                   return true;
388                }else if ((getThresholdCriteria() == CHART_AND_VENDOR) && 
389                          (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField ||
390                           ThresholdField.VENDOR_HEADER_GENERATED_ID == thresholdField || 
391                           ThresholdField.VENDOR_DETAIL_ASSIGNED_ID == thresholdField)){
392                   return true;
393                }
394                
395                return false;
396            }
397            
398            @Override
399            public boolean equals(Object obj){
400                
401                if (obj != null){
402                    
403                    if (!(obj instanceof ThresholdSummary)){
404                      return false;
405                    }   
406                    
407                    ThresholdSummary thresholdItem = (ThresholdSummary)obj;
408                    
409                    if (getThresholdCriteria() == thresholdItem.getThresholdCriteria()){
410                        
411                        if (getThresholdCriteria() == CHART){
412                            
413                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
414                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE))){
415                                return true;
416                            }
417                            
418                        }else if (getThresholdCriteria() == CHART_AND_ACCOUNTTYPE){
419                            
420                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
421                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
422                                StringUtils.equals(property2Value.get(ThresholdField.ACCOUNT_TYPE_CODE),
423                                                   thresholdItem.getProperty(ThresholdField.ACCOUNT_TYPE_CODE))){
424                                return true;
425                            }
426                            
427                        }else if (getThresholdCriteria() == CHART_AND_SUBFUND){
428                            
429                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
430                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
431                                StringUtils.equals(property2Value.get(ThresholdField.SUBFUND_GROUP_CODE),
432                                                   thresholdItem.getProperty(ThresholdField.SUBFUND_GROUP_CODE))){
433                                return true;
434                            }
435                            
436                        }else if (getThresholdCriteria() == CHART_AND_COMMODITYCODE){
437                            
438                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
439                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
440                                StringUtils.equals(property2Value.get(ThresholdField.COMMODITY_CODE),
441                                                   thresholdItem.getProperty(ThresholdField.COMMODITY_CODE))){
442                                return true;
443                            }
444                            
445                        }else if (getThresholdCriteria() == CHART_AND_OBJECTCODE){
446                            
447                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
448                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
449                                StringUtils.equals(property2Value.get(ThresholdField.FINANCIAL_OBJECT_CODE),
450                                                   thresholdItem.getProperty(ThresholdField.FINANCIAL_OBJECT_CODE))){
451                                return true;
452                            }
453                            
454                        }else if (getThresholdCriteria() == CHART_AND_ORGANIZATIONCODE){
455                            
456                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
457                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
458                                StringUtils.equals(property2Value.get(ThresholdField.ORGANIZATION_CODE),
459                                                   thresholdItem.getProperty(ThresholdField.ORGANIZATION_CODE))){
460                                return true;
461                            }
462                            
463                        }else if (getThresholdCriteria() == CHART_AND_VENDOR){
464                            
465                            if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE),
466                                                   thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) &&
467                                StringUtils.equals(property2Value.get(ThresholdField.VENDOR_HEADER_GENERATED_ID),
468                                                   thresholdItem.getProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID)) &&
469                                StringUtils.equals(property2Value.get(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID),
470                                                   thresholdItem.getProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID))){
471                                return true;
472                            }
473                            
474                        }
475                    }
476                }
477                return false;
478            }
479            
480            @Override
481            public String toString(){
482                ToStringBuilder stringBuilder = new ToStringBuilder(this);
483                stringBuilder.append("ThresholdCriteria",getThresholdCriteria().getName());
484                stringBuilder.append("Amount",getTotalAmount());
485                stringBuilder.append("Field2Values",property2Value);
486                
487                return stringBuilder.toString();
488            }
489        }
490      
491    }
492    
493    final class ThresholdCriteria extends Enum {
494    
495        ThresholdCriteria(String name) {
496            super(name);
497        }
498      
499        public static List<ThresholdCriteria> getEnumList() {
500            return getEnumList(ThresholdCriteria.class);
501        }
502    }
503