KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ofbiz > accounting > invoice > InvoiceServices


1 /*
2  * $Id: InvoiceServices.java 7251 2006-04-10 05:39:15Z jacopo $
3  *
4  * Copyright 2003-2006 The Apache Software Foundation
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7  * use this file except in compliance with the License. You may obtain a copy of
8  * the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15  * License for the specific language governing permissions and limitations
16  * under the License.
17  */

18 package org.ofbiz.accounting.invoice;
19
20 import java.math.BigDecimal JavaDoc;
21 import java.sql.Timestamp JavaDoc;
22 import java.util.*;
23
24 import javolution.util.FastMap;
25
26 import org.ofbiz.accounting.payment.BillingAccountWorker;
27 import org.ofbiz.accounting.payment.PaymentWorker;
28 import org.ofbiz.accounting.payment.PaymentGatewayServices;
29 import org.ofbiz.base.util.Debug;
30 import org.ofbiz.base.util.UtilDateTime;
31 import org.ofbiz.base.util.UtilFormatOut;
32 import org.ofbiz.base.util.UtilMisc;
33 import org.ofbiz.base.util.UtilNumber;
34 import org.ofbiz.base.util.UtilProperties;
35 import org.ofbiz.base.util.UtilValidate;
36 import org.ofbiz.entity.GenericDelegator;
37 import org.ofbiz.entity.GenericEntityException;
38 import org.ofbiz.entity.GenericValue;
39 import org.ofbiz.entity.util.EntityUtil;
40 import org.ofbiz.entity.condition.EntityCondition;
41 import org.ofbiz.entity.condition.EntityOperator;
42 import org.ofbiz.entity.condition.EntityExpr;
43 import org.ofbiz.order.order.OrderReadHelper;
44 import org.ofbiz.product.product.ProductWorker;
45 import org.ofbiz.service.DispatchContext;
46 import org.ofbiz.service.GenericServiceException;
47 import org.ofbiz.service.LocalDispatcher;
48 import org.ofbiz.service.ServiceUtil;
49
50 /**
51  * InvoiceServices - Services for creating invoices
52  *
53  * Note that throughout this file we use BigDecimal to do arithmetic. It is
54  * critical to understand the way BigDecimal works if you wish to modify the
55  * computations in this file. The most important things to keep in mind:
56  *
57  * Critically important: BigDecimal arithmetic methods like add(),
58  * multiply(), divide() do not modify the BigDecimal itself. Instead, they
59  * return a new BigDecimal. For example, to keep a running total of an
60  * amount, make sure you do this:
61  *
62  * amount = amount.add(subAmount);
63  *
64  * and not this,
65  *
66  * amount.add(subAmount);
67  *
68  * Use .setScale(scale, roundingMode) after every computation to scale and
69  * round off the decimals. Check the code to see how the scale and
70  * roundingMode are obtained and how the function is used.
71  *
72  * use .compareTo() to compare big decimals
73  *
74  * ex. (amountOne.compareTo(amountTwo) == 1)
75  * checks if amountOne is greater than amountTwo
76  *
77  * Use .signum() to test if value is negative, zero, or positive
78  *
79  * ex. (amountOne.signum() == 1)
80  * checks if the amount is a positive non-zero number
81  *
82  * Never use the .equals() function becaues it considers 2.0 not equal to 2.00 (the scale is different)
83  * Instead, use .compareTo() or .signum(), which handles scale correctly.
84  *
85  * For reference, check the official Sun Javadoc on java.math.BigDecimal.
86  *
87  * @author <a HREF="mailto:jaz@ofbiz.org">Andy Zeneski</a>
88  * @author <a HREF="mailto:sichen@opensourcestrategies.com">Si Chen</a>
89  * @author <a HREF="mailto:leon@opensourcestrategies.com">Leon Torres</a>
90  * @version $Rev: 7251 $
91  * @since 2.2
92  */

