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