1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.ole.select.document.service.impl;
17
18 import org.kuali.ole.module.purap.PurapConstants;
19 import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
20 import org.kuali.ole.module.purap.businessobject.PurApItem;
21 import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument;
22 import org.kuali.ole.module.purap.document.service.PurapService;
23 import org.kuali.ole.module.purap.service.impl.PurapAccountingServiceImpl;
24 import org.kuali.ole.module.purap.util.PurApItemUtils;
25 import org.kuali.ole.module.purap.util.PurApObjectUtils;
26 import org.kuali.ole.select.businessobject.OlePaymentRequestItem;
27 import org.kuali.ole.select.businessobject.OlePurchaseOrderItem;
28 import org.kuali.ole.select.document.service.OlePurapAccountingService;
29 import org.kuali.ole.sys.businessobject.AccountingLineBase;
30 import org.kuali.ole.sys.businessobject.SourceAccountingLine;
31 import org.kuali.ole.sys.service.NonTransactional;
32 import org.kuali.rice.core.api.util.type.KualiDecimal;
33 import org.kuali.rice.krad.util.ObjectUtils;
34 import org.kuali.ole.select.businessobject.OleInvoiceItem;
35
36 import java.math.BigDecimal;
37 import java.math.RoundingMode;
38 import java.util.*;
39
40
41
42
43
44 @NonTransactional
45 public class OlePurapAccountingServiceImpl extends PurapAccountingServiceImpl implements OlePurapAccountingService {
46 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OlePurapAccountingServiceImpl.class);
47
48 private PurapService purapService;
49
50
51 @Override
52 public void setPurapService(PurapService purapService) {
53 this.purapService = purapService;
54 }
55
56
57
58
59
60
61
62 @Override
63 public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
64 if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
65
66 KualiDecimal accountTotal = KualiDecimal.ZERO;
67 T lastAccount = null;
68
69
70 for (T account : sourceAccountingLines) {
71 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
72 BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
73 account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
74 } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNull(account.getAccountLinePercent())) {
75 KualiDecimal dollar = account.getAmount().multiply(new KualiDecimal(100));
76 BigDecimal dollarToPercent = dollar.bigDecimalValue().divide((totalAmount.bigDecimalValue()), 0, RoundingMode.FLOOR);
77 account.setAccountLinePercent(dollarToPercent);
78 } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNotNull(account.getAccountLinePercent())) {
79 account.setAmount(account.getAmount());
80 account.setAccountLinePercent(account.getAccountLinePercent());
81 } else {
82 account.setAmount(KualiDecimal.ZERO);
83 }
84 accountTotal = accountTotal.add(account.getAmount());
85 lastAccount = account;
86 }
87
88 } else {
89
90 for (T account : sourceAccountingLines) {
91 account.setAmount(KualiDecimal.ZERO);
92 }
93 }
94 }
95
96
97
98
99
100 @Override
101 public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale, Class clazz) {
102 String methodName = "generateAccountDistributionForProration()";
103 if (LOG.isDebugEnabled()) {
104 LOG.debug(methodName + " started");
105 }
106 List<PurApAccountingLine> newAccounts = new ArrayList();
107
108 if (totalAmount.isZero()) {
109 throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total.");
110 }
111
112 BigDecimal percentTotal = BigDecimal.ZERO;
113 BigDecimal totalAmountBigDecimal = totalAmount.bigDecimalValue();
114 for (SourceAccountingLine accountingLine : accounts) {
115 if (LOG.isDebugEnabled()) {
116 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + accountingLine.getAmount() + "/" + totalAmountBigDecimal);
117 }
118
119 BigDecimal pct = BigDecimal.ZERO;
120 if (accountingLine.getSourceAcountLinePercent() != null)
121 pct = accountingLine.getSourceAcountLinePercent();
122 else
123 pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE);
124 pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED);
125
126 if (LOG.isDebugEnabled()) {
127 LOG.debug(methodName + " pct = " + pct + " (trailing zeros removed)");
128 }
129
130 BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber();
131 if (lowestPossible.compareTo(pct) <= 0) {
132 PurApAccountingLine newAccountingLine;
133 newAccountingLine = null;
134
135 try {
136 newAccountingLine = (PurApAccountingLine) clazz.newInstance();
137 } catch (InstantiationException e) {
138 e.printStackTrace();
139 } catch (IllegalAccessException e) {
140 e.printStackTrace();
141 }
142
143 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine);
144 newAccountingLine.setAccountLinePercent(pct);
145 if (LOG.isDebugEnabled()) {
146 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent());
147 }
148 newAccounts.add(newAccountingLine);
149 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent());
150 if (LOG.isDebugEnabled()) {
151 LOG.debug(methodName + " total = " + percentTotal);
152 }
153 }
154 }
155
156 if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) {
157
158
159
160
161 throwRuntimeException(methodName, "Can't round properly due to number of accounts");
162 }
163
164
165 if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) {
166
167
168
169
170 BigDecimal difference = percentTotal.subtract(ONE_HUNDRED);
171 if (LOG.isDebugEnabled()) {
172 LOG.debug(methodName + " Rounding up by " + difference);
173 }
174
175 boolean foundAccountToUse = false;
176 int currentNbr = newAccounts.size() - 1;
177 while (currentNbr >= 0) {
178 PurApAccountingLine potentialSlushAccount = (PurApAccountingLine) newAccounts.get(currentNbr);
179
180 if ((difference.compareTo(potentialSlushAccount.getAccountLinePercent())) < 0) {
181
182
183 potentialSlushAccount.setAccountLinePercent(potentialSlushAccount.getAccountLinePercent().subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
184 foundAccountToUse = true;
185 break;
186 }
187 currentNbr--;
188 }
189
190 if (!foundAccountToUse) {
191
192
193
194
195 throwRuntimeException(methodName, "Can't round properly due to math calculation error");
196 }
197
198 } else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) {
199
200
201
202
203 BigDecimal difference = ONE_HUNDRED.subtract(percentTotal);
204 if (LOG.isDebugEnabled()) {
205 LOG.debug(methodName + " Rounding down by " + difference);
206 }
207 PurApAccountingLine slushAccount = (PurApAccountingLine) newAccounts.get(newAccounts.size() - 1);
208 slushAccount.setAccountLinePercent(slushAccount.getAccountLinePercent().add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
209 }
210 if (LOG.isDebugEnabled()) {
211 LOG.debug(methodName + " ended");
212 }
213 return newAccounts;
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227 @Override
228 protected List<SourceAccountingLine> generateAccountSummary(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) {
229 BigDecimal addChargeItem = BigDecimal.ZERO;
230 for (PurApItem item : items) {
231 if (!item.getItemType().isQuantityBasedGeneralLedgerIndicator() && !item.getItemTypeCode().equalsIgnoreCase(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE) && item.getItemUnitPrice() != null) {
232 addChargeItem = addChargeItem.add(item.getItemUnitPrice());
233 }
234 }
235 items = PurApItemUtils.getAboveTheLineOnly(items);
236 List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals);
237 Map<PurApAccountingLine, KualiDecimal> accountMap = new HashMap<PurApAccountingLine, KualiDecimal>();
238 Map<PurApAccountingLine, KualiDecimal> prorateMap = new HashMap<PurApAccountingLine, KualiDecimal>();
239
240 for (PurApItem currentItem : itemsToProcess) {
241 if (PurApItemUtils.checkItemActive(currentItem)) {
242 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines();
243
244
245 if (taxableOnly) {
246 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
247 if (!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)) {
248 continue;
249 }
250 }
251
252 if (!useTaxIncluded) {
253
254
255 PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem);
256 sourceAccountingLines = cloneItem.getSourceAccountingLines();
257 updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount());
258 }
259
260 for (PurApAccountingLine account : sourceAccountingLines) {
261
262
263 if (taxableOnly) {
264 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
265
266 if (!purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document)))) {
267 continue;
268 }
269 }
270
271
272 KualiDecimal total = KualiDecimal.ZERO;
273 KualiDecimal prorateTotal = KualiDecimal.ZERO;
274 if (accountMap.containsKey(account)) {
275 total = accountMap.get(account);
276 prorateTotal = prorateMap.get(account);
277 }
278
279 if (useAlternateAmount) {
280 total = total.add(account.getAlternateAmountForGLEntryCreation());
281 } else {
282 total = total.add(account.getAmount());
283 }
284 if ((currentItem instanceof OlePaymentRequestItem) &&
285 currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
286 OlePaymentRequestItem item = (OlePaymentRequestItem) currentItem;
287 prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
288 }
289 else if ((currentItem instanceof OleInvoiceItem) &&
290 currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
291 OleInvoiceItem item = (OleInvoiceItem) currentItem;
292 prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
293 }
294 else if ((currentItem instanceof OlePurchaseOrderItem) &&
295 currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
296 OlePurchaseOrderItem item = (OlePurchaseOrderItem) currentItem;
297 prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
298 }
299 accountMap.put(account, total);
300 prorateMap.put(account, prorateTotal);
301 }
302 }
303 }
304
305
306 Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator();
307 List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>();
308 for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext(); ) {
309 PurApAccountingLine accountToConvert = (PurApAccountingLine) iter.next();
310 if (accountToConvert.isEmpty()) {
311 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString();
312 LOG.error("generateAccountSummary() " + errorMessage);
313 throw new RuntimeException(errorMessage);
314 }
315 KualiDecimal sourceLineTotal = accountMap.get(accountToConvert);
316 SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine();
317 sourceLine.setAmount(sourceLineTotal);
318 if (addChargeItem.compareTo(BigDecimal.ZERO) != 0) {
319 sourceLine.setSourceAcountLinePercent(prorateMap.get(accountToConvert).bigDecimalValue().divide(addChargeItem, new Integer(6), BIG_DECIMAL_ROUNDING_MODE));
320 }
321 sourceAccounts.add(sourceLine);
322 }
323
324
325 Collections.sort(sourceAccounts, new Comparator<SourceAccountingLine>() {
326 @Override
327 public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) {
328 int compare = 0;
329 if (sal1 != null && sal2 != null) {
330 if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) {
331 compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber());
332 if (compare == 0) {
333 if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null)
334 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode());
335 }
336 }
337 }
338 return compare;
339 }
340 });
341
342 return sourceAccounts;
343 }
344
345 }