93 public class InvoiceServices {
94
95     public static String JavaDoc module = InvoiceServices.class.getName();
96
97     // set some BigDecimal properties
98
private static BigDecimal JavaDoc ZERO = new BigDecimal JavaDoc("0");
99     private static int decimals = UtilNumber.getBigDecimalScale("invoice.decimals");
100     private static int rounding = UtilNumber.getBigDecimalRoundingMode("invoice.rounding");
101     private static int taxDecimals = UtilNumber.getBigDecimalScale("salestax.calc.decimals");
102     private static int taxRounding = UtilNumber.getBigDecimalScale("salestax.rounding");
103
104     public static final String JavaDoc resource = "AccountingUiLabels";
105
106     /* Service to create an invoice for an order */
107     public static Map createInvoiceForOrder(DispatchContext dctx, Map context) {
108         GenericDelegator delegator = dctx.getDelegator();
109         LocalDispatcher dispatcher = dctx.getDispatcher();
110         GenericValue userLogin = (GenericValue) context.get("userLogin");
111         Locale locale = (Locale) context.get("locale");
112
113         if (decimals == -1 || rounding == -1) {
114             return ServiceUtil.returnError("Arithmetic properties for Invoice services not configured properly. Cannot proceed.");
115         }
116
117         String JavaDoc orderId = (String JavaDoc) context.get("orderId");
118         List billItems = (List) context.get("billItems");
119         boolean previousInvoiceFound = false;
120
121         if (billItems == null || billItems.size() == 0) {
122             Debug.logVerbose("No order items to invoice; not creating invoice; returning success", module);
123             return ServiceUtil.returnSuccess("No order items to invoice, not creating invoice.");
124         }
125
126         try {
127             GenericValue orderHeader = delegator.findByPrimaryKey("OrderHeader", UtilMisc.toMap("orderId", orderId));
128             if (orderHeader == null) {
129                 return ServiceUtil.returnError("No OrderHeader, cannot create invoice");
130             }
131
132             // get list of previous invoices for the order
133
List billedItems = delegator.findByAnd("OrderItemBilling", UtilMisc.toMap("orderId", orderId));
134             if (billedItems != null && billedItems.size() > 0) {
135                 boolean nonDigitalInvoice = false;
136                 Iterator bii = billedItems.iterator();
137                 while (bii.hasNext() && !nonDigitalInvoice) {
138                     GenericValue orderItemBilling = (GenericValue) bii.next();
139                     GenericValue invoiceItem = orderItemBilling.getRelatedOne("InvoiceItem");
140                     if (invoiceItem != null) {
141                         String JavaDoc invoiceItemType = invoiceItem.getString("invoiceItemTypeId");
142                         if (invoiceItemType != null) {
143                             if ("INV_FPROD_ITEM".equals(invoiceItemType) || "INV_PROD_FEATR_ITEM".equals(invoiceItemType)) {
144                                 nonDigitalInvoice = true;
145                             }
146                         }
147                     }
148                 }
149                 if (nonDigitalInvoice) {
150                     previousInvoiceFound = true;
151                 }
152             }
153
154             // figure out the invoice type
155
String JavaDoc invoiceType = null;
156
157             String JavaDoc orderType = orderHeader.getString("orderTypeId");
158             if (orderType.equals("SALES_ORDER")) {
159                 invoiceType = "SALES_INVOICE";
160             } else if (orderType.equals("PURCHASE_ORDER")) {
161                 invoiceType = "PURCHASE_INVOICE";
162             }
163
164             // Make an order read helper from the order
165
OrderReadHelper orh = new OrderReadHelper(orderHeader);
166
167             // get the product store
168
GenericValue productStore = delegator.findByPrimaryKey("ProductStore", UtilMisc.toMap("productStoreId", orh.getProductStoreId()));
169
170             // get the shipping adjustment mode (Y = Pro-Rate; N = First-Invoice)
171
String JavaDoc prorateShipping = productStore.getString("prorateShipping");
172             if (prorateShipping == null) {
173                 prorateShipping = "Y";
174             }
175
176             // get the billing parties
177
String JavaDoc billToCustomerPartyId = orh.getBillToParty().getString("partyId");
178             String JavaDoc billFromVendorPartyId = orh.getBillFromParty().getString("partyId");
179
180             // get some quantity totals
181
BigDecimal JavaDoc totalItemsInOrder = orh.getTotalOrderItemsQuantityBd();
182
183             // get some price totals
184
BigDecimal JavaDoc shippableAmount = orh.getShippableTotalBd(null);
185             BigDecimal JavaDoc orderSubTotal = orh.getOrderItemsSubTotalBd();
186
187             // these variables are for pro-rating order amounts across invoices, so they should not be rounded off for maximum accuracy
188
BigDecimal JavaDoc invoiceShipProRateAmount = ZERO;
189             BigDecimal JavaDoc invoiceSubTotal = ZERO;
190             BigDecimal JavaDoc invoiceQuantity = ZERO;
191
192             GenericValue billingAccount = orderHeader.getRelatedOne("BillingAccount");
193             String JavaDoc billingAccountId = billingAccount != null ? billingAccount.getString("billingAccountId") : null;
194
195             // TODO: ideally this should be the same time as when a shipment is sent and be passed in as a parameter
196
Timestamp JavaDoc invoiceDate = UtilDateTime.nowTimestamp();
197             // TODO: perhaps consider billing account net days term as well?
198
Long JavaDoc orderTermNetDays = orh.getOrderTermNetDays();
199             Timestamp JavaDoc dueDate = null;
200             if (orderTermNetDays != null) {
201                 dueDate = UtilDateTime.getDayEnd(invoiceDate, orderTermNetDays.intValue());
202             }
203             
204             // create the invoice record
205
Map createInvoiceContext = FastMap.newInstance();
206             createInvoiceContext.put("partyId", billToCustomerPartyId);
207             createInvoiceContext.put("partyIdFrom", billFromVendorPartyId);
208             createInvoiceContext.put("billingAccountId", billingAccountId);
209             createInvoiceContext.put("invoiceDate", invoiceDate);
210             createInvoiceContext.put("dueDate", dueDate);
211             createInvoiceContext.put("invoiceTypeId", invoiceType);
212             // start with INVOICE_IN_PROCESS, in the INVOICE_READY we can't change the invoice (or shouldn't be able to...)
213
createInvoiceContext.put("statusId", "INVOICE_IN_PROCESS");
214             createInvoiceContext.put("currencyUomId", orderHeader.getString("currencyUom"));
215             createInvoiceContext.put("userLogin", userLogin);
216
217             // store the invoice first
218
Map createInvoiceResult = dispatcher.runSync("createInvoice", createInvoiceContext);
219             if (ServiceUtil.isError(createInvoiceResult)) {
220                 return ServiceUtil.returnError("Error creating invoice from order", null, null, createInvoiceResult);
221             }
222             
223             // call service, not direct entity op: delegator.create(invoice);
224
String JavaDoc invoiceId = (String JavaDoc) createInvoiceResult.get("invoiceId");
225
226             // order roles to invoice roles
227
List orderRoles = orderHeader.getRelated("OrderRole");
228             if (orderRoles != null) {
229                 Iterator orderRolesIt = orderRoles.iterator();
230                 Map createInvoiceRoleContext = FastMap.newInstance();
231                 createInvoiceRoleContext.put("invoiceId", invoiceId);
232                 createInvoiceRoleContext.put("userLogin", userLogin);
233                 while (orderRolesIt.hasNext()) {
234                     GenericValue orderRole = (GenericValue)orderRolesIt.next();
235                     createInvoiceRoleContext.put("partyId", orderRole.getString("partyId"));
236                     createInvoiceRoleContext.put("roleTypeId", orderRole.getString("roleTypeId"));
237                     Map createInvoiceRoleResult = dispatcher.runSync("createInvoiceRole", createInvoiceRoleContext);
238                     if (ServiceUtil.isError(createInvoiceRoleResult)) {
239                         return ServiceUtil.returnError("Error creating invoice role from order", null, null, createInvoiceRoleResult);
240                     }
241                 }
242             }
243
244             // order terms to invoice terms. Implemented for purchase orders, although it may be useful
245
// for sales orders as well. Later it might be nice to filter OrderTerms to only copy over financial terms.
246
List orderTerms = orh.getOrderTerms();
247             createInvoiceTerms(delegator, dispatcher, invoiceId, orderTerms, userLogin);
248
249             // billing accounts
250
List billingAccountTerms = null;
251             // for billing accounts we will use related information
252
if (billingAccount != null) {
253                 // get the billing account terms
254
billingAccountTerms = billingAccount.getRelated("BillingAccountTerm");
255
256                 // set the invoice terms as defined for the billing account
257
createInvoiceTerms(delegator, dispatcher, invoiceId, billingAccountTerms, userLogin);
258
259                 // set the invoice bill_to_customer from the billing account
260
List billToRoles = billingAccount.getRelated("BillingAccountRole", UtilMisc.toMap("roleTypeId", "BILL_TO_CUSTOMER"), null);
261                 Iterator billToIter = billToRoles.iterator();
262                 while (billToIter.hasNext()) {
263                     GenericValue billToRole = (GenericValue) billToIter.next();
264                     if (!(billToRole.getString("partyId").equals(billToCustomerPartyId))) {
265                         Map createInvoiceRoleContext = UtilMisc.toMap("invoiceId", invoiceId, "partyId", billToRole.get("partyId"),
266                                                                            "roleTypeId", "BILL_TO_CUSTOMER", "userLogin", userLogin);
267                         Map createInvoiceRoleResult = dispatcher.runSync("createInvoiceRole", createInvoiceRoleContext);
268                         if (ServiceUtil.isError(createInvoiceRoleResult)) {
269                             return ServiceUtil.returnError("Error creating InvoiceRole from order", null, null, createInvoiceRoleResult);
270                         }
271                     }
272                 }
273
274                 // set the bill-to contact mech as the contact mech of the billing account
275
if (UtilValidate.isNotEmpty(billingAccount.getString("contactMechId"))) {
276                     Map createBillToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", billingAccount.getString("contactMechId"),
277                                                                        "contactMechPurposeTypeId", "BILLING_LOCATION", "userLogin", userLogin);
278                     Map createBillToContactMechResult = dispatcher.runSync("createInvoiceContactMech", createBillToContactMechContext);
279                     if (ServiceUtil.isError(createBillToContactMechResult)) {
280                         return ServiceUtil.returnError("Error creating BILLING_LOCATION InvoiceContactMech from order", null, null, createBillToContactMechResult);
281                     }
282                 }
283             } else {
284                 List billingLocations = orh.getBillingLocations();
285                 if (billingLocations != null) {
286                     Iterator bli = billingLocations.iterator();
287                     while (bli.hasNext()) {
288                         GenericValue ocm = (GenericValue) bli.next();
289                         Map createBillToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", ocm.getString("contactMechId"),
290                                                                            "contactMechPurposeTypeId", "BILLING_LOCATION", "userLogin", userLogin);
291                         Map createBillToContactMechResult = dispatcher.runSync("createInvoiceContactMech", createBillToContactMechContext);
292                         if (ServiceUtil.isError(createBillToContactMechResult)) {
293                             return ServiceUtil.returnError("Error creating BILLING_LOCATION InvoiceContactMech from order", null, null, createBillToContactMechResult);
294                         }
295                     }
296                 }
297             }
298
299             // get a list of the payment method types
300
//DEJ20050705 doesn't appear to be used: List paymentPreferences = orderHeader.getRelated("OrderPaymentPreference");
301

302             // create the bill-from (or pay-to) contact mech as the primary PAYMENT_LOCATION of the party from the store
303
GenericValue payToAddress = null;
304             if (invoiceType.equals("PURCHASE_INVOICE")) {
305                 // for purchase orders, the pay to address is the BILLING_LOCATION of the vendor
306
GenericValue billFromVendor = orh.getPartyFromRole("BILL_FROM_VENDOR");
307                 if (billFromVendor != null) {
308                     List billingContactMechs = billFromVendor.getRelatedOne("Party").getRelatedByAnd("PartyContactMechPurpose",
309                             UtilMisc.toMap("contactMechPurposeTypeId", "BILLING_LOCATION"));
310                     if ((billingContactMechs != null) && (billingContactMechs.size() > 0)) {
311                         payToAddress = (GenericValue) billingContactMechs.get(0);
312                     }
313                 }
314             } else {
315                 // for sales orders, it is the payment address on file for the store
316
payToAddress = PaymentWorker.getPaymentAddress(delegator, productStore.getString("payToPartyId"));
317             }
318             if (payToAddress != null) {
319
320                 Map createPayToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", payToAddress.getString("contactMechId"),
321                                                                    "contactMechPurposeTypeId", "PAYMENT_LOCATION", "userLogin", userLogin);
322                 Map createPayToContactMechResult = dispatcher.runSync("createInvoiceContactMech", createPayToContactMechContext);
323                 if (ServiceUtil.isError(createPayToContactMechResult)) {
324                     return ServiceUtil.returnError("Error creating PAYMENT_LOCATION InvoiceContactMech from order", null, null, createPayToContactMechResult);
325                 }
326             }
327
328             // sequence for items - all OrderItems or InventoryReservations + all Adjustments
329
int invoiceItemSeqNum = 1;
330             String JavaDoc invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
331
332             // create the item records
333
if (billItems != null) {
334                 Iterator itemIter = billItems.iterator();
335                 while (itemIter.hasNext()) {
336                     GenericValue itemIssuance = null;
337                     GenericValue orderItem = null;
338                     GenericValue shipmentReceipt = null;
339                     GenericValue currentValue = (GenericValue) itemIter.next();
340                     if ("ItemIssuance".equals(currentValue.getEntityName())) {
341                         itemIssuance = currentValue;
342                     } else if ("OrderItem".equals(currentValue.getEntityName())) {
343                         orderItem = currentValue;
344                     } else if ("ShipmentReceipt".equals(currentValue.getEntityName())) {
345                         shipmentReceipt = currentValue;
346                     } else {
347                         Debug.logError("Unexpected entity " + currentValue + " of type " + currentValue.getEntityName(), module);
348                     }
349
350                     if (orderItem == null && itemIssuance != null) {
351                         orderItem = itemIssuance.getRelatedOne("OrderItem");
352                     } else if ((orderItem == null) && (shipmentReceipt != null)) {
353                         orderItem = shipmentReceipt.getRelatedOne("OrderItem");
354                     } else if ((orderItem == null) && (itemIssuance == null) && (shipmentReceipt == null)) {
355                         Debug.logError("Cannot create invoice when orderItem, itemIssuance, and shipmentReceipt are all null", module);
356                         return ServiceUtil.returnError("Illegal values passed to create invoice service");
357                     }
358                     GenericValue product = null;
359                     if (orderItem.get("productId") != null) {
360                         product = orderItem.getRelatedOne("Product");
361                     }
362
363                     // get some quantities
364
BigDecimal JavaDoc orderedQuantity = orderItem.getBigDecimal("quantity");
365                     BigDecimal JavaDoc billingQuantity = null;
366                     if (itemIssuance != null) {
367                         billingQuantity = itemIssuance.getBigDecimal("quantity");
368                     } else if (shipmentReceipt != null) {
369                         billingQuantity = shipmentReceipt.getBigDecimal("quantityAccepted");
370                     } else {
371                         billingQuantity = orderedQuantity;
372                     }
373                     if (orderedQuantity == null) orderedQuantity = ZERO;
374                     if (billingQuantity == null) billingQuantity = ZERO;
375
376                     String JavaDoc lookupType = "FINISHED_GOOD"; // the default product type
377
if (product != null) {
378                         lookupType = product.getString("productTypeId");
379                     } else if (orderItem != null) {
380                         lookupType = orderItem.getString("orderItemTypeId");
381                     }
382
383                     // check if shipping applies to this item. Shipping is calculated for sales invoices, not purchase invoices.
384
boolean shippingApplies = false;
385                     if ((product != null) && (ProductWorker.shippingApplies(product)) && (invoiceType.equals("SALES_INVOICE"))) {
386                         shippingApplies = true;
387                     }
388
389                     BigDecimal JavaDoc billingAmount = orderItem.getBigDecimal("unitPrice").setScale(decimals, rounding);
390                     
391                     Map createInvoiceItemContext = FastMap.newInstance();
392                     createInvoiceItemContext.put("invoiceId", invoiceId);
393                     createInvoiceItemContext.put("invoiceItemSeqId", invoiceItemSeqId);
394                     createInvoiceItemContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, lookupType, invoiceType, "INV_FPROD_ITEM"));
395                     createInvoiceItemContext.put("description", orderItem.get("itemDescription"));
396                     createInvoiceItemContext.put("quantity", new Double JavaDoc(billingQuantity.doubleValue()));
397                     createInvoiceItemContext.put("amount", new Double JavaDoc(billingAmount.doubleValue()));
398                     createInvoiceItemContext.put("productId", orderItem.get("productId"));
399                     createInvoiceItemContext.put("productFeatureId", orderItem.get("productFeatureId"));
400                     createInvoiceItemContext.put("overrideGlAccountId", orderItem.get("overrideGlAccountId"));
401                     //createInvoiceItemContext.put("uomId", "");
402
createInvoiceItemContext.put("userLogin", userLogin);
403
404                     String JavaDoc itemIssuanceId = null;
405                     if (itemIssuance != null && itemIssuance.get("inventoryItemId") != null) {
406                         itemIssuanceId = itemIssuance.getString("itemIssuanceId");
407                         createInvoiceItemContext.put("inventoryItemId", itemIssuance.get("inventoryItemId"));
408                     }
409                     // similarly, tax only for purchase invoices
410
if ((product != null) && (invoiceType.equals("SALES_INVOICE"))) {
411                         createInvoiceItemContext.put("taxableFlag", product.get("taxable"));
412                     }
413
414                     Map createInvoiceItemResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemContext);
415                     if (ServiceUtil.isError(createInvoiceItemResult)) {
416                         return ServiceUtil.returnError("Error creating InvoiceItem from order", null, null, createInvoiceItemResult);
417                     }
418
419                     // this item total
420
BigDecimal JavaDoc thisAmount = billingAmount.multiply(billingQuantity).setScale(decimals, rounding);
421
422                     // add to the ship amount only if it applies to this item
423
if (shippingApplies) {
424                         invoiceShipProRateAmount = invoiceShipProRateAmount.add(thisAmount).setScale(decimals, rounding);
425                     }
426
427                     // increment the invoice subtotal
428
invoiceSubTotal = invoiceSubTotal.add(thisAmount).setScale(100, rounding);
429
430                     // increment the invoice quantity
431
invoiceQuantity = invoiceQuantity.add(billingQuantity).setScale(decimals, rounding);
432
433                     // create the OrderItemBilling record
434
Map createOrderItemBillingContext = FastMap.newInstance();
435                     createOrderItemBillingContext.put("invoiceId", invoiceId);
436                     createOrderItemBillingContext.put("invoiceItemSeqId", invoiceItemSeqId);
437                     createOrderItemBillingContext.put("orderId", orderItem.get("orderId"));
438                     createOrderItemBillingContext.put("orderItemSeqId", orderItem.get("orderItemSeqId"));
439                     createOrderItemBillingContext.put("itemIssuanceId", itemIssuanceId);
440                     createOrderItemBillingContext.put("quantity", new Double JavaDoc(billingQuantity.doubleValue()));
441                     createOrderItemBillingContext.put("amount", new Double JavaDoc(billingAmount.doubleValue()));
442                     createOrderItemBillingContext.put("userLogin", userLogin);
443                     if ((shipmentReceipt != null) && (shipmentReceipt.getString("receiptId") != null)) {
444                         createOrderItemBillingContext.put("shipmentReceiptId", shipmentReceipt.getString("receiptId"));
445                     }
446
447                     Map createOrderItemBillingResult = dispatcher.runSync("createOrderItemBilling", createOrderItemBillingContext);
448                     if (ServiceUtil.isError(createOrderItemBillingResult)) {
449                         return ServiceUtil.returnError("Error creating OrderItemBilling from order", null, null, createOrderItemBillingResult);
450                     }
451
452                     // increment the counter
453
invoiceItemSeqNum++;
454                     invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
455
456                     // create the item adjustment as line items
457
List itemAdjustments = OrderReadHelper.getOrderItemAdjustmentList(orderItem, orh.getAdjustments());
458                     Iterator itemAdjIter = itemAdjustments.iterator();
459                     while (itemAdjIter.hasNext()) {
460                         GenericValue adj = (GenericValue) itemAdjIter.next();
461                         
462                         // Check against OrderAdjustmentBilling to see how much of this adjustment has already been invoiced
463
BigDecimal JavaDoc adjAlreadyInvoicedAmount = null;
464                         try {
465                             Map checkResult = dispatcher.runSync("calculateInvoicedAdjustmentTotal", UtilMisc.toMap("orderAdjustment", adj));
466                             adjAlreadyInvoicedAmount = (BigDecimal JavaDoc) checkResult.get("invoicedTotal");
467                         } catch (GenericServiceException e) {
468                             String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale);
469                             Debug.logError(e, errMsg, module);
470                             return ServiceUtil.returnError(errMsg);
471                         }
472         
473                         // If the absolute invoiced amount >= the abs of the adjustment amount, the full amount has already been invoiced,
474
// so skip this adjustment
475
if (adjAlreadyInvoicedAmount.abs().compareTo(adj.getBigDecimal("amount").setScale(decimals, rounding).abs()) > 0) {
476                             continue;
477                         }
478         
479                         if (adj.get("amount") != null) {
480                             // pro-rate the amount
481

482                             // set decimals = 100 means we don't round this intermediate value, which is very important
483
BigDecimal JavaDoc amount = adj.getBigDecimal("amount").divide(orderItem.getBigDecimal("quantity"), 100, rounding);
484                             amount = amount.multiply(billingQuantity);
485                             amount = amount.setScale(decimals, rounding);
486
487                             Map createInvoiceItemAdjContext = FastMap.newInstance();
488                             createInvoiceItemAdjContext.put("invoiceId", invoiceId);
489                             createInvoiceItemAdjContext.put("invoiceItemSeqId", invoiceItemSeqId);
490                             createInvoiceItemAdjContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, adj.getString("orderAdjustmentTypeId"), invoiceType, "INVOICE_ITM_ADJ"));
491                             createInvoiceItemAdjContext.put("description", adj.get("description"));
492                             createInvoiceItemAdjContext.put("quantity", new Double JavaDoc(1));
493                             createInvoiceItemAdjContext.put("amount", new Double JavaDoc(amount.doubleValue()));
494                             createInvoiceItemAdjContext.put("productId", orderItem.get("productId"));
495                             createInvoiceItemAdjContext.put("productFeatureId", orderItem.get("productFeatureId"));
496                             createInvoiceItemAdjContext.put("overrideGlAccountId", adj.get("overrideGlAccountId"));
497                             //createInvoiceItemAdjContext.put("uomId", "");
498
createInvoiceItemAdjContext.put("userLogin", userLogin);
499                             createInvoiceItemAdjContext.put("taxAuthPartyId", adj.get("taxAuthPartyId"));
500                             createInvoiceItemAdjContext.put("taxAuthGeoId", adj.get("taxAuthGeoId"));
501                             createInvoiceItemAdjContext.put("taxAuthorityRateSeqId", adj.get("taxAuthorityRateSeqId"));
502         
503                             // invoice items for sales tax are not taxable themselves
504
// TODO: This is not an ideal solution. Instead, we need to use OrderAdjustment.includeInTax when it is implemented
505
if (!(adj.getString("orderAdjustmentTypeId").equals("SALES_TAX"))) {
506                                 createInvoiceItemAdjContext.put("taxableFlag", product.get("taxable"));
507                             }
508         
509                             Map createInvoiceItemAdjResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemAdjContext);
510                             if (ServiceUtil.isError(createInvoiceItemAdjResult)) {
511                                 return ServiceUtil.returnError("Error creating InvoiceItem from order item adjustment", null, null, createInvoiceItemAdjResult);
512                             }
513
514                             // Create the OrderAdjustmentBilling record
515
Map createOrderAdjustmentBillingContext = FastMap.newInstance();
516                             createOrderAdjustmentBillingContext.put("orderAdjustmentId", adj.getString("orderAdjustmentId"));
517                             createOrderAdjustmentBillingContext.put("invoiceId", invoiceId);
518                             createOrderAdjustmentBillingContext.put("invoiceItemSeqId", invoiceItemSeqId);
519                             createOrderAdjustmentBillingContext.put("amount", new Double JavaDoc(amount.doubleValue()));
520                             createOrderAdjustmentBillingContext.put("userLogin", userLogin);
521
522                             Map createOrderAdjustmentBillingResult = dispatcher.runSync("createOrderAdjustmentBilling", createOrderAdjustmentBillingContext);
523                             if (ServiceUtil.isError(createOrderAdjustmentBillingResult)) {
524                                 return ServiceUtil.returnError(UtilProperties.getMessage(resource,"AccountingErrorCreatingOrderAdjustmentBillingFromOrder",locale), null, null, createOrderAdjustmentBillingContext);
525                             }
526
527                             // this adjustment amount
528
BigDecimal JavaDoc thisAdjAmount = new BigDecimal JavaDoc(amount.doubleValue()).setScale(decimals, rounding);
529
530                             // adjustments only apply to totals when they are not tax or shipping adjustments
531
if (!"SALES_TAX".equals(adj.getString("orderAdjustmentTypeId")) &&
532                                     !"SHIPPING_ADJUSTMENT".equals(adj.getString("orderAdjustmentTypeId"))) {
533                                 // increment the invoice subtotal
534
invoiceSubTotal = invoiceSubTotal.add(thisAdjAmount).setScale(100, rounding);
535
536                                 // add to the ship amount only if it applies to this item
537
if (shippingApplies) {
538                                     invoiceShipProRateAmount = invoiceShipProRateAmount.add(thisAdjAmount).setScale(decimals, rounding);
539                                 }
540                             }
541
542                             // increment the counter
543
invoiceItemSeqNum++;
544                             invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
545                         }
546                     }
547                 }
548             }
549
550             // create header adjustments as line items -- always to tax/shipping last
551
Map shipAdjustments = new HashMap();
552             Map taxAdjustments = new HashMap();
553
554             List headerAdjustments = orh.getOrderHeaderAdjustments();
555             Iterator headerAdjIter = headerAdjustments.iterator();
556             while (headerAdjIter.hasNext()) {
557                 GenericValue adj = (GenericValue) headerAdjIter.next();
558
559                 // Check against OrderAdjustmentBilling to see how much of this adjustment has already been invoiced
560
BigDecimal JavaDoc adjAlreadyInvoicedAmount = null;
561                 try {
562                     Map checkResult = dispatcher.runSync("calculateInvoicedAdjustmentTotal", UtilMisc.toMap("orderAdjustment", adj));
563                     adjAlreadyInvoicedAmount = ((BigDecimal JavaDoc) checkResult.get("invoicedTotal")).setScale(decimals, rounding);
564                 } catch (GenericServiceException e) {
565                     String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale);
566                     Debug.logError(e, errMsg, module);
567                     return ServiceUtil.returnError(errMsg);
568                 }
569
570                 // If the absolute invoiced amount >= the abs of the adjustment amount, the full amount has already been invoiced,
571
// so skip this adjustment
572
if (adjAlreadyInvoicedAmount.abs().compareTo(adj.getBigDecimal("amount").setScale(decimals, rounding).abs()) > 0) {
573                     continue;
574                 }
575
576                 if ("SHIPPING_CHARGES".equals(adj.getString("orderAdjustmentTypeId"))) {
577                     shipAdjustments.put(adj, adjAlreadyInvoicedAmount);
578                 } else if ("SALES_TAX".equals(adj.getString("orderAdjustmentTypeId"))) {
579                     taxAdjustments.put(adj, adjAlreadyInvoicedAmount);
580                 } else {
581                     // these will effect the shipping pro-rate (unless commented)
582
// other adjustment type
583
BigDecimal JavaDoc adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId,
584                             orderSubTotal, invoiceSubTotal, adj.getBigDecimal("amount").setScale(decimals, rounding), decimals, rounding, userLogin, dispatcher, locale);
585                     // invoiceShipProRateAmount += adjAmount;
586
// do adjustments compound or are they based off subtotal? Here we will (unless commented)
587
// invoiceSubTotal += adjAmount;
588

