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