589                     // increment the counter
590
invoiceItemSeqNum++;
591                     invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
592                 }
593             }
594
595             // next do the shipping adjustments. Note that we do not want to add these to the invoiceSubTotal or orderSubTotal for pro-rating tax later, as that would cause
596
// numerator/denominator problems when the shipping is not pro-rated but rather charged all on the first invoice
597
Iterator shipAdjIter = shipAdjustments.keySet().iterator();
598             while (shipAdjIter.hasNext()) {
599                 GenericValue adj = (GenericValue) shipAdjIter.next();
600                 BigDecimal JavaDoc adjAlreadyInvoicedAmount = (BigDecimal JavaDoc) shipAdjustments.get(adj);
601                 
602                 if ("N".equalsIgnoreCase(prorateShipping)) {
603                     // Set the divisor and multiplier to 1 to avoid prorating
604
BigDecimal JavaDoc divisor = new BigDecimal JavaDoc("1");
605                     BigDecimal JavaDoc multiplier = new BigDecimal JavaDoc("1");
606                     
607                     // The base amount in this case is the adjustment amount minus the total already invoiced for that adjustment, since
608
// it won't be prorated
609
BigDecimal JavaDoc baseAmount = adj.getBigDecimal("amount").setScale(decimals, rounding).subtract(adjAlreadyInvoicedAmount);
610                     BigDecimal JavaDoc adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId,
611                         divisor, multiplier, baseAmount, decimals, rounding, userLogin, dispatcher, locale);
612                 } else {
613                     // Pro-rate the shipping amount based on shippable information
614
BigDecimal JavaDoc divisor = shippableAmount;
615                     BigDecimal JavaDoc multiplier = invoiceShipProRateAmount;
616                     
617                     // The base amount in this case is the adjustment amount, since we want to prorate based on the full amount
618
BigDecimal JavaDoc baseAmount = adj.getBigDecimal("amount").setScale(decimals, rounding);
619                     BigDecimal JavaDoc adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId,
620                             divisor, multiplier, baseAmount, decimals, rounding, userLogin, dispatcher, locale);
621                 }
622
623                 // Increment the counter
624
invoiceItemSeqNum++;
625                 invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
626             }
627
628             // last do the tax adjustments
629
String JavaDoc prorateTaxes = productStore.getString("prorateTaxes");
630             if (prorateTaxes == null) {
631                 prorateTaxes = "Y";
632             }
633             Iterator taxAdjIter = taxAdjustments.keySet().iterator();
634             while (taxAdjIter.hasNext()) {
635                 GenericValue adj = (GenericValue) taxAdjIter.next();
636                 BigDecimal JavaDoc adjAlreadyInvoicedAmount = (BigDecimal JavaDoc) taxAdjustments.get(adj);
637                 BigDecimal JavaDoc adjAmount = null;
638                 
639                 if ("N".equalsIgnoreCase(prorateTaxes)) {
640
641                     // Set the divisor and multiplier to 1 to avoid prorating
642
BigDecimal JavaDoc divisor = new BigDecimal JavaDoc("1");
643                     BigDecimal JavaDoc multiplier = new BigDecimal JavaDoc("1");
644                     
645                     // The base amount in this case is the adjustment amount minus the total already invoiced for that adjustment, since
646
// it won't be prorated
647
// Note this should use invoice decimals & rounding instead of taxDecimals and taxRounding for tax adjustments, because it will be added to the invoice
648
BigDecimal JavaDoc baseAmount = adj.getBigDecimal("amount").setScale(decimals, rounding).subtract(adjAlreadyInvoicedAmount);
649                     adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId,
650                              divisor, multiplier, baseAmount, decimals, rounding, userLogin, dispatcher, locale);
651                 } else {
652
653                     // Pro-rate the tax amount based on shippable information
654
BigDecimal JavaDoc divisor = orderSubTotal;
655                     BigDecimal JavaDoc multiplier = invoiceSubTotal;
656                     
657                     // The base amount in this case is the adjustment amount, since we want to prorate based on the full amount
658
// Note this should use invoice decimals & rounding instead of taxDecimals and taxRounding for tax adjustments, because it will be added to the invoice
659
BigDecimal JavaDoc baseAmount = adj.getBigDecimal("amount").setScale(decimals, rounding);
660                     adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId,
661                             divisor, multiplier, baseAmount, decimals, rounding, userLogin, dispatcher, locale);
662                 }
663                 invoiceSubTotal = invoiceSubTotal.add(adjAmount).setScale(decimals, rounding);
664
665                 // Increment the counter
666
invoiceItemSeqNum++;
667                 invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
668             }
669
670             // check for previous order payments
671
List orderPaymentPrefs = delegator.findByAnd("OrderPaymentPreference", UtilMisc.toMap("orderId", orderId));
672             if (orderPaymentPrefs != null) {
673                 List currentPayments = new ArrayList();
674                 Iterator opi = orderPaymentPrefs.iterator();
675                 while (opi.hasNext()) {
676                     GenericValue paymentPref = (GenericValue) opi.next();
677                     List payments = paymentPref.getRelated("Payment");
678                     currentPayments.addAll(payments);
679                 }
680                 if (currentPayments.size() > 0) {
681                     // apply these payments to the invoice; only if they haven't already been applied
682
Iterator cpi = currentPayments.iterator();
683                     while (cpi.hasNext()) {
684                         GenericValue payment = (GenericValue) cpi.next();
685                         List currentApplications = null;
686                         currentApplications = payment.getRelated("PaymentApplication");
687                         if (currentApplications == null || currentApplications.size() == 0) {
688                             // no applications; okay to apply
689
Map appl = new HashMap();
690                             appl.put("paymentId", payment.get("paymentId"));
691                             appl.put("invoiceId", invoiceId);
692                             appl.put("billingAccountId", billingAccountId);
693                             appl.put("amountApplied", payment.get("amount"));
694                             appl.put("userLogin", userLogin);
695                             Map createPayApplResult = dispatcher.runSync("createPaymentApplication", appl);
696                             if (ServiceUtil.isError(createPayApplResult)) {
697                                 return ServiceUtil.returnError("Error creating invoice from order", null, null, createPayApplResult);
698                             }
699                         }
700                     }
701                 }
702             }
703
704             // should all be in place now, so set status to INVOICE_READY (unless it's a purchase invoice, which we sets to INVOICE_IN_PROCESS)
705
String JavaDoc nextStatusId = "INVOICE_READY";
706             if (invoiceType.equals("PURCHASE_INVOICE")) {
707                 nextStatusId = "INVOICE_IN_PROCESS";
708             }
709             Map setInvoiceStatusResult = dispatcher.runSync("setInvoiceStatus", UtilMisc.toMap("invoiceId", invoiceId, "statusId", nextStatusId, "userLogin", userLogin));
710             if (ServiceUtil.isError(setInvoiceStatusResult)) {
711                 return ServiceUtil.returnError("Error creating invoice from order", null, null, setInvoiceStatusResult);
712             }
713
714             // check to see if we are all paid up
715
Map checkResp = dispatcher.runSync("checkInvoicePaymentApplications", UtilMisc.toMap("invoiceId", invoiceId, "userLogin", userLogin));
716             if (ServiceUtil.isError(checkResp)) {
717                 return ServiceUtil.returnError("Error creating invoice from order while checking payment applications", null, null, checkResp);
718             }
719
720             Map resp = ServiceUtil.returnSuccess();
721             resp.put("invoiceId", invoiceId);
722             return resp;
723         } catch (GenericEntityException e) {
724             String JavaDoc errMsg = "Entity/data problem creating invoice from order items: " + e.toString();
725             Debug.logError(e, errMsg, module);
726             return ServiceUtil.returnError(errMsg);
727         } catch (GenericServiceException e) {
728             String JavaDoc errMsg = "Service/other problem creating invoice from order items: " + e.toString();
729             Debug.logError(e, errMsg, module);
730             return ServiceUtil.returnError(errMsg);
731         }
732     }
733
734     public static Map createInvoicesFromShipment(DispatchContext dctx, Map context) {
735         GenericDelegator delegator = dctx.getDelegator();
736         LocalDispatcher dispatcher = dctx.getDispatcher();
737         String JavaDoc shipmentId = (String JavaDoc) context.get("shipmentId");
738         List invoicesCreated = new ArrayList();
739         Locale locale = (Locale) context.get("locale");
740         
741         Map serviceContext = UtilMisc.toMap("shipmentIds", UtilMisc.toList(shipmentId), "userLogin", context.get("userLogin"));
742         try {
743             Map result = dispatcher.runSync("createInvoicesFromShipments", serviceContext);
744             invoicesCreated = (List)result.get("invoicesCreated");
745         } catch (GenericServiceException e) {
746             Debug.logError(e, "Trouble calling createInvoicesFromShipment service; invoice not created for shipment [" + shipmentId + "]", module);
747             return ServiceUtil.returnError(UtilProperties.getMessage(resource,"AccountingTroubleCallingCreateInvoicesFromShipmentService",UtilMisc.toMap("shipmentId",shipmentId),locale));
748         }
749         Map response = ServiceUtil.returnSuccess();
750         response.put("invoicesCreated", invoicesCreated);
751         return response;
752     }
753     
754     public static Map createInvoicesFromShipments(DispatchContext dctx, Map context) {
755         GenericDelegator delegator = dctx.getDelegator();
756         LocalDispatcher dispatcher = dctx.getDispatcher();
757         List shipmentIds = (List) context.get("shipmentIds");
758         Locale locale = (Locale) context.get("locale");
759
760         boolean salesShipmentFound = false;
761         boolean purchaseShipmentFound = false;
762         
763         List invoicesCreated = new ArrayList();
764
765         List shipmentIdList = new LinkedList();
766         for (int i = 0; i < shipmentIds.size(); i++) {
767             String JavaDoc tmpShipmentId = (String JavaDoc)shipmentIds.get(i);
768             try {
769                 GenericValue shipment = delegator.findByPrimaryKey("Shipment", UtilMisc.toMap("shipmentId", tmpShipmentId));
770                 if ((shipment.getString("shipmentTypeId") != null) && (shipment.getString("shipmentTypeId").equals("PURCHASE_SHIPMENT"))) {
771                     purchaseShipmentFound = true;
772                 } else {
773                     salesShipmentFound = true;
774                 }
775                 if (purchaseShipmentFound && salesShipmentFound) {
776                     return ServiceUtil.returnError("Shipments of different types found; shipment [" + tmpShipmentId + "] of type [" + shipment.getString("shipmentTypeId") + "] is of different type from the previous ones.");
777                 }
778             } catch (GenericEntityException e) {
779                 Debug.logError(e, "Trouble getting Shipment entity for shipment [" + tmpShipmentId + "]", module);
780                 return ServiceUtil.returnError("Trouble getting Shipment entity for shipment [" + tmpShipmentId + "]");
781             }
782         }
783         EntityCondition shipmentIdsCond = new EntityExpr("shipmentId", EntityOperator.IN, shipmentIds);
784         // check the status of the shipment
785

786         // get the items of the shipment. They can come from ItemIssuance if the shipment were from a sales order or ShipmentReceipt
787
// if it were a purchase order
788
List items = null;
789         try {
790             if (purchaseShipmentFound) {
791                 items = delegator.findByCondition("ShipmentReceipt", shipmentIdsCond, null, UtilMisc.toList("shipmentId"));
792             } else {
793                 items = delegator.findByCondition("ItemIssuance", shipmentIdsCond, null, UtilMisc.toList("shipmentId"));
794             }
795         } catch (GenericEntityException e) {
796             Debug.logError(e, "Problem getting issued items from shipments", module);
797             return ServiceUtil.returnError("Problem getting issued items from shipments");
798         }
799         if (items == null) {
800             Debug.logInfo("No items issued for shipments", module);
801             return ServiceUtil.returnSuccess();
802         }
803
804         // group items by order
805
Map shippedOrderItems = new HashMap();
806         Iterator itemsIter = items.iterator();
807         while (itemsIter.hasNext()) {
808             GenericValue item = (GenericValue) itemsIter.next();
809             String JavaDoc orderId = item.getString("orderId");
810             String JavaDoc orderItemSeqId = item.getString("orderItemSeqId");
811             List itemsByOrder = (List) shippedOrderItems.get(orderId);
812             if (itemsByOrder == null) {
813                 itemsByOrder = new ArrayList();
814             }
815
816             // check and make sure we haven't already billed for this issuance or shipment receipt
817
Map billFields = UtilMisc.toMap("orderId", orderId, "orderItemSeqId", orderItemSeqId);
818             if (item.getEntityName().equals("ItemIssuance")) {
819                 billFields.put("itemIssuanceId", item.get("itemIssuanceId"));
820             } else if (item.getEntityName().equals("ShipmentReceipt")) {
821                 billFields.put("shipmentReceiptId", item.getString("receiptId"));
822             }
823             List itemBillings = null;
824             try {
825                 itemBillings = delegator.findByAnd("OrderItemBilling", billFields);
826             } catch (GenericEntityException e) {
827                 Debug.logError(e, "Problem looking up OrderItemBilling records for : " + billFields, module);
828                 return ServiceUtil.returnError("Problem getting OrderItemBilling records");
829             }
830
831             // if none found, then okay to bill
832
if (itemBillings == null || itemBillings.size() == 0) {
833                 itemsByOrder.add(item);
834             }
835
836             // update the map with modified list
837
shippedOrderItems.put(orderId, itemsByOrder);
838         }
839
840         // make sure we aren't billing items already invoiced i.e. items billed as digital (FINDIG)
841
Set orders = shippedOrderItems.keySet();
842         Iterator ordersIter = orders.iterator();
843         while (ordersIter.hasNext()) {
844             String JavaDoc orderId = (String JavaDoc) ordersIter.next();
845
846             // we'll only use this list to figure out which ones to send
847
List billItems = (List) shippedOrderItems.get(orderId);
848
849             // a new list to be used to pass to the create invoice service
850
List toBillItems = new ArrayList();
851
852             // map of available quantities so we only have to calc once
853
Map itemQtyAvail = new HashMap();
854
855             // now we will check each issuance and make sure it hasn't already been billed
856
Iterator billIt = billItems.iterator();
857             while (billIt.hasNext()) {
858                 GenericValue issue = (GenericValue) billIt.next();
859                 BigDecimal JavaDoc issueQty = ZERO;
860                 if (issue.getEntityName().equals("ShipmentReceipt")) {
861                     issueQty = issue.getBigDecimal("quantityAccepted");
862                 } else {
863                     issueQty = issue.getBigDecimal("quantity");
864                 }
865
866                 BigDecimal JavaDoc billAvail = (BigDecimal JavaDoc) itemQtyAvail.get(issue.getString("orderItemSeqId"));
867                 if (billAvail == null) {
868                     Map lookup = UtilMisc.toMap("orderId", orderId, "orderItemSeqId", issue.get("orderItemSeqId"));
869                     GenericValue orderItem = null;
870                     List billed = null;
871                     try {
872                         orderItem = issue.getRelatedOne("OrderItem");
873                         billed = delegator.findByAnd("OrderItemBilling", lookup);
874                     } catch (GenericEntityException e) {
875                         Debug.logError(e, "Problem looking up OrderItem/OrderItemBilling records for : " + lookup, module);
876                         return ServiceUtil.returnError("Problem getting OrderItem/OrderItemBilling records");
877                     }
878
879                     // total ordered
880
BigDecimal JavaDoc orderedQty = orderItem.getBigDecimal("quantity");
881
882                     // add up the already billed total
883
if (billed != null && billed.size() > 0) {
884                         BigDecimal JavaDoc billedQuantity = ZERO;
885                         Iterator bi = billed.iterator();
886                         while (bi.hasNext()) {
887                             GenericValue oib = (GenericValue) bi.next();
888                             BigDecimal JavaDoc qty = oib.getBigDecimal("quantity");
889                             if (qty != null) {
890                                 billedQuantity = billedQuantity.add(qty).setScale(decimals, rounding);
891                             }
892                         }
893                         BigDecimal JavaDoc leftToBill = orderedQty.subtract(billedQuantity).setScale(decimals, rounding);
894                         billAvail = leftToBill;
895                     } else {
896                         billAvail = orderedQty;
897                     }
898                 }
899
900                 // no available means we cannot bill anymore
901
if (billAvail != null && billAvail.signum() == 1) { // this checks if billAvail is a positive non-zero number
902
if (issueQty != null && issueQty.doubleValue() > billAvail.doubleValue()) {
903                         // can only bill some of the issuance; others have been billed already
904
issue.set("quantity", new Double JavaDoc(billAvail.doubleValue()));
905                         billAvail = ZERO;
906                     } else {
907                         // now have been billed
908
billAvail = billAvail.subtract(issueQty).setScale(decimals, rounding);
909                     }
910
911                     // okay to bill these items; but none else
912
toBillItems.add(issue);
913                 }
914
915                 // update the available to bill quantity for the next pass
916
itemQtyAvail.put(issue.getString("orderItemSeqId"), billAvail);
917             }
918
919             OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
920             GenericValue productStore = orh.getProductStore();
921
922             // If shipping charges are not prorated, the shipments need to be examined for additional shipping charges
923
if (productStore.getString("prorateShipping").equals("N")) {
924     
925                 // Get the set of filtered shipments
926
List invoiceableShipmentIds = EntityUtil.getFieldListFromEntityList(toBillItems, "shipmentId", true);
927                 List invoiceableShipments = null;
928                 try {
929                     invoiceableShipments = delegator.findByCondition("Shipment", new EntityExpr("shipmentId", EntityOperator.IN, invoiceableShipmentIds), null, null);
930                 } catch( GenericEntityException e ) {
931                     String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentsService", locale);
932                     Debug.logError(e, errMsg, module);
933                     return ServiceUtil.returnError(errMsg);
934                 }
935                 
936                 // Total the additional shipping charges for the shipments
937
Map additionalShippingCharges = new HashMap();
938                 BigDecimal JavaDoc totalAdditionalShippingCharges = ZERO;
939                 Iterator isit = invoiceableShipments.iterator();
940                 while(isit.hasNext()) {
941                     GenericValue shipment = (GenericValue) isit.next();
942                     if (shipment.get("additionalShippingCharge") == null) continue;
943                     BigDecimal JavaDoc shipmentAdditionalShippingCharges = shipment.getBigDecimal("additionalShippingCharge").setScale(decimals, rounding);
944                     additionalShippingCharges.put(shipment, shipmentAdditionalShippingCharges);
945                     totalAdditionalShippingCharges = totalAdditionalShippingCharges.add(shipmentAdditionalShippingCharges);
946                 }
947                 
948                 // If the additional shipping charges are greater than zero, process them
949
if (totalAdditionalShippingCharges.signum() == 1) {
950
951                     // Add an OrderAdjustment to the order for each additional shipping charge
952
Iterator ascit = additionalShippingCharges.keySet().iterator();
953                     while (ascit.hasNext()) {
954                         GenericValue shipment = (GenericValue) ascit.next();
955                         String JavaDoc shipmentId = shipment.getString("shipmentId");
956                         BigDecimal JavaDoc additionalShippingCharge = (BigDecimal JavaDoc) additionalShippingCharges.get(shipment);
957                         Map createOrderAdjustmentContext = new HashMap();
958                         createOrderAdjustmentContext.put("orderId", orderId);
959                         createOrderAdjustmentContext.put("orderAdjustmentTypeId", "SHIPPING_CHARGES");
960                         createOrderAdjustmentContext.put("description", UtilProperties.getMessage(resource, "AccountingAdditionalShippingChargeForShipment", locale) + " #" + shipmentId);
961                         createOrderAdjustmentContext.put("sourceReferenceId", shipmentId);
962                         createOrderAdjustmentContext.put("amount", new Double JavaDoc(additionalShippingCharge.doubleValue()));
963                         createOrderAdjustmentContext.put("userLogin", context.get("userLogin"));
964                         String JavaDoc shippingOrderAdjustmentId = null;
965                         try {
966                             Map createOrderAdjustmentResult = dispatcher.runSync("createOrderAdjustment", createOrderAdjustmentContext);
967                             shippingOrderAdjustmentId = (String JavaDoc) createOrderAdjustmentResult.get("orderAdjustmentId");
968                         } catch (GenericServiceException e) {
969                             String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateOrderAdjustmentService", locale);
970                             Debug.logError(e, errMsg, module);
971                             return ServiceUtil.returnError(errMsg);
972                         }
973
974                         // Obtain a list of OrderAdjustments due to tax on the shipping charges, if any
975
GenericValue billToParty = orh.getBillToParty();
976                         GenericValue destinationContactMech = null;
977                         try {
978                             destinationContactMech = shipment.getRelatedOne("DestinationPostalAddress");
979                         } catch( GenericEntityException e ) {
980                             String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentService", locale);
981                             Debug.logError(e, errMsg, module);
982                             return ServiceUtil.returnError(errMsg);
983                         }
984                         
985                         List emptyList = new ArrayList();
986                         Map calcTaxContext = new HashMap();
987                         calcTaxContext.put("productStoreId", orh.getProductStoreId());
988                         calcTaxContext.put("billToPartyId", billToParty.getString("partyId"));
989                         calcTaxContext.put("orderShippingAmount", totalAdditionalShippingCharges);
990                         calcTaxContext.put("shippingAddress", destinationContactMech);
991
992                         // These parameters don't matter if we're only worried about adjustments on the shipping charges
993
calcTaxContext.put("itemProductList", emptyList);
994                         calcTaxContext.put("itemAmountList", emptyList);
995                         calcTaxContext.put("itemPriceList", emptyList);
996                         calcTaxContext.put("itemShippingList", emptyList);
997
998                         List orderAdjustments = null;
999                         Map calcTaxResult = null;
1000                        try {
1001                            calcTaxResult = dispatcher.runSync("calcTax", calcTaxContext);
1002                        } catch (GenericServiceException e) {
1003                            String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCalcTaxService", locale);
1004                            Debug.logError(e, errMsg, module);
1005                            return ServiceUtil.returnError(errMsg);
1006                        }
1007                        orderAdjustments = (List) calcTaxResult.get("orderAdjustments");
1008
1009                        // If we have any OrderAdjustments due to tax on shipping, store them and add them to the total
1010
if (calcTaxResult != null && orderAdjustments != null) {
1011                            Iterator oait = orderAdjustments.iterator();
1012                            while (oait.hasNext()) {
1013                                GenericValue orderAdjustment = (GenericValue) oait.next();
1014                                totalAdditionalShippingCharges = totalAdditionalShippingCharges.add(orderAdjustment.getBigDecimal("amount").setScale(decimals, rounding));
1015                                orderAdjustment.set("orderAdjustmentId", delegator.getNextSeqId("OrderAdjustment"));
1016                                orderAdjustment.set("orderId", orderId);
1017                                orderAdjustment.set("orderItemSeqId", "_NA_");
1018                                orderAdjustment.set("shipGroupSeqId", shipment.getString("primaryShipGroupSeqId"));
1019                                orderAdjustment.set("originalAdjustmentId", shippingOrderAdjustmentId);
1020                            }
1021                            try {
1022                                delegator.storeAll(orderAdjustments);
1023                            } catch( GenericEntityException e ) {
1024                                String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingProblemStoringOrderAdjustments", UtilMisc.toMap("orderAdjustments", orderAdjustments), locale);
1025                                Debug.logError(e, errMsg, module);
1026                                return ServiceUtil.returnError(errMsg);
1027                            }
1028                        }
1029
1030                        // If part of the order was paid via credit card, try to charge it for the additional shipping
1031
List orderPaymentPreferences = new ArrayList();
1032                        try {
1033                            orderPaymentPreferences = delegator.findByAnd("OrderPaymentPreference", UtilMisc.toMap("orderId", orderId, "paymentMethodTypeId", "CREDIT_CARD"));
1034                        } catch( GenericEntityException e ) {
1035                            String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingProblemGettingOrderPaymentPreferences", locale);
1036                            Debug.logError(e, errMsg, module);
1037                            return ServiceUtil.returnError(errMsg);
1038                        }
1039
1040                        // Use the first credit card we find, for the sake of simplicity
1041
String JavaDoc paymentMethodId = null;
1042                        GenericValue cardOrderPaymentPref = EntityUtil.getFirst(orderPaymentPreferences);
1043                        if (cardOrderPaymentPref != null) {
1044                            paymentMethodId = cardOrderPaymentPref.getString("paymentMethodId");
1045                        }
1046                            
1047                        if (paymentMethodId != null ) {
1048
1049                            // Release all outstanding (not settled or cancelled) authorizations, while keeping a running
1050
// total of their amounts so that the total plus the additional shipping charges can be authorized again
1051
// all at once.
1052
BigDecimal JavaDoc totalNewAuthAmount = new BigDecimal JavaDoc(totalAdditionalShippingCharges.doubleValue()).setScale(decimals, rounding);
1053                            Iterator oppit = orderPaymentPreferences.iterator();
1054                            while (oppit.hasNext()) {
1055                                GenericValue orderPaymentPreference = (GenericValue) oppit.next();
1056                                if (! (orderPaymentPreference.getString("statusId").equals("PAYMENT_SETTLED") || orderPaymentPreference.getString("statusId").equals("PAYMENT_CANCELLED"))) {
1057                                    GenericValue authTransaction = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
1058                                    if (authTransaction != null && authTransaction.get("amount") != null) {
1059
1060                                        // Update the total authorized amount
1061
totalNewAuthAmount = totalNewAuthAmount.add(authTransaction.getBigDecimal("amount").setScale(decimals, rounding));
1062
1063                                        // Release the authorization for the OrderPaymentPreference
1064
Map prefReleaseResult = null;
1065                                        try {
1066                                            prefReleaseResult = dispatcher.runSync("releaseOrderPaymentPreference", UtilMisc.toMap("orderPaymentPreferenceId", orderPaymentPreference.getString("orderPaymentPreferenceId"), "userLogin", context.get("userLogin")));
1067                                        } catch( GenericServiceException e ) {
1068                                            String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingReleaseOrderPaymentPreferenceService", locale);
1069                                            Debug.logError(e, errMsg, module);
1070                                            return ServiceUtil.returnError(errMsg);
1071                                        }
1072                                        if (ServiceUtil.isError(prefReleaseResult) || ServiceUtil.isFailure(prefReleaseResult)) {
1073                                            String JavaDoc errMsg = ServiceUtil.getErrorMessage(prefReleaseResult);
1074                                            Debug.logError(errMsg, module);
1075                                            return ServiceUtil.returnError(errMsg);
1076                                        }
1077                                    }
1078                                }
1079                            }
1080                            
1081                            // Create a new OrderPaymentPreference for the order to handle the new (totalled) charge. Don't
1082
// set the maxAmount so that it doesn't interfere with other authorizations
1083
Map serviceContext = UtilMisc.toMap("orderId", orderId, "paymentMethodId", paymentMethodId, "paymentMethodTypeId", "CREDIT_CARD", "userLogin", context.get("userLogin"));
1084                            String JavaDoc orderPaymentPreferenceId = null;
1085                            try {
1086                                Map result = dispatcher.runSync("createOrderPaymentPreference", serviceContext);
1087                                orderPaymentPreferenceId = (String JavaDoc) result.get("orderPaymentPreferenceId");
1088                            } catch (GenericServiceException e) {
1089                                String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateOrderPaymentPreferenceService", locale);
1090                                Debug.logError(e, errMsg, module);
1091                                return ServiceUtil.returnError(errMsg);
1092                            }
1093
1094                            // Attempt to authorize the new orderPaymentPreference
1095
Map authResult = null;
1096                            try {
1097
1098                                // Use an overrideAmount because the maxAmount wasn't set on the OrderPaymentPreference
1099
authResult = dispatcher.runSync("authOrderPaymentPreference", UtilMisc.toMap("orderPaymentPreferenceId", orderPaymentPreferenceId, "overrideAmount", new Double JavaDoc(totalNewAuthAmount.doubleValue()), "userLogin", context.get("userLogin")));
1100                            } catch (GenericServiceException e) {
1101                                String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingAuthOrderPaymentPreferenceService", locale);
1102                                Debug.logError(e, errMsg, module);
1103                                return ServiceUtil.returnError(errMsg);
1104                            }
1105
1106                            // If the authorization fails, create the invoice anyway, but make a note of it
1107
boolean authFinished = ( (Boolean JavaDoc) authResult.get("finished") ).booleanValue();
1108                            boolean authErrors = ( (Boolean JavaDoc) authResult.get("errors") ).booleanValue();
1109                            if (authErrors || ! authFinished) {
1110                                String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingUnableToAuthAdditionalShipCharges", UtilMisc.toMap("shipmentId", shipmentId, "paymentMethodId", paymentMethodId, "orderPaymentPreferenceId", orderPaymentPreferenceId), locale);
1111                                Debug.logError(errMsg, module);
1112                            }
1113                            
1114                        }
1115                    }
1116                }
1117            }
1118
1119            // call the createInvoiceForOrder service for each order
1120
Map serviceContext = UtilMisc.toMap("orderId", orderId, "billItems", toBillItems, "userLogin", context.get("userLogin"));
1121            try {
1122                Map result = dispatcher.runSync("createInvoiceForOrder", serviceContext);
1123                invoicesCreated.add(result.get("invoiceId"));
1124            } catch (GenericServiceException e) {
1125                Debug.logError(e, "Trouble calling createInvoiceForOrder service; invoice not created for shipment", module);
1126                return ServiceUtil.returnError("Trouble calling createInvoiceForOrder service; invoice not created for shipment");
1127            }
1128        }
1129
1130        Map response = ServiceUtil.returnSuccess();
1131        response.put("invoicesCreated", invoicesCreated);
1132        return response;
1133    }
1134
1135    private static String JavaDoc getInvoiceItemType(GenericDelegator delegator, String JavaDoc key, String JavaDoc invoiceTypeId, String JavaDoc defaultValue) {
1136        GenericValue itemMap = null;
1137        try {
1138            itemMap = delegator.findByPrimaryKey("InvoiceItemTypeMap", UtilMisc.toMap("invoiceItemMapKey", key, "invoiceTypeId", invoiceTypeId));
1139        } catch (GenericEntityException e) {
1140            Debug.logError(e, "Trouble getting InvoiceItemTypeMap entity", module);
1141            return defaultValue;
1142        }
1143        if (itemMap != null) {
1144            return itemMap.getString("invoiceItemTypeId");
1145        } else {
1146            return defaultValue;
1147        }
1148    }
1149
1150    public static Map createInvoicesFromReturnShipment(DispatchContext dctx, Map context) {
1151        GenericDelegator delegator = dctx.getDelegator();
1152        LocalDispatcher dispatcher = dctx.getDispatcher();
1153
1154        String JavaDoc shipmentId = (String JavaDoc) context.get("shipmentId");
1155        String JavaDoc errorMsg = "Error creating invoice for shipment [" + shipmentId + "]. ";
1156        List invoicesCreated = new ArrayList();
1157        try {
1158
1159            // get the shipment and validate that it is a sales return
1160
GenericValue shipment = delegator.findByPrimaryKey("Shipment", UtilMisc.toMap("shipmentId", shipmentId));
1161            if (shipment == null) {
1162                return ServiceUtil.returnError(errorMsg + "Shipment not found.");
1163            }
1164            if (!shipment.getString("shipmentTypeId").equals("SALES_RETURN")) {
1165                return ServiceUtil.returnError(errorMsg + "Shipment is not of type SALES_RETURN.");
1166            }
1167
1168            // get the list of ShipmentReceipt for this shipment
1169
List shipmentReceipts = shipment.getRelated("ShipmentReceipt");
1170
1171            // group the shipments by returnId (because we want a seperate itemized invoice for each return)
1172
Map receiptsGroupedByReturn = new HashMap();
1173            for (Iterator iter = shipmentReceipts.iterator(); iter.hasNext(); ) {
1174                GenericValue receipt = (GenericValue) iter.next();
1175                String JavaDoc returnId = receipt.getString("returnId");
1176
1177                // see if there are ReturnItemBillings for this item
1178
List billings = delegator.findByAnd("ReturnItemBilling", UtilMisc.toMap("shipmentReceiptId", receipt.getString("receiptId"), "returnId", returnId,
1179                            "returnItemSeqId", receipt.get("returnItemSeqId")));
1180
1181                // if there are billings, we have already billed the item, so skip it
1182
if (billings.size() > 0) continue;
1183
1184                // get the List of receipts keyed to this returnId or create a new one
1185
List receipts = (List) receiptsGroupedByReturn.get(returnId);
1186                if (receipts == null) {
1187                    receipts = new ArrayList();
1188                }
1189
1190                // add our item to the group and put it back in the map
1191
receipts.add(receipt);
1192                receiptsGroupedByReturn.put(returnId, receipts);
1193            }
1194
1195            // loop through the returnId keys in the map and invoke the createInvoiceFromReturn service for each
1196
for (Iterator iter = receiptsGroupedByReturn.keySet().iterator(); iter.hasNext(); ) {
1197                String JavaDoc returnId = (String JavaDoc) iter.next();
1198                List receipts = (List) receiptsGroupedByReturn.get(returnId);
1199                if (Debug.verboseOn()) {
1200                    Debug.logVerbose("Creating invoice for return [" + returnId + "] with receipts: " + receipts.toString(), module);
1201                }
1202                Map input = UtilMisc.toMap("returnId", returnId, "shipmentReceiptsToBill", receipts, "userLogin", context.get("userLogin"));
1203                Map serviceResults = dispatcher.runSync("createInvoiceFromReturn", input);
1204                if (ServiceUtil.isError(serviceResults)) {
1205                    return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1206                }
1207
1208                // put the resulting invoiceId in the return list
1209
invoicesCreated.add(serviceResults.get("invoiceId"));
1210            }
1211        } catch (GenericServiceException e) {
1212            Debug.logError(e, errorMsg + e.getMessage(), module);
1213            return ServiceUtil.returnError(errorMsg + e.getMessage());
1214        } catch (GenericEntityException e) {
1215            Debug.logError(e, errorMsg + e.getMessage(), module);
1216            return ServiceUtil.returnError(errorMsg + e.getMessage());
1217        }
1218
1219        Map result = ServiceUtil.returnSuccess();
1220        result.put("invoicesCreated", invoicesCreated);
1221        return result;
1222    }
1223
1224    public static Map createInvoiceFromReturn(DispatchContext dctx, Map context) {
1225        GenericDelegator delegator = dctx.getDelegator();
1226        LocalDispatcher dispatcher = dctx.getDispatcher();
1227        GenericValue userLogin = (GenericValue) context.get("userLogin");
1228
1229        String JavaDoc returnId= (String JavaDoc) context.get("returnId");
1230        List receipts = (List) context.get("shipmentReceiptsToBill");
1231        String JavaDoc errorMsg = "Error creating invoice for return [" + returnId + "]. ";
1232        List invoicesCreated = new ArrayList();
1233        try {
1234            // get the return header
1235
GenericValue returnHeader = delegator.findByPrimaryKey("ReturnHeader", UtilMisc.toMap("returnId", returnId));
1236
1237            // set the invoice data
1238
Map input = UtilMisc.toMap("invoiceTypeId", "CUST_RTN_INVOICE", "statusId", "INVOICE_IN_PROCESS");
1239            input.put("partyId", returnHeader.get("toPartyId"));
1240            input.put("partyIdFrom", returnHeader.get("fromPartyId"));
1241            input.put("currencyUomId", returnHeader.get("currencyUomId"));
1242            input.put("invoiceDate", UtilDateTime.nowTimestamp());
1243            input.put("billingAccountId", returnHeader.get("billingAccountId"));
1244            input.put("userLogin", userLogin);
1245
1246            // call the service to create the invoice
1247
Map serviceResults = dispatcher.runSync("createInvoice", input);
1248            if (ServiceUtil.isError(serviceResults)) {
1249                return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1250            }
1251            String JavaDoc invoiceId = (String JavaDoc) serviceResults.get("invoiceId");
1252
1253            // keep track of the invoice total vs the promised return total (how much the customer promised to return)
1254
BigDecimal JavaDoc invoiceTotal = ZERO;
1255            BigDecimal JavaDoc promisedTotal = ZERO;
1256
1257            // loop through shipment receipts to create invoice items and return item billings for each item and adjustment
1258
int invoiceItemSeqNum = 1;
1259            String JavaDoc invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
1260            for (Iterator iter = receipts.iterator(); iter.hasNext(); ) {
1261                GenericValue receipt = (GenericValue) iter.next();
1262
1263                // we need the related return item and product
1264
GenericValue returnItem = receipt.getRelatedOneCache("ReturnItem");
1265                GenericValue product = returnItem.getRelatedOneCache("Product");
1266
1267                // extract the return price as a big decimal for convenience
1268
BigDecimal JavaDoc returnPrice = returnItem.getBigDecimal("returnPrice");
1269
1270                // determine invoice item type from the return item type
1271
String JavaDoc invoiceItemTypeId = getInvoiceItemType(delegator, returnItem.getString("returnItemTypeId"), "CUST_RTN_INVOICE", null);
1272                if (invoiceItemTypeId == null) {
1273                    return ServiceUtil.returnError(errorMsg + "No known invoice item type for the return item type ["
1274                            + returnItem.getString("returnItemTypeId") + "]");
1275                }
1276
1277                // create the invoice item for this shipment receipt
1278
input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", receipt.get("quantityAccepted"));
1279                input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack
1280
input.put("amount", returnItem.get("returnPrice")); // this service requires Double
1281
input.put("productId", returnItem.get("productId"));
1282                input.put("taxableFlag", product.get("taxable"));
1283                input.put("description", returnItem.get("description"));
1284                // TODO: what about the productFeatureId?
1285
input.put("userLogin", userLogin);
1286                serviceResults = dispatcher.runSync("createInvoiceItem", input);
1287                if (ServiceUtil.isError(serviceResults)) {
1288                    return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1289                }
1290
1291                // copy the return item information into ReturnItemBilling
1292
input = UtilMisc.toMap("returnId", returnId, "returnItemSeqId", returnItem.get("returnItemSeqId"),
1293                        "invoiceId", invoiceId);
1294                input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack
1295
input.put("shipmentReceiptId", receipt.get("receiptId"));
1296                input.put("quantity", receipt.get("quantityAccepted"));
1297                input.put("amount", returnItem.get("returnPrice")); // this service requires Double
1298
input.put("userLogin", userLogin);
1299                serviceResults = dispatcher.runSync("createReturnItemBilling", input);
1300                if (ServiceUtil.isError(serviceResults)) {
1301                    return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1302                }
1303                if (Debug.verboseOn()) {
1304                    Debug.logVerbose("Creating Invoice Item with amount " + returnPrice + " and quantity " + receipt.getBigDecimal("quantityAccepted")
1305                            + " for shipment receipt [" + receipt.getString("receiptId") + "]", module);
1306                }
1307
1308                // increment the seqId counter after creating the invoice item and return item billing
1309
invoiceItemSeqNum += 1;
1310                invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
1311
1312                // keep a running total (note: a returnItem may have many receipts. hence, the promised total quantity is the receipt quantityAccepted + quantityRejected)
1313
BigDecimal JavaDoc actualAmount = returnPrice.multiply(receipt.getBigDecimal("quantityAccepted")).setScale(decimals, rounding);
1314                BigDecimal JavaDoc promisedAmount = returnPrice.multiply(receipt.getBigDecimal("quantityAccepted").add(receipt.getBigDecimal("quantityRejected"))).setScale(decimals, rounding);
1315                invoiceTotal = invoiceTotal.add(actualAmount).setScale(decimals, rounding);
1316                promisedTotal = promisedTotal.add(promisedAmount).setScale(decimals, rounding);
1317
1318                // for each adjustment related to this ReturnItem, create a separate invoice item
1319
List adjustments = returnItem.getRelatedCache("ReturnAdjustment");
1320                for (Iterator adjIter = adjustments.iterator(); adjIter.hasNext(); ) {
1321                    GenericValue adjustment = (GenericValue) adjIter.next();
1322
1323                    // determine invoice item type from the return item type
1324
invoiceItemTypeId = getInvoiceItemType(delegator, adjustment.getString("returnAdjustmentTypeId"), "CUST_RTN_INVOICE", null);
1325                    if (invoiceItemTypeId == null) {
1326                        return ServiceUtil.returnError(errorMsg + "No known invoice item type for the return adjustment type ["
1327                                + adjustment.getString("returnAdjustmentTypeId") + "]");
1328                    }
1329
1330                    // prorate the adjustment amount by the returned amount; do not round ratio
1331
BigDecimal JavaDoc ratio = receipt.getBigDecimal("quantityAccepted").divide(returnItem.getBigDecimal("returnQuantity"), 100, rounding);
1332                    BigDecimal JavaDoc amount = adjustment.getBigDecimal("amount");
1333                    amount = amount.multiply(ratio).setScale(decimals, rounding);
1334                    if (Debug.verboseOn()) {
1335                        Debug.logVerbose("Creating Invoice Item with amount " + adjustment.getBigDecimal("amount") + " prorated to " + amount
1336                                + " for return adjustment [" + adjustment.getString("returnAdjustmentId") + "]", module);
1337                    }
1338
1339                    // prepare invoice item data for this adjustment
1340
input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", new Double JavaDoc(1.0));
1341                    input.put("amount", new Double JavaDoc(amount.doubleValue()));
1342                    input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack
1343
input.put("productId", returnItem.get("productId"));
1344                    input.put("description", adjustment.get("description"));
1345                    input.put("overrideGlAccountId", adjustment.get("overrideGlAccountId"));
1346                    input.put("taxAuthPartyId", adjustment.get("taxAuthPartyId"));
1347                    input.put("taxAuthGeoId", adjustment.get("taxAuthGeoId"));
1348                    input.put("userLogin", userLogin);
1349
1350                    // only set taxable flag when the adjustment is not a tax
1351
// TODO: Note that we use the value of Product.taxable here. This is not an ideal solution. Instead, use returnAdjustment.includeInTax
1352
if (adjustment.get("returnAdjustmentTypeId").equals("RET_SALES_TAX_ADJ")) {
1353                        input.put("taxableFlag", "N");
1354                    }
1355
1356                    // create the invoice item
1357
serviceResults = dispatcher.runSync("createInvoiceItem", input);
1358                    if (ServiceUtil.isError(serviceResults)) {
1359                        return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1360                    }
1361
1362                    // increment the seqId counter
1363
invoiceItemSeqNum += 1;
1364                    invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
1365
1366                    // keep a running total (promised adjustment in this case is the same as the invoice adjustment)
1367
invoiceTotal = invoiceTotal.add(amount).setScale(decimals, rounding);
1368                    promisedTotal = promisedTotal.add(amount).setScale(decimals, rounding);
1369                }
1370            }
1371
1372            // ratio of the invoice total to the promised total so far or zero if the amounts were zero
1373
BigDecimal JavaDoc actualToPromisedRatio = ZERO;
1374            if (invoiceTotal.signum() != 0) {
1375                actualToPromisedRatio = invoiceTotal.divide(promisedTotal, 100, rounding); // do not round ratio
1376
}
1377
1378            // loop through return-wide adjustments and create invoice items for each
1379
List adjustments = returnHeader.getRelatedByAndCache("ReturnAdjustment", UtilMisc.toMap("returnItemSeqId", "_NA_"));
1380            for (Iterator iter = adjustments.iterator(); iter.hasNext(); ) {
1381                GenericValue adjustment = (GenericValue) iter.next();
1382
1383                // determine invoice item type from the return item type
1384
String JavaDoc invoiceItemTypeId = getInvoiceItemType(delegator, adjustment.getString("returnAdjustmentTypeId"), "CUST_RTN_INVOICE", null);
1385                if (invoiceItemTypeId == null) {
1386                    return ServiceUtil.returnError(errorMsg + "No known invoice item type for the return adjustment type ["
1387                            + adjustment.getString("returnAdjustmentTypeId") + "]");
1388                }
1389
1390                // prorate the adjustment amount by the actual to promised ratio
1391
BigDecimal JavaDoc amount = adjustment.getBigDecimal("amount").multiply(actualToPromisedRatio).setScale(decimals, rounding);
1392                if (Debug.verboseOn()) {
1393                    Debug.logVerbose("Creating Invoice Item with amount " + adjustment.getBigDecimal("amount") + " prorated to " + amount
1394                            + " for return adjustment [" + adjustment.getString("returnAdjustmentId") + "]", module);
1395                }
1396
1397                // prepare the invoice item for the return-wide adjustment
1398
input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", new Double JavaDoc(1.0));
1399                input.put("amount", new Double JavaDoc(amount.doubleValue()));
1400                input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack
1401
input.put("description", adjustment.get("description"));
1402                input.put("overrideGlAccountId", adjustment.get("overrideGlAccountId"));
1403                input.put("taxAuthPartyId", adjustment.get("taxAuthPartyId"));
1404                input.put("taxAuthGeoId", adjustment.get("taxAuthGeoId"));
1405                input.put("userLogin", userLogin);
1406
1407                // XXX TODO Note: we need to implement ReturnAdjustment.includeInTax for this to work properly
1408
input.put("taxableFlag", adjustment.get("includeInTax"));
1409
1410                // create the invoice item
1411
serviceResults = dispatcher.runSync("createInvoiceItem", input);
1412                if (ServiceUtil.isError(serviceResults)) {
1413                    return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1414                }
1415
1416                // increment the seqId counter
1417
invoiceItemSeqNum += 1;
1418                invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, 2);
1419            }
1420        
1421            // Set the invoice to READY
1422
serviceResults = dispatcher.runSync("setInvoiceStatus", UtilMisc.toMap("invoiceId", invoiceId, "statusId", "INVOICE_READY", "userLogin", userLogin));
1423            if (ServiceUtil.isError(serviceResults)) {
1424                return ServiceUtil.returnError(errorMsg, null, null, serviceResults);
1425            }
1426
1427            // return the invoiceId
1428
Map results = ServiceUtil.returnSuccess();
1429            results.put("invoiceId", invoiceId);
1430            return results;
1431        } catch (GenericServiceException e) {
1432            Debug.logError(e, errorMsg + e.getMessage(), module);
1433            return ServiceUtil.returnError(errorMsg + e.getMessage());
1434        } catch (GenericEntityException e) {
1435            Debug.logError(e, errorMsg + e.getMessage(), module);
1436            return ServiceUtil.returnError(errorMsg + e.getMessage());
1437        }
1438    }
1439
1440    public static Map checkInvoicePaymentApplications(DispatchContext ctx, Map context) {
1441        GenericDelegator delegator = ctx.getDelegator();
1442        LocalDispatcher dispatcher = ctx.getDispatcher();
1443        GenericValue userLogin = (GenericValue) context.get("userLogin");
1444
1445        if (decimals == -1 || rounding == -1) {
1446            return ServiceUtil.returnError("Arithmetic properties for Invoice services not configured properly. Cannot proceed.");
1447        }
1448
1449        String JavaDoc invoiceId = (String JavaDoc) context.get("invoiceId");
1450        GenericValue invoice = null ;
1451        try {
1452            invoice = delegator.findByPrimaryKey("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
1453        } catch( GenericEntityException e ) {
1454            Debug.logError(e, "Problem getting Invoice for Invoice ID" + invoiceId, module);
1455            return ServiceUtil.returnError("Problem getting Invoice for Invoice ID" + invoiceId);
1456        }
1457        
1458        // Ignore invoices that aren't ready yet
1459
if (! invoice.getString("statusId").equals("INVOICE_READY")) {
1460            return ServiceUtil.returnSuccess();
1461        }
1462        
1463        List paymentAppl = null;
1464        try {
1465            paymentAppl = delegator.findByAnd("PaymentApplication", UtilMisc.toMap("invoiceId", invoiceId));
1466        } catch (GenericEntityException e) {
1467            Debug.logError(e, "Problem getting PaymentApplication(s) for Invoice #" + invoiceId, module);
1468            return ServiceUtil.returnError("Problem getting PaymentApplication(s) for Invoice #" + invoiceId);
1469        }
1470
1471        Map payments = new HashMap();
1472        Timestamp JavaDoc paidDate = null;
1473        if (paymentAppl != null) {
1474            Iterator pai = paymentAppl.iterator();
1475            while (pai.hasNext()) {
1476                GenericValue payAppl = (GenericValue) pai.next();
1477                payments.put(payAppl.getString("paymentId"), payAppl.getBigDecimal("amountApplied"));
1478                
1479                // paidDate will be the last date (chronologically) of all the Payments applied to this invoice
1480
try {
1481                    GenericValue Payment = payAppl.getRelatedOne("Payment");
1482                    Timestamp JavaDoc paymentDate = Payment.getTimestamp("effectiveDate");
1483                    if (paymentDate != null) {
1484                        if ((paidDate == null) || (paidDate.before(paymentDate))) {
1485                            paidDate = paymentDate;
1486                        }
1487                    }
1488                } catch (GenericEntityException ex) {
1489                    Debug.logError(ex, "Cannot get payment for application [" + payAppl + "]", module);
1490                    return ServiceUtil.returnError("Cannot get payment for application [" + payAppl + "] due to " + ex.getMessage());
1491                }
1492                
1493            }
1494        }
1495
1496        BigDecimal JavaDoc totalPayments = ZERO;
1497        Iterator pi = payments.keySet().iterator();
1498        while (pi.hasNext()) {
1499            String JavaDoc paymentId = (String JavaDoc) pi.next();
1500            BigDecimal JavaDoc amount = (BigDecimal JavaDoc) payments.get(paymentId);
1501            if (amount == null) amount = ZERO;
1502            totalPayments = totalPayments.add(amount).setScale(decimals, rounding);
1503        }
1504
1505        if (totalPayments.signum() == 1) {
1506            BigDecimal JavaDoc invoiceTotal = InvoiceWorker.getInvoiceTotalBd(delegator, invoiceId);
1507            if (Debug.verboseOn()) {
1508                Debug.logVerbose("Invoice #" + invoiceId + " total: " + invoiceTotal, module);
1509                Debug.logVerbose("Total payments : " + totalPayments, module);
1510            }
1511            if (totalPayments.compareTo(invoiceTotal) >= 0) { // this checks that totalPayments is greater than or equal to invoiceTotal
1512
// this invoice is paid
1513
Map svcCtx = UtilMisc.toMap("statusId", "INVOICE_PAID", "invoiceId", invoiceId,
1514                        "paidDate", paidDate, "userLogin", userLogin);
1515                try {
1516                    dispatcher.runSync("setInvoiceStatus", svcCtx);
1517                } catch (GenericServiceException e) {
1518                    Debug.logError(e, "Problem changing invoice status : " + svcCtx, module);
1519                    return ServiceUtil.returnError("Problem changing invoice status");
1520                }
1521            }
1522        } else {
1523            Debug.log("No payments found for Invoice #" + invoiceId, module);
1524        }
1525
1526        return ServiceUtil.returnSuccess();
1527    }
1528
1529    private static BigDecimal JavaDoc calcHeaderAdj(GenericDelegator delegator, GenericValue adj, String JavaDoc invoiceTypeId, String JavaDoc invoiceId, String JavaDoc invoiceItemSeqId,
1530            BigDecimal JavaDoc divisor, BigDecimal JavaDoc multiplier, BigDecimal JavaDoc baseAmount, int decimals, int rounding, GenericValue userLogin, LocalDispatcher dispatcher, Locale locale) {
1531        BigDecimal JavaDoc adjAmount = ZERO;
1532        if (adj.get("amount") != null) {
1533            
1534            // pro-rate the amount
1535
BigDecimal JavaDoc baseAdjAmount = adj.getBigDecimal("amount");
1536            BigDecimal JavaDoc amount = ZERO;
1537            // make sure the divisor is not 0 to avoid NaN problems; just leave the amount as 0 and skip it in essense
1538
if (divisor.signum() != 0) {
1539                // multiply first then divide to avoid rounding errors
1540
amount = baseAmount.multiply(multiplier).divide(divisor, decimals, rounding);
1541            }
1542            if (amount.signum() != 0) {
1543                Map createInvoiceItemContext = FastMap.newInstance();
1544                createInvoiceItemContext.put("invoiceId", invoiceId);
1545                createInvoiceItemContext.put("invoiceItemSeqId", invoiceItemSeqId);
1546                createInvoiceItemContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, adj.getString("orderAdjustmentTypeId"), invoiceTypeId, "INVOICE_ADJ"));
1547                createInvoiceItemContext.put("description", adj.get("description"));
1548                createInvoiceItemContext.put("quantity", new Double JavaDoc(1));
1549                createInvoiceItemContext.put("amount", new Double JavaDoc(amount.doubleValue()));
1550                createInvoiceItemContext.put("overrideGlAccountId", adj.get("overrideGlAccountId"));
1551                //createInvoiceItemContext.put("productId", orderItem.get("productId"));
1552
//createInvoiceItemContext.put("productFeatureId", orderItem.get("productFeatureId"));
1553
//createInvoiceItemContext.put("uomId", "");
1554
//createInvoiceItemContext.put("taxableFlag", product.get("taxable"));
1555
createInvoiceItemContext.put("taxAuthPartyId", adj.get("taxAuthPartyId"));
1556                createInvoiceItemContext.put("taxAuthGeoId", adj.get("taxAuthGeoId"));
1557                createInvoiceItemContext.put("taxAuthorityRateSeqId", adj.get("taxAuthorityRateSeqId"));
1558                createInvoiceItemContext.put("userLogin", userLogin);
1559
1560                Map createInvoiceItemResult = null;
1561                try {
1562                    createInvoiceItemResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemContext);
1563                } catch( GenericServiceException e ) {
1564                    String JavaDoc errMsg = "Service/other problem creating InvoiceItem from order header adjustment: " + e.toString();
1565                    Debug.logError(e, errMsg, module);
1566                    ServiceUtil.returnError(errMsg);
1567                }
1568                if (ServiceUtil.isError(createInvoiceItemResult)) {
1569                    ServiceUtil.returnError("Error creating InvoiceItem from order header adjustment", null, null, createInvoiceItemResult);
1570                }
1571
1572                // Create the OrderAdjustmentBilling record
1573
Map createOrderAdjustmentBillingContext = FastMap.newInstance();
1574                createOrderAdjustmentBillingContext.put("orderAdjustmentId", adj.getString("orderAdjustmentId"));
1575                createOrderAdjustmentBillingContext.put("invoiceId", invoiceId);
1576                createOrderAdjustmentBillingContext.put("invoiceItemSeqId", invoiceItemSeqId);
1577                createOrderAdjustmentBillingContext.put("amount", new Double JavaDoc(amount.doubleValue()));
1578                createOrderAdjustmentBillingContext.put("userLogin", userLogin);
1579
1580                try {
1581                    Map createOrderAdjustmentBillingResult = dispatcher.runSync("createOrderAdjustmentBilling", createOrderAdjustmentBillingContext);
1582                } catch( GenericServiceException e ) {
1583                    ServiceUtil.returnError(UtilProperties.getMessage(resource,"AccountingErrorCreatingOrderAdjustmentBillingFromOrder",locale), null, null, createOrderAdjustmentBillingContext);
1584                }
1585
1586            }
1587            amount.setScale(decimals, rounding);
1588            adjAmount = amount;
1589        }
1590
1591        Debug.logInfo("adjAmount: " + adjAmount + ", divisor: " + divisor + ", multiplier: " + multiplier +
1592                ", invoiceTypeId: " + invoiceTypeId + ", invoiceId: " + invoiceId + ", itemSeqId: " + invoiceItemSeqId +
1593                ", decimals: " + decimals + ", rounding: " + rounding + ", adj: " + adj, module);
1594        return adjAmount;
1595    }
1596
1597    /* Creates InvoiceTerm entries for a list of terms, which can be BillingAccountTerms, OrderTerms, etc. */
1598    private static void createInvoiceTerms(GenericDelegator delegator, LocalDispatcher dispatcher, String JavaDoc invoiceId, List terms, GenericValue userLogin) {
1599        if ((terms != null) && (terms.size() > 0)) {
1600            for (Iterator termsIter = terms.iterator(); termsIter.hasNext(); ) {
1601                GenericValue term = (GenericValue) termsIter.next();
1602
1603                Map createInvoiceTermContext = FastMap.newInstance();
1604                createInvoiceTermContext.put("invoiceId", invoiceId);
1605                createInvoiceTermContext.put("invoiceItemSeqId", "_NA_");
1606                createInvoiceTermContext.put("termTypeId", term.get("termTypeId"));
1607                createInvoiceTermContext.put("termValue", term.get("termValue"));
1608                createInvoiceTermContext.put("termDays", term.get("termDays"));
1609                createInvoiceTermContext.put("uomId", term.get("uomId"));
1610                createInvoiceTermContext.put("userLogin", userLogin);
1611
1612                Map createInvoiceTermResult = null;
1613                try {
1614                    createInvoiceTermResult = dispatcher.runSync("createInvoiceTerm", createInvoiceTermContext);
1615                } catch( GenericServiceException e ) {
1616                    String JavaDoc errMsg = "Service/other problem creating InvoiceTerm from order: " + e.toString();
1617                    Debug.logError(e, errMsg, module);
1618                    ServiceUtil.returnError(errMsg);
1619                }
1620                if (ServiceUtil.isError(createInvoiceTermResult)) {
1621                    ServiceUtil.returnError("Error creating InvoiceTerm from order", null, null, createInvoiceTermResult);
1622                }
1623            }
1624        }
1625    }
1626
1627    /**
1628     * Service to add payment application records to indicate which invoices
1629     * have been paid/received. For invoice processing, this service works on
1630     * the invoice level when 'invoiceProcessing' parameter is set to "Y" else
1631     * it works on the invoice item level.
1632     *
1633     * @author <a HREF="mailto:h.bakker@antwebsystems.com">Hans Bakker</a>
1634     */

1635    public static Map updatePaymentApplication(DispatchContext dctx, Map context) {
1636        Double JavaDoc amountApplied = (Double JavaDoc) context.get("amountApplied");
1637        if (amountApplied != null) {
1638            BigDecimal JavaDoc amountAppliedBd = new BigDecimal JavaDoc(amountApplied.toString());
1639            context.put("amountApplied", amountAppliedBd);
1640        } else {
1641            BigDecimal JavaDoc amountAppliedBd = ZERO;
1642            context.put("amountApplied", amountAppliedBd);
1643        }
1644
1645        return updatePaymentApplicationBd(dctx, context);
1646    }
1647
1648    public static Map updatePaymentApplicationBd(DispatchContext dctx, Map context) {
1649        GenericDelegator delegator = dctx.getDelegator();
1650        LocalDispatcher dispatcher = dctx.getDispatcher();
1651
1652        if (decimals == -1 || rounding == -1) {
1653            return ServiceUtil.returnError("Arithmetic properties for Invoice services not configured properly. Cannot proceed.");
1654        }
1655
1656        String JavaDoc defaultInvoiceProcessing = UtilProperties.getPropertyValue("AccountingConfig","invoiceProcessing").toString();
1657        
1658        boolean debug = true; // show processing messages in the log..or
1659
// not....
1660

1661        // a 'y' in invoiceProssesing wil reverse the default processing
1662
String JavaDoc changeProcessing = (String JavaDoc) context.get("invoiceProcessing");
1663        String JavaDoc invoiceId = (String JavaDoc) context.get("invoiceId");
1664        String JavaDoc invoiceItemSeqId = (String JavaDoc) context.get("invoiceItemSeqId");
1665        String JavaDoc paymentId = (String JavaDoc) context.get("paymentId");
1666        String JavaDoc toPaymentId = (String JavaDoc) context.get("toPaymentId");
1667        String JavaDoc paymentApplicationId = (String JavaDoc) context.get("paymentApplicationId");
1668        BigDecimal JavaDoc amountApplied = (BigDecimal JavaDoc) context.get("amountApplied");
1669        String JavaDoc billingAccountId = (String JavaDoc) context.get("billingAccountId");
1670        String JavaDoc taxAuthGeoId = (String JavaDoc) context.get("taxAuthGeoId");
1671
1672        List errorMessageList = new LinkedList();
1673        String JavaDoc successMessage = null;
1674        if (debug) Debug.logInfo("Input parameters..." +
1675                " defaultInvoiceProcessing: " + defaultInvoiceProcessing +
1676                " changeDefaultInvoiceProcessing: " + changeProcessing +
1677                " paymentApplicationId: " + paymentApplicationId +
1678                " PaymentId: " + paymentId +
1679                " InvoiceId: " + invoiceId +
1680                " InvoiceItemSeqId: " + invoiceItemSeqId +
1681                " BillingAccountId: " + billingAccountId +
1682                " toPaymentId: " + toPaymentId +
1683                " amountApplied: " + amountApplied.toString() +
1684                " TaxAuthGeoId: " + taxAuthGeoId, module);
1685
1686        if (changeProcessing == null) changeProcessing = new String JavaDoc("N"); // not provided, so no change
1687

1688        boolean invoiceProcessing = true;
1689        if (defaultInvoiceProcessing.equals("YY")) invoiceProcessing = true;
1690
1691        else if (defaultInvoiceProcessing.equals("NN")) invoiceProcessing = false;
1692
1693        else if (defaultInvoiceProcessing.equals("Y")) {
1694            if (changeProcessing.equals("Y")) invoiceProcessing = false;
1695            else invoiceProcessing = true;
1696        }
1697
1698        else if (defaultInvoiceProcessing.equals("N")) {
1699            if (changeProcessing.equals("Y")) invoiceProcessing = true;
1700            else invoiceProcessing = false;
1701        }
1702
1703        // on a new paymentApplication check if only billing or invoice or tax
1704
// id is provided not 2,3... BUT a combination of billingAccountId and invoiceId is permitted - that's how you use a
1705
// Billing Account to pay for an Invoice
1706
if (paymentApplicationId == null) {
1707            int count = 0;
1708            if (invoiceId != null) count++;
1709            if (toPaymentId != null) count++;
1710            if (billingAccountId != null) count++;
1711            if (taxAuthGeoId != null) count++;
1712            if ((billingAccountId != null) && (invoiceId != null)) count--;
1713            if (count != 1) {
1714                errorMessageList.add("- Specify either Invoice or toPayment or billing account or taxGeoId....\n");
1715            }
1716        }
1717
1718        // avoid null pointer exceptions.
1719
if (amountApplied == null) amountApplied = new BigDecimal JavaDoc("0");
1720        // makes no sense to have an item numer without an invoice number
1721
if (invoiceId == null) invoiceItemSeqId = null;
1722
1723        // retrieve all information and perform checking on the retrieved info.....
1724

1725        // Payment.....
1726
BigDecimal JavaDoc paymentApplyAvailable = new BigDecimal JavaDoc("0");
1727        // amount available on the payment reduced by the already applied amounts
1728
BigDecimal JavaDoc amountAppliedMax = new BigDecimal JavaDoc("0");
1729        // the maximum that can be applied taking payment,invoice,invoiceitem,billing account in concideration
1730
// if maxApplied is missing, this value can be used
1731
GenericValue payment = null;
1732        if (paymentId == null || paymentId.equals("")) {
1733            errorMessageList.add("- PaymentId blank or not supplied.... .....\n");
1734        } else {
1735            try {
1736                payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId));
1737            } catch (GenericEntityException e) {
1738                ServiceUtil.returnError(e.getMessage());
1739            }
1740            if (payment == null) {
1741                errorMessageList.add("- Payment record not found, ID: " + paymentId + "\n");
1742            }
1743            paymentApplyAvailable = payment.getBigDecimal("amount").subtract(PaymentWorker.getPaymentAppliedBd(payment)).setScale(decimals,rounding);
1744
1745            if (payment.getString("statusId").equals("PMNT_CANCELLED")) {
1746                errorMessageList.add("- Payment(" + paymentId + ") is cancelled and cannot be applied\n");
1747            }
1748
1749            // if the amount to apply is 0 give it amount the payment still need
1750
// to apply
1751
if (amountApplied.signum() == 0) {
1752                amountAppliedMax = paymentApplyAvailable;
1753            }
1754
1755            if (paymentApplicationId == null) {
1756                // only check for new application records, update on existing records is checked in the paymentApplication section
1757
if (paymentApplyAvailable.signum() == 0) {
1758                    errorMessageList.add("- Payment(" + paymentId + ") is already fully applied\n");
1759                } else {
1760                    // check here for too much application if a new record is
1761
// added (paymentApplicationId == null)
1762
if (amountApplied.compareTo(paymentApplyAvailable) > 0) {
1763                        errorMessageList.add("- Payment(" + paymentId + ") has " + paymentApplyAvailable + " to apply but " + amountApplied + " is requested\n");
1764                    }
1765                }
1766            }
1767
1768            if (debug) Debug.logInfo("Payment info retrieved and checked...", module);
1769        }
1770
1771        // the "TO" Payment.....
1772
BigDecimal JavaDoc toPaymentApplyAvailable = new BigDecimal JavaDoc("0");
1773        GenericValue toPayment = null;
1774        if (toPaymentId != null && !toPaymentId.equals("")) {
1775            try {
1776                toPayment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", toPaymentId));
1777            } catch (GenericEntityException e) {
1778                ServiceUtil.returnError(e.getMessage());
1779            }
1780            if (toPayment == null) {
1781                errorMessageList.add("- toPayment record not found, ID: " + toPaymentId + "\n");
1782            }
1783            toPaymentApplyAvailable = toPayment.getBigDecimal("amount").subtract(PaymentWorker.getPaymentAppliedBd(toPayment)).setScale(decimals,rounding);
1784
1785            if (toPayment.getString("statusId").equals("PMNT_CANCELLED")) {
1786                errorMessageList.add("- toPayment(" + toPaymentId + ") is cancelled and cannot be applied\n");
1787            }
1788
1789            // if the amount to apply is less then required by the payment reduce it
1790
if (amountAppliedMax.compareTo(toPaymentApplyAvailable) > 0) {
1791                amountAppliedMax = toPaymentApplyAvailable;
1792            }
1793
1794            if (paymentApplicationId == null) {
1795                // only check for new application records, update on existing records is checked in the paymentApplication section
1796
if (toPaymentApplyAvailable.signum() == 0) {
1797                    errorMessageList.add("- toPayment(" + toPaymentId + ") is already fully applied\n");
1798                } else {
1799                    // check here for too much application if a new record is
1800
// added (paymentApplicationId == null)
1801
if (amountApplied.compareTo(toPaymentApplyAvailable) > 0) {
1802                        errorMessageList.add("- toPayment(" + toPaymentId + ") has " + toPaymentApplyAvailable + " to apply but " + amountApplied + " is requested\n");
1803                    }
1804                }
1805            }
1806            
1807            // check if at least one send is the same as one receiver on the other payment
1808
if (!payment.getString("partyIdFrom").equals(toPayment.getString("partyIdTo")) &&
1809                    !payment.getString("partyIdFrom").equals(toPayment.getString("partyIdTo"))) {
1810                errorMessageList.add("- At least the 'from' party should be the same as the 'to' party of the other payment\n");
1811            }
1812
1813            if (debug) Debug.logInfo("toPayment info retrieved and checked...", module);
1814        }
1815
1816        // billing account
1817
GenericValue billingAccount = null;
1818        BigDecimal JavaDoc billingAccountApplyAvailable = new BigDecimal JavaDoc("0");
1819        if (billingAccountId != null && !billingAccountId.equals("")) {
1820            try {
1821                billingAccount = delegator.findByPrimaryKey("BillingAccount", UtilMisc.toMap("billingAccountId", billingAccountId));
1822            } catch (GenericEntityException e) {
1823                ServiceUtil.returnError(e.getMessage());
1824            }
1825            if (billingAccount == null) {
1826                errorMessageList.add("- Billing Account record not found, ID: " + billingAccountId + "\n");
1827            }
1828            
1829            // Get the balance which can be captured, rather than the regular balance, which is how much has already been charged
1830
try {
1831                billingAccountApplyAvailable = BillingAccountWorker.availableToCapture(billingAccount);
1832            } catch (GenericEntityException e) {
1833                ServiceUtil.returnError(e.getMessage());
1834                errorMessageList.add("- Billing Account(" + billingAccountId + ") balance could not be retrieved, see log for more info...(\n");
1835            }
1836
1837            if (paymentApplicationId == null) {
1838                // only check when a new record is created, existing record check is done in the paymentAllication section
1839
// when creating a new PaymentApplication, check if there is sufficient balance in the billing account, but only if the invoiceId is not null
1840
// If you create a PaymentApplication with both billingAccountId and invoiceId, then you're applying a billing account towards an invoice
1841
// If you create a PaymentApplication just with billingAccountId and no invoiceId, then you're adding value to billing account, so it should not matter
1842
// what the previous available balance is
1843
if (invoiceId != null) {
1844                    if (billingAccountApplyAvailable.signum() <= 0) {
1845                        errorMessageList.add("- Billing Account(" + billingAccountId + " doesn't have a positive balance: " + billingAccountApplyAvailable + "\n");
1846                    } else {
1847                        // check here for too much application if a new record is
1848
// added (paymentApplicationId == null)
1849
if (amountApplied.compareTo(billingAccountApplyAvailable) == 1) {
1850                            errorMessageList.add("- Billing Account(" + billingAccountId + ") has " + paymentApplyAvailable + " to apply but " + amountApplied + " is requested\n");
1851                        }
1852                    }
1853                }
1854            }
1855
1856            // check the currency
1857
if (billingAccount.get("accountCurrencyUomId") != null && payment.get("currencyUomId") != null &&
1858                    !billingAccount.getString("accountCurrencyUomId").equals(payment.getString("currencyUomId"))) {
1859                errorMessageList.add("- Currencies are not the same, Billing Account(" + billingAccountId + ") has currency " + billingAccount.getString("accountCurrencyUomId") + " and Payment(" + paymentId + ") has a currency of: " + payment.getString("currencyUomId") + "\n");
1860            }
1861
1862            if (debug) Debug.logInfo("Billing Account info retrieved and checked...", module);
1863        }
1864
1865        // get the invoice (item) information
1866
BigDecimal JavaDoc invoiceApplyAvailable = new BigDecimal JavaDoc("0");
1867        // amount available on the invoice reduced by the already applied amounts
1868
BigDecimal JavaDoc invoiceItemApplyAvailable = new BigDecimal JavaDoc("0");
1869        // amount available on the invoiceItem reduced by the already applied amounts
1870
GenericValue invoice = null;
1871        GenericValue invoiceItem = null;
1872        if (invoiceId != null) {
1873            try {
1874                invoice = delegator.findByPrimaryKey("Invoice", UtilMisc.toMap("invoiceId", invoiceId));
1875            } catch (GenericEntityException e) {
1876                ServiceUtil.returnError(e.getMessage());
1877            }
1878            
1879            if (invoice == null) {
1880                errorMessageList.add("- Invoice record not found, ID: " + invoiceId + "\n");
1881            }
1882            else { // check the invoice and when supplied the invoice item...
1883

1884                if (invoice.getString("statusId").equals("INVOICE_CANCELLED")) {
1885                    errorMessageList.add("- Invoice is cancelled, cannot be applied to, ID: " + invoiceId + "\n");
1886                }
1887                
1888                // check if the invoice already covered by payments
1889
BigDecimal JavaDoc invoiceTotal = InvoiceWorker.getInvoiceTotalBd(invoice);
1890                invoiceApplyAvailable = invoiceTotal.subtract(InvoiceWorker.getInvoiceAppliedBd(invoice)).setScale(decimals, rounding);
1891                
1892                // adjust the amountAppliedMax value if required....
1893
if (invoiceApplyAvailable.compareTo(amountAppliedMax) < 0) {
1894                    amountAppliedMax = invoiceApplyAvailable;
1895                }
1896                
1897                if (invoiceTotal.signum() == 0) {
1898                    errorMessageList.add("- Invoice (" + invoiceId + ") has a total value of zero....cannot apply anything...\n");
1899                } else if (paymentApplicationId == null) {
1900                    // only check for new records here...updates are checked in the paymentApplication section
1901
if (invoiceApplyAvailable.signum() == 0) {
1902                        errorMessageList.add("- Invoice (" + invoiceId + ") is already completely covered by payments... \n");
1903                    }
1904                    // check here for too much application if a new record(s) are
1905
// added (paymentApplicationId == null)
1906
else if (amountApplied.compareTo(invoiceApplyAvailable) > 0) {
1907                        errorMessageList.add("- Invoice(" + invoiceId + ") has " + invoiceApplyAvailable + " to apply but " + amountApplied + " is requested\n");
1908                    }
1909                }
1910                
1911                // check if at least one sender is the same as one receiver on the invoice
1912
if (!payment.getString("partyIdFrom").equals(invoice.getString("partyId")) &&
1913                        !payment.getString("partyIdTo").equals(invoice.getString("partyIdFrom"))) {
1914                    errorMessageList.add("- At least the 'from' party should be the same as the 'to' party of the payment/invoice\n");
1915                }
1916                
1917                if (debug) Debug.logInfo("Invoice info retrieved and checked ...", module);
1918            }
1919            
1920            // if provided check the invoice item.
1921
if (invoiceItemSeqId != null) {
1922                // when itemSeqNr not provided delay checking on invoiceItemSeqId
1923
try {
1924                    invoiceItem = delegator.findByPrimaryKey("InvoiceItem", UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId));
1925                } catch (GenericEntityException e) {
1926                    ServiceUtil.returnError(e.getMessage());
1927                }
1928                
1929                if (invoiceItem == null) {
1930                    errorMessageList.add("- InvoiceItem record not found, ID: " + invoiceId + " " + invoiceItemSeqId + "\n");
1931                } else {
1932                    if (invoiceItem.get("uomId") != null && payment.get("currencyUomId") != null && !invoiceItem.getString("uomId").equals(payment.getString("currencyUomId"))) {
1933                        errorMessageList.add("- Payment currency(" + payment.getString("currencyUomId") + ") and invoice Item currency(" + invoiceItem.getString("uomId") + ") not the same\n");
1934                    } else if (invoice.get("currencyUomId") != null && payment.get("currencyUomId") != null && !invoice.getString("currencyUomId").equals(payment.getString("currencyUomId"))) {
1935                        errorMessageList.add("- Payment currency(" + payment.getString("currencyUomId") + ") and invoice currency(" + invoice.getString("currencyUomId") + ") not the same\n");
1936                    }
1937                    
1938                    // get the invoice item applied value
1939
BigDecimal JavaDoc quantity = null;
1940                    if (invoiceItem.get("quantity") == null) {
1941                        quantity = new BigDecimal JavaDoc("1");
1942                    } else {
1943                        quantity = invoiceItem.getBigDecimal("quantity").setScale(decimals,rounding);
1944                    }
1945                    invoiceItemApplyAvailable = invoiceItem.getBigDecimal("amount").multiply(quantity).subtract(InvoiceWorker.getInvoiceItemAppliedBd(invoiceItem));
1946                    // check here for too much application if a new record is added
1947
// (paymentApplicationId == null)
1948
if (paymentApplicationId == null && amountApplied.compareTo(invoiceItemApplyAvailable) > 0) {
1949                        // new record
1950
errorMessageList.add("- Invoice(" + invoiceId + ") item(" + invoiceItemSeqId + ") has " + invoiceItemApplyAvailable + " to apply but " + amountApplied + " is requested\n");
1951                    }
1952                    
1953                }
1954                if (debug) Debug.logInfo("InvoiceItem info retrieved and checked against the Invoice (currency and amounts) ...", module);
1955            }
1956        }
1957
1958        // get the application record if the applicationId is supplied if not
1959
// create empty record.
1960
BigDecimal JavaDoc newInvoiceApplyAvailable = invoiceApplyAvailable;
1961        // amount available on the invoice taking into account if the invoiceItemnumber has changed
1962
BigDecimal JavaDoc newInvoiceItemApplyAvailable = invoiceItemApplyAvailable;
1963        // amount available on the invoiceItem taking into account if the itemnumber has changed
1964
BigDecimal JavaDoc newToPaymentApplyAvailable = toPaymentApplyAvailable;
1965        // amount available on the Billing Account taking into account if the billing account number has changed
1966
BigDecimal JavaDoc newBillingAccountApplyAvailable = billingAccountApplyAvailable;
1967        // amount available on the Billing Account taking into account if the billing account number has changed
1968
BigDecimal JavaDoc newPaymentApplyAvailable = paymentApplyAvailable;
1969        GenericValue paymentApplication = null;
1970        if (paymentApplicationId == null) {
1971            paymentApplication = delegator.makeValue("PaymentApplication", null);
1972            // prepare for creation
1973
} else { // retrieve existing paymentApplication
1974
try {
1975                paymentApplication = delegator.findByPrimaryKey("PaymentApplication", UtilMisc.toMap("paymentApplicationId", paymentApplicationId));
1976            } catch (GenericEntityException e) {
1977                ServiceUtil.returnError(e.getMessage());
1978            }
1979
1980            if (paymentApplication == null) {
1981                errorMessageList.add("- PaymentApplication record not found, ID: " + paymentApplicationId + "\n");
1982                paymentApplicationId = null;
1983            } else {
1984
1985                // if both invoiceId and BillingId is entered there was
1986
// obviously a change
1987
// only take the newly entered item, same for tax authority and toPayment
1988
if (paymentApplication.get("invoiceId") == null && invoiceId != null) {
1989                    billingAccountId = null;
1990                    taxAuthGeoId = null;
1991                    toPaymentId = null;
1992                } else if (paymentApplication.get("toPaymentId") == null && toPaymentId != null) {
1993                    invoiceId = null;
1994                    invoiceItemSeqId = null;
1995                    taxAuthGeoId = null;
1996                    billingAccountId = null;
1997                } else if (paymentApplication.get("billingAccountId") == null && billingAccountId != null) {
1998                    invoiceId = null;
1999                    invoiceItemSeqId = null;
2000                    toPaymentId = null;
2001                    taxAuthGeoId = null;
2002                } else if (paymentApplication.get("taxAuthGeoId") == null && taxAuthGeoId != null) {
2003                    invoiceId = null;
2004                    invoiceItemSeqId = null;
2005                    toPaymentId = null;
2006                    billingAccountId = null;
2007                }
2008
2009                // check if the payment for too much application if an existing
2010
// application record is changed
2011
newPaymentApplyAvailable = paymentApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied).setScale(decimals, rounding);
2012                if (newPaymentApplyAvailable.compareTo(ZERO) < 0) {
2013                    errorMessageList.add("- Payment(" + paymentId + ") has an amount(" + paymentApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")) + ") to apply and the " + amountApplied + " specified is too much\n");
2014                }
2015
2016                if (invoiceId != null) {
2017                    // only when we are processing an invoice on existing paymentApplication check invoice item for to much application if the invoice
2018
// number did not change
2019
if (invoiceId.equals(paymentApplication .getString("invoiceId"))) {
2020                        // check if both the itemNumbers are null then this is a
2021
// record for the whole invoice
2022
if (invoiceItemSeqId == null && paymentApplication.get("invoiceItemSeqId") == null) {
2023                            newInvoiceApplyAvailable = invoiceApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied).setScale(decimals, rounding);
2024                            if (invoiceApplyAvailable.compareTo(ZERO) < 0) {
2025                                errorMessageList.add("- Update would apply " + newInvoiceApplyAvailable.negate() + " to much on this Invoice(" + invoiceId + ")\n");
2026                            }
2027                        } else if (invoiceItemSeqId == null && paymentApplication.get("invoiceItemSeqId") != null) {
2028                            // check if the item number changed from a real Item number to a null value
2029
newInvoiceApplyAvailable = invoiceApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied).setScale(decimals, rounding);
2030                            if (invoiceApplyAvailable.compareTo(ZERO) < 0) {
2031                                errorMessageList.add("- Update would apply " + newInvoiceApplyAvailable.negate() + " to much on this Invoice(" + invoiceId + ")\n");
2032                            }
2033                        } else if (invoiceItemSeqId != null && paymentApplication.get("invoiceItemSeqId") == null) {
2034                            // check if the item number changed from a null value to
2035
// a real Item number
2036
newInvoiceItemApplyAvailable = invoiceItemApplyAvailable.subtract(amountApplied).setScale(decimals, rounding);
2037                            if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) {
2038                                errorMessageList.add("- Update would apply " + newInvoiceItemApplyAvailable.negate() + " to much on this Invoice item(" + invoiceId + "/" + invoiceItemSeqId + "\n");
2039                            }
2040                        } else if (invoiceItemSeqId.equals(paymentApplication.getString("invoiceItemSeqId"))) {
2041                            // check if the real item numbers the same
2042
// item number the same numeric value
2043
newInvoiceItemApplyAvailable = invoiceItemApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied).setScale(decimals, rounding);
2044                            if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) {
2045                                errorMessageList.add("- Update would apply " + newInvoiceItemApplyAvailable.negate() + " to much on this Invoice item(" + invoiceId + "/" + invoiceItemSeqId + "\n");
2046                            }
2047                        } else {
2048                            // item number changed only check new item
2049
newInvoiceItemApplyAvailable = invoiceItemApplyAvailable.add(amountApplied).setScale(decimals, rounding);
2050                            if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) {
2051                                errorMessageList.add("- Update would apply " + newInvoiceItemApplyAvailable.negate() + " to much on this Invoice item\n");
2052                            }
2053                        }
2054
2055                        // if the amountApplied = 0 give it the higest possible
2056
// value
2057
if (amountApplied.signum() == 0) {
2058                            if (newInvoiceItemApplyAvailable.compareTo(newPaymentApplyAvailable) < 0) {
2059                                amountApplied = newInvoiceItemApplyAvailable;
2060                                // from the item number
2061
} else {
2062                                amountApplied = newPaymentApplyAvailable;
2063                                // from the payment
2064
}
2065                        }
2066
2067                        // check the invoice
2068
newInvoiceApplyAvailable = invoiceApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied").subtract(amountApplied)).setScale(decimals, rounding);
2069                        if (newInvoiceApplyAvailable.compareTo(ZERO) < 0) {
2070                            errorMessageList.add("- Invoice (" + invoiceId + ") has only " + invoiceApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")) + " left which it not applied, so " + amountApplied + " is too much\n");
2071                        }
2072                    }
2073                }
2074
2075                // check the toPayment account when only the amountApplied has
2076
// changed,
2077
if (toPaymentId != null && toPaymentId.equals(paymentApplication.getString("toPaymentId"))) {
2078                    newToPaymentApplyAvailable = toPaymentApplyAvailable.subtract(paymentApplication.getBigDecimal("amountApplied")).add(amountApplied).setScale(decimals, rounding);
2079                    if (newToPaymentApplyAvailable.compareTo(ZERO) < 0) {
2080                        errorMessageList.add("- toPaymentId(" + toPaymentId + ") has only " + newToPaymentApplyAvailable + " available so " + amountApplied + " is too much\n");
2081                    }
2082                } else if (toPaymentId != null) {
2083                    // billing account entered number has changed so we have to
2084
// check the new billing account number.
2085
newToPaymentApplyAvailable = toPaymentApplyAvailable.add(amountApplied).setScale(decimals, rounding);
2086                    if (newToPaymentApplyAvailable.compareTo(ZERO) < 0) {
2087                        errorMessageList.add("- toPaymentId(" + toPaymentId + ") has only "+ newToPaymentApplyAvailable + " available so " + amountApplied + " is too much\n");
2088                    }
2089
2090                }
2091                // check the billing account when only the amountApplied has
2092
// changed,
2093
// change in account number is already checked in the billing
2094
// account section
2095
if (billingAccountId != null && billingAccountId.equals(paymentApplication.getString("billingAccountId"))) {
2096                    newBillingAccountApplyAvailable = billingAccountApplyAvailable.subtract(paymentApplication.getBigDecimal("amountApplied")).add(amountApplied).setScale(decimals, rounding);
2097                    if (newBillingAccountApplyAvailable.compareTo(ZERO) < 0) {
2098                        errorMessageList.add("- Billing Account(" + billingAccountId + ") has only " + newBillingAccountApplyAvailable + " available so " + amountApplied + " is too much\n");
2099                    }
2100                } else if (billingAccountId != null) {
2101                    // billing account entered number has changed so we have to
2102
// check the new billing account number.
2103
newBillingAccountApplyAvailable = billingAccountApplyAvailable.add(amountApplied).setScale(decimals, rounding);
2104                    if (newBillingAccountApplyAvailable.compareTo(ZERO) < 0) {
2105                        errorMessageList.add("- Billing Account(" + billingAccountId + ") has only "+ newBillingAccountApplyAvailable + " available so " + amountApplied + " is too much\n");
2106                    }
2107
2108                }
2109            }
2110            if (debug) Debug.logInfo("paymentApplication record info retrieved and checked...", module);
2111        }
2112
2113        // show the maximumus what can be added in the payment application file.
2114
if (debug) {
2115            String JavaDoc extra = new String JavaDoc("");
2116            if (invoiceItemSeqId != null) {
2117                extra = new String JavaDoc(" Invoice item(" + invoiceItemSeqId + ") amount not yet applied: " + newInvoiceItemApplyAvailable);
2118            }
2119            Debug.logInfo("checking finished, start processing withe the following data... ", module);
2120            if (invoiceId != null) {
2121            Debug.logInfo(" Invoice(" + invoiceId + ") amount not yet applied: " + newInvoiceApplyAvailable + extra + " Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module);
2122            }
2123            if (toPaymentId != null) {
2124                Debug.logInfo(" toPayment(" + toPaymentId + ") amount not yet applied: " + newToPaymentApplyAvailable + " Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module);
2125            }
2126            if (billingAccountId != null) {
2127                Debug.logInfo(" billingAccount(" + billingAccountId + ") amount not yet applied: " + newBillingAccountApplyAvailable + " Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module);
2128            }
2129            if (taxAuthGeoId != null) {
2130                Debug.logInfo(" taxAuthGeoId(" + taxAuthGeoId + ") Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module);
2131            }
2132        }
2133
2134        // report error messages if any
2135
if (errorMessageList.size() > 0) {
2136            return ServiceUtil.returnError(errorMessageList);
2137        }
2138
2139        // if the application is specified it is easy, update the existing record only
2140
if (paymentApplicationId != null) {
2141            // record is already retrieved previously
2142
if (debug) Debug.logInfo("Process an existing paymentApplication record: " + paymentApplicationId, module);
2143            // update the current record
2144
paymentApplication.set("invoiceId", invoiceId);
2145            paymentApplication.set("invoiceItemSeqId", invoiceItemSeqId);
2146            paymentApplication.set("paymentId", paymentId);
2147            paymentApplication.set("toPaymentId", toPaymentId);
2148            paymentApplication.set("amountApplied", new Double JavaDoc(amountApplied.doubleValue()));
2149            paymentApplication.set("billingAccountId", billingAccountId);
2150            paymentApplication.set("taxAuthGeoId", taxAuthGeoId);
2151            return storePaymentApplication(delegator, paymentApplication);
2152        }
2153
2154        // if no invoice sequence number is provided it assumed the requested paymentAmount will be
2155
// spread over the invoice starting with the lowest sequence number if
2156
// itemprocessing is on otherwise creat one record
2157
if (invoiceId != null && paymentId != null && (invoiceItemSeqId == null)) {
2158            if (invoiceProcessing) {
2159                // create only a single record with a null seqId
2160
if (debug) Debug.logInfo("Try to allocate the payment to the invoice as a whole", module);
2161                paymentApplication.set("paymentId", paymentId);
2162                paymentApplication.set("toPaymentId",toPaymentId);
2163                paymentApplication.set("invoiceId", invoiceId);
2164                paymentApplication.set("invoiceItemSeqId", null);
2165                paymentApplication.set("toPaymentId", null);
2166                if (amountApplied.compareTo(ZERO) > 0) {
2167                    paymentApplication.set("amountApplied", new Double JavaDoc(amountApplied.doubleValue()));
2168                } else {
2169                    paymentApplication.set("amountApplied", new Double JavaDoc(amountAppliedMax.doubleValue()));
2170                }
2171                paymentApplication.set("billingAccountId", null);
2172                paymentApplication.set("taxAuthGeoId", null);
2173                if (debug) Debug.logInfo("NEW paymentapplication ID:" + paymentApplicationId + " created", module);
2174                return storePaymentApplication(delegator, paymentApplication);
2175            } else { // spread the amount over every single item number
2176
if (debug) Debug.logInfo("Try to allocate the payment to the itemnumbers of the invoice", module);
2177                // get the invoice items
2178
List invoiceItems = null;
2179                try {
2180                    invoiceItems = delegator.findByAnd("InvoiceItem", UtilMisc.toMap("invoiceId", invoiceId));
2181                } catch (GenericEntityException e) {
2182                    ServiceUtil.returnError(e.getMessage());
2183                }
2184                if (invoiceItems == null || invoiceItems.size() == 0) {
2185                    errorMessageList.add("- No invoice items found for invoice " + invoiceId + " to match payment against...\n");
2186                    return ServiceUtil.returnError(errorMessageList);
2187                } else { // we found some invoice items, start processing....
2188
Iterator i = invoiceItems.iterator();
2189                    // check if the user want to apply a smaller amount than the maximum possible on the payment
2190
if (amountApplied.signum() != 0 && amountApplied.compareTo(paymentApplyAvailable) < 0) {
2191                        paymentApplyAvailable = amountApplied;
2192                    }
2193                    while (i.hasNext() && paymentApplyAvailable.compareTo(ZERO) > 0) {
2194                        // get the invoiceItem
2195
invoiceItem = (GenericValue) i.next();
2196                        if (debug) Debug.logInfo("Start processing item: " + invoiceItem.getString("invoiceItemSeqId"), module);
2197                        BigDecimal JavaDoc itemQuantity = new BigDecimal JavaDoc("1");
2198                        if (invoiceItem.get("quantity") != null && invoiceItem.getBigDecimal("quantity").signum() != 0) {
2199                            itemQuantity = new BigDecimal JavaDoc(invoiceItem.getString("quantity")).setScale(decimals,rounding);
2200                        }
2201                        BigDecimal JavaDoc itemAmount = invoiceItem.getBigDecimal("amount").setScale(decimals,rounding);
2202                        BigDecimal JavaDoc itemTotal = itemAmount.multiply(itemQuantity).setScale(decimals,rounding);
2203
2204                        // get the application(s) already allocated to this
2205
// item, if available
2206
List paymentApplications = null;
2207                        try {
2208                            paymentApplications = invoiceItem.getRelated("PaymentApplication");
2209                        } catch (GenericEntityException e) {
2210                            ServiceUtil.returnError(e.getMessage());
2211                        }
2212                        BigDecimal JavaDoc tobeApplied = new BigDecimal JavaDoc("0");
2213                        // item total amount - already applied (if any)
2214
BigDecimal JavaDoc alreadyApplied = new BigDecimal JavaDoc("0");
2215                        if (paymentApplications != null && paymentApplications.size() > 0) {
2216                            // application(s) found, add them all together
2217
Iterator p = paymentApplications.iterator();
2218                            while (p.hasNext()) {
2219                                paymentApplication = (GenericValue) p.next();
2220                                alreadyApplied = alreadyApplied.add(paymentApplication.getBigDecimal("amountApplied").setScale(decimals,rounding));
2221                            }
2222                            tobeApplied = itemTotal.subtract(alreadyApplied).setScale(decimals,rounding);
2223                        } else {
2224                            // no application connected yet
2225
tobeApplied = itemTotal;
2226                        }
2227                        if (debug) Debug.logInfo("tobeApplied:(" + tobeApplied + ") = " + "itemTotal(" + itemTotal + ") - alreadyApplied(" + alreadyApplied + ") but not more then (nonapplied) paymentAmount(" + paymentApplyAvailable + ")", module);
2228
2229                        if (tobeApplied.signum() == 0) {
2230                            // invoiceItem already fully applied so look at the next one....
2231
continue;
2232                        }
2233
2234                        if (paymentApplyAvailable.compareTo(tobeApplied) > 0) {
2235                            paymentApplyAvailable = paymentApplyAvailable.subtract(tobeApplied);
2236                        } else {
2237                            tobeApplied = paymentApplyAvailable;
2238                            paymentApplyAvailable = new BigDecimal JavaDoc("0");
2239                        }
2240
2241                        // create application payment record but check currency
2242
// first if supplied
2243
if (invoiceItem.get("uomId") != null && payment.get("currencyUomId") != null && !invoiceItem.getString("uomId").equals(payment.getString("currencyUomId"))) {
2244                            errorMessageList.add("- Payment currency (" + payment.getString("currencyUomId") + ") and invoice Item(" + invoiceItem.getString("invoiceItemSeqId") + ") currency(" + invoiceItem.getString("uomId") + ") not the same\n");
2245                        } else if (invoice.get("currencyUomId") != null && payment.get("currencyUomId") != null && !invoice.getString("currencyUomId").equals( payment.getString("currencyUomId"))) {
2246                            errorMessageList.add("- Payment currency (" + payment.getString("currencyUomId") + ") and invoice currency(" + invoice.getString("currencyUomId") + ") not the same\n");
2247                        } else {
2248                            paymentApplication.set("paymentApplicationId", null);
2249                            // make sure we get a new record
2250
paymentApplication.set("invoiceId", invoiceId);
2251                            paymentApplication.set("invoiceItemSeqId", invoiceItem.getString("invoiceItemSeqId"));
2252                            paymentApplication.set("paymentId", paymentId);
2253                            paymentApplication.set("toPaymentId", toPaymentId);
2254                            paymentApplication.set("amountApplied", new Double JavaDoc( tobeApplied.doubleValue()));
2255                            paymentApplication.set("billingAccountId", billingAccountId);
2256                            paymentApplication.set("taxAuthGeoId", taxAuthGeoId);
2257                            storePaymentApplication(delegator, paymentApplication);
2258                        }
2259
2260                        // check if either the invoice or the payment is fully
2261
// applied, when yes change the status to paid
2262
// which triggers the ledger routines....
2263
/*
2264                         * if
2265                         * (InvoiceWorker.getInvoiceTotalBd(invoice).equals(InvoiceWorker.getInvoiceAppliedBd(invoice))) {
2266                         * try { dispatcher.runSync("setInvoiceStatus",
2267                         * UtilMisc.toMap("invoiceId",invoiceId,"statusId","INVOICE_PAID")); }
2268                         * catch (GenericServiceException e1) {
2269                         * Debug.logError(e1, "Error updating invoice status",
2270                         * module); } }
2271                         *
2272                         * if
2273                         * (payment.getBigDecimal("amount").equals(PaymentWorker.getPaymentAppliedBd(payment))) {
2274                         * GenericValue appliedPayment = (GenericValue)
2275                         * delegator.makeValue("Payment",
2276                         * UtilMisc.toMap("paymentId",paymentId,"statusId","INVOICE_PAID"));
2277                         * try { appliedPayment.store(); } catch
2278                         * (GenericEntityException e) {
2279                         * ServiceUtil.returnError(e.getMessage()); } }
2280                         */

2281                    }
2282
2283                    if (errorMessageList.size() > 0) {
2284                        return ServiceUtil.returnError(errorMessageList);
2285                    } else {
2286                        if (successMessage != null) {
2287                            return ServiceUtil.returnSuccess(successMessage);
2288                        }
2289                        else {
2290                            return ServiceUtil.returnSuccess();
2291                        }
2292                    }
2293                }
2294            }
2295        }
2296        
2297        // if no paymentApplicationId supplied create a new record with the data
2298
// supplied...
2299
if (paymentApplicationId == null && amountApplied != null) {
2300            paymentApplication.set("paymentApplicationId", paymentApplicationId);
2301            paymentApplication.set("invoiceId", invoiceId);
2302            paymentApplication.set("invoiceItemSeqId", invoiceItemSeqId);
2303            paymentApplication.set("paymentId", paymentId);
2304            paymentApplication.set("toPaymentId", toPaymentId);
2305            paymentApplication.set("amountApplied", new Double JavaDoc(amountApplied.doubleValue()));
2306            paymentApplication.set("billingAccountId", billingAccountId);
2307            paymentApplication.set("taxAuthGeoId", taxAuthGeoId);
2308            return storePaymentApplication(delegator, paymentApplication);
2309        }
2310
2311        // should never come here...
2312
errorMessageList.add("??unsuitable parameters passed...?? This message.... should never be shown\n");
2313        errorMessageList.add("--Input parameters...InvoiceId:" + invoiceId + " invoiceItemSeqId:" + invoiceItemSeqId + " PaymentId:" + paymentId + " toPaymentId:" + toPaymentId + "\n paymentApplicationId:" + paymentApplicationId + " amountApplied:" + amountApplied);
2314        return ServiceUtil.returnError(errorMessageList);
2315    }
2316
2317    public static Map calculateInvoicedAdjustmentTotalBd(DispatchContext dctx, Map context) {
2318        GenericDelegator delegator = dctx.getDelegator();
2319        Locale locale = (Locale) context.get("locale");
2320        GenericValue orderAdjustment = (GenericValue) context.get("orderAdjustment");
2321        Map result = ServiceUtil.returnSuccess();
2322        
2323        BigDecimal JavaDoc invoicedTotal = ZERO;
2324        List invoicedAdjustments = null;
2325        try {
2326            invoicedAdjustments = delegator.findByAnd("OrderAdjustmentBilling", UtilMisc.toMap("orderAdjustmentId", orderAdjustment.getString("orderAdjustmentId")));
2327        } catch( GenericEntityException e ) {
2328            String JavaDoc errMsg = UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService" + ": " + e.getMessage(), locale);
2329            Debug.logError(e, errMsg, module);
2330            return ServiceUtil.returnError(errMsg);
2331        }
2332        Iterator iait = invoicedAdjustments.iterator();
2333        while (iait.hasNext()) {
2334            GenericValue invoicedAdjustment = (GenericValue) iait.next();
2335            invoicedTotal = invoicedTotal.add(invoicedAdjustment.getBigDecimal("amount").setScale(decimals, rounding));
2336        }
2337        result.put("invoicedTotal", invoicedTotal);
2338        return result;
2339    }
2340
2341    /**
2342     * Update/add to the paymentApplication table and making sure no dup[licate
2343     * record exist
2344     *
2345     * @param delegator
2346     * @param paymentApplication
2347     * @return map results
2348     */

2349    private static Map storePaymentApplication(GenericDelegator delegator, GenericValue paymentApplication) {
2350        Map results = ServiceUtil.returnSuccess();
2351        boolean debug = true;
2352        if (debug) Debug.logInfo("===============Start updating the paymentApplication table ", module);
2353
2354        if (decimals == -1 || rounding == -1) {
2355            return ServiceUtil.returnError("Arithmetic properties for Invoice services not configured properly. Cannot proceed.");
2356        }
2357        
2358        // check if a record already exists with this data
2359
List checkAppls = null;
2360        try {
2361            checkAppls = delegator.findByAnd("PaymentApplication", UtilMisc.toMap(
2362                    "invoiceId", paymentApplication.get("invoiceId"),
2363                    "invoiceItemSeqId", paymentApplication.get("invoiceItemSeqId"),
2364                    "billingAccountId", paymentApplication.get("billingAccountId"),
2365                    "paymentId", paymentApplication.get("paymentId"),
2366                    "toPaymentId", paymentApplication.get("toPaymentId"),
2367                    "taxAuthGeoId", paymentApplication.get("taxAuthGeoId")));
2368        } catch (GenericEntityException e) {
2369            ServiceUtil.returnError(e.getMessage());
2370        }
2371        if (checkAppls != null && checkAppls.size() > 0) {
2372            if (debug) Debug.logInfo("===============" + checkAppls.size() + " records already exist", module);
2373            // 1 record exists just update and if diffrent ID delete other record and add together.
2374
GenericValue checkAppl = (GenericValue) checkAppls.get(0);
2375            // if new record add to the already existing one.
2376
if ( paymentApplication.get("paymentApplicationId") == null) {
2377                // add 2 amounts together
2378
checkAppl.set("amountApplied", new Double JavaDoc(paymentApplication.getBigDecimal("amountApplied").
2379                        add(checkAppl.getBigDecimal("amountApplied")).setScale(decimals,rounding).doubleValue()));
2380                if (debug) Debug.logInfo("==============Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module);
2381                try {
2382                    checkAppl.store();
2383                } catch (GenericEntityException e) {
2384                    ServiceUtil.returnError(e.getMessage());
2385                }
2386            } else if (paymentApplication.getString("paymentApplicationId").equals(checkAppl.getString("paymentApplicationId"))) {
2387                // update existing record inplace
2388
checkAppl.set("amountApplied", new Double JavaDoc(paymentApplication.getBigDecimal("amountApplied").doubleValue()));
2389                if (debug) Debug.logInfo("==============Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module);
2390                try {
2391                    checkAppl.store();
2392                } catch (GenericEntityException e) {
2393                    ServiceUtil.returnError(e.getMessage());
2394                }
2395            } else { // two existing records, an updated one added to the existing one
2396
// add 2 amounts together
2397
checkAppl.set("amountApplied", new Double JavaDoc(paymentApplication.getBigDecimal("amountApplied").
2398                        add(checkAppl.getBigDecimal("amountApplied")).setScale(decimals,rounding).doubleValue()));
2399                // delete paymentApplication record and update the checkAppls one.
2400
if (debug) Debug.logInfo("============Delete paymentApplication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module);
2401                try {
2402                    paymentApplication.remove();
2403                } catch (GenericEntityException e) {
2404                    ServiceUtil.returnError(e.getMessage());
2405                }
2406                // update amount existing record
2407
if (debug) Debug.logInfo("==============Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module);
2408                try {
2409                    checkAppl.store();
2410                } catch (GenericEntityException e) {
2411                    ServiceUtil.returnError(e.getMessage());
2412                }
2413            }
2414        } else {
2415            if (debug) Debug.logInfo("No records found with paymentId,invoiceid..etc probaly changed one of them...", module);
2416            // create record if ID null;
2417
if (paymentApplication.get("paymentApplicationId") == null) {
2418                paymentApplication.set("paymentApplicationId", delegator.getNextSeqId("PaymentApplication"));
2419                    if (debug) Debug.logInfo("=============Create new paymentAppication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module);
2420                try {
2421                    paymentApplication.create();
2422                } catch (GenericEntityException e) {
2423                    ServiceUtil.returnError(e.getMessage());
2424                }
2425            } else {
2426                // update existing record (could not be found because a non existing combination of paymentId/invoiceId/invoiceSeqId/ etc... was provided
2427
if (debug) Debug.logInfo("===========Update existing paymentApplication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module);
2428                try {
2429                    paymentApplication.store();
2430                } catch (GenericEntityException e) {
2431                    ServiceUtil.returnError(e.getMessage());
2432                }
2433            }
2434        }
2435        return results;
2436    }
2437
2438    public static Map checkPaymentInvoices(DispatchContext dctx, Map context) {
2439        GenericDelegator delegator = dctx.getDelegator();
2440        LocalDispatcher dispatcher = dctx.getDispatcher();
2441        GenericValue userLogin = (GenericValue) context.get("userLogin");
2442
2443        String JavaDoc paymentId = (String JavaDoc) context.get("paymentId");
2444        try {
2445            GenericValue payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId));
2446            if (payment == null) throw new GenericServiceException("Payment with ID [" + paymentId + "] not found!");
2447
2448            List paymentApplications = payment.getRelated("PaymentApplication");
2449            if (UtilValidate.isEmpty(paymentApplications)) return ServiceUtil.returnSuccess();
2450
2451            Iterator iter = paymentApplications.iterator();
2452            while (iter.hasNext()) {
2453                GenericValue paymentApplication = (GenericValue) iter.next();
2454                String JavaDoc invoiceId = paymentApplication.getString("invoiceId");
2455                if (invoiceId != null) {
2456                    Map serviceResult = dispatcher.runSync("checkInvoicePaymentApplications", UtilMisc.toMap("invoiceId", invoiceId, "userLogin", userLogin));
2457                    if (ServiceUtil.isError(serviceResult)) return serviceResult;
2458                }
2459            }
2460            return ServiceUtil.returnSuccess();
2461        } catch (GenericServiceException se) {
2462            Debug.logError(se, se.getMessage(), module);
2463            return ServiceUtil.returnError(se.getMessage());
2464        } catch (GenericEntityException ee) {
2465            Debug.logError(ee, ee.getMessage(), module);
2466            return ServiceUtil.returnError(ee.getMessage());
2467        }
2468    }
2469
2470}
2471
Popular Tags