Java tutorial
/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.ofbiz.accounting.invoice; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javolution.util.FastList; import javolution.util.FastMap; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; import org.ofbiz.accounting.payment.PaymentGatewayServices; import org.ofbiz.accounting.payment.PaymentWorker; import org.ofbiz.accounting.util.UtilAccounting; import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.UtilDateTime; import org.ofbiz.base.util.UtilFormatOut; import org.ofbiz.base.util.UtilGenerics; import org.ofbiz.base.util.UtilMisc; import org.ofbiz.base.util.UtilNumber; import org.ofbiz.base.util.UtilProperties; import org.ofbiz.base.util.UtilValidate; import org.ofbiz.entity.Delegator; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.GenericValue; import org.ofbiz.entity.condition.EntityCondition; import org.ofbiz.entity.condition.EntityExpr; import org.ofbiz.entity.condition.EntityOperator; import org.ofbiz.entity.util.EntityQuery; import org.ofbiz.entity.util.EntityUtil; import org.ofbiz.entity.util.EntityUtilProperties; import org.ofbiz.order.order.OrderReadHelper; import org.ofbiz.product.product.ProductWorker; import org.ofbiz.service.DispatchContext; import org.ofbiz.service.GenericServiceException; import org.ofbiz.service.LocalDispatcher; import org.ofbiz.service.ServiceUtil; /** * InvoiceServices - Services for creating invoices * * Note that throughout this file we use BigDecimal to do arithmetic. It is * critical to understand the way BigDecimal works if you wish to modify the * computations in this file. The most important things to keep in mind: * * Critically important: BigDecimal arithmetic methods like add(), * multiply(), divide() do not modify the BigDecimal itself. Instead, they * return a new BigDecimal. For example, to keep a running total of an * amount, make sure you do this: * * amount = amount.add(subAmount); * * and not this, * * amount.add(subAmount); * * Use .setScale(scale, roundingMode) after every computation to scale and * round off the decimals. Check the code to see how the scale and * roundingMode are obtained and how the function is used. * * use .compareTo() to compare big decimals * * ex. (amountOne.compareTo(amountTwo) == 1) * checks if amountOne is greater than amountTwo * * Use .signum() to test if value is negative, zero, or positive * * ex. (amountOne.signum() == 1) * checks if the amount is a positive non-zero number * * Never use the .equals() function becaues it considers 2.0 not equal to 2.00 (the scale is different) * Instead, use .compareTo() or .signum(), which handles scale correctly. * * For reference, check the official Sun Javadoc on java.math.BigDecimal. */ public class InvoiceServices { public static String module = InvoiceServices.class.getName(); // set some BigDecimal properties private static final BigDecimal ZERO = BigDecimal.ZERO; private static final int DECIMALS = UtilNumber.getBigDecimalScale("invoice.decimals"); private static final int ROUNDING = UtilNumber.getBigDecimalRoundingMode("invoice.rounding"); private static final int TAX_DECIMALS = UtilNumber.getBigDecimalScale("salestax.calc.decimals"); private static final int TAX_ROUNDING = UtilNumber.getBigDecimalRoundingMode("salestax.rounding"); public static final int TAX_CALC_SCALE = UtilNumber.getBigDecimalScale("salestax.calc.decimals"); private static final int INVOICE_ITEM_SEQUENCE_ID_DIGITS = 5; // this is the number of digits used for invoiceItemSeqId: 00001, 00002... public static final String resource = "AccountingUiLabels"; // service to create an invoice for a complete order by the system userid public static Map<String, Object> createInvoiceForOrderAllItems(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); try { List<GenericValue> orderItems = EntityQuery.use(delegator).from("OrderItem") .where("orderId", context.get("orderId")).orderBy("orderItemSeqId").queryList(); if (orderItems.size() > 0) { context.put("billItems", orderItems); } // get the system userid and store in context otherwise the invoice add service does not work GenericValue userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", "system") .queryOne(); if (userLogin != null) { context.put("userLogin", userLogin); } Map<String, Object> result = dispatcher.runSync("createInvoiceForOrder", context); result.remove("invoiceTypeId"); //remove extra parameter return result; } catch (GenericServiceException e) { Debug.logError(e, "Entity/data problem creating invoice from order items: " + e.toString(), module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingEntityDataProblemCreatingInvoiceFromOrderItems", UtilMisc.toMap("reason", e.toString()), locale)); } catch (GenericEntityException e) { Debug.logError(e, "Entity/data problem creating invoice from order items: " + e.toString(), module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingEntityDataProblemCreatingInvoiceFromOrderItems", UtilMisc.toMap("reason", e.toString()), locale)); } } /* Service to create an invoice for an order */ public static Map<String, Object> createInvoiceForOrder(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); if (DECIMALS == -1 || ROUNDING == -1) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingAritmeticPropertiesNotConfigured", locale)); } String orderId = (String) context.get("orderId"); List<GenericValue> billItems = UtilGenerics.checkList(context.get("billItems")); String invoiceId = (String) context.get("invoiceId"); if (UtilValidate.isEmpty(billItems)) { Debug.logVerbose("No order items to invoice; not creating invoice; returning success", module); return ServiceUtil .returnSuccess(UtilProperties.getMessage(resource, "AccountingNoOrderItemsToInvoice", locale)); } try { GenericValue orderHeader = EntityQuery.use(delegator).from("OrderHeader").where("orderId", orderId) .queryOne(); if (orderHeader == null) { return ServiceUtil .returnError(UtilProperties.getMessage(resource, "AccountingNoOrderHeader", locale)); } // figure out the invoice type String invoiceType = null; String orderType = orderHeader.getString("orderTypeId"); if (orderType.equals("SALES_ORDER")) { invoiceType = "SALES_INVOICE"; } else if (orderType.equals("PURCHASE_ORDER")) { invoiceType = "PURCHASE_INVOICE"; } // Set the precision depending on the type of invoice int invoiceTypeDecimals = UtilNumber.getBigDecimalScale("invoice." + invoiceType + ".decimals"); if (invoiceTypeDecimals == -1) invoiceTypeDecimals = DECIMALS; // Make an order read helper from the order OrderReadHelper orh = new OrderReadHelper(orderHeader); // get the product store GenericValue productStore = orh.getProductStore(); // get the shipping adjustment mode (Y = Pro-Rate; N = First-Invoice) String prorateShipping = productStore != null ? productStore.getString("prorateShipping") : "Y"; if (prorateShipping == null) { prorateShipping = "Y"; } // get the billing parties String billToCustomerPartyId = orh.getBillToParty().getString("partyId"); String billFromVendorPartyId = orh.getBillFromParty().getString("partyId"); // get some price totals BigDecimal shippableAmount = orh.getShippableTotal(null); BigDecimal orderSubTotal = orh.getOrderItemsSubTotal(); // these variables are for pro-rating order amounts across invoices, so they should not be rounded off for maximum accuracy BigDecimal invoiceShipProRateAmount = ZERO; BigDecimal invoiceSubTotal = ZERO; BigDecimal invoiceQuantity = ZERO; GenericValue billingAccount = orderHeader.getRelatedOne("BillingAccount", false); String billingAccountId = billingAccount != null ? billingAccount.getString("billingAccountId") : null; Timestamp invoiceDate = (Timestamp) context.get("eventDate"); if (UtilValidate.isEmpty(invoiceDate)) { // TODO: ideally this should be the same time as when a shipment is sent and be passed in as a parameter invoiceDate = UtilDateTime.nowTimestamp(); } // TODO: perhaps consider billing account net days term as well? Long orderTermNetDays = orh.getOrderTermNetDays(); Timestamp dueDate = null; if (orderTermNetDays != null) { dueDate = UtilDateTime.getDayEnd(invoiceDate, orderTermNetDays); } // create the invoice record if (UtilValidate.isEmpty(invoiceId)) { Map<String, Object> createInvoiceContext = FastMap.newInstance(); createInvoiceContext.put("partyId", billToCustomerPartyId); createInvoiceContext.put("partyIdFrom", billFromVendorPartyId); createInvoiceContext.put("billingAccountId", billingAccountId); createInvoiceContext.put("invoiceDate", invoiceDate); createInvoiceContext.put("dueDate", dueDate); createInvoiceContext.put("invoiceTypeId", invoiceType); // start with INVOICE_IN_PROCESS, in the INVOICE_READY we can't change the invoice (or shouldn't be able to...) createInvoiceContext.put("statusId", "INVOICE_IN_PROCESS"); createInvoiceContext.put("currencyUomId", orderHeader.getString("currencyUom")); createInvoiceContext.put("userLogin", userLogin); // store the invoice first Map<String, Object> createInvoiceResult = dispatcher.runSync("createInvoice", createInvoiceContext); if (ServiceUtil.isError(createInvoiceResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceFromOrder", locale), null, null, createInvoiceResult); } // call service, not direct entity op: delegator.create(invoice); invoiceId = (String) createInvoiceResult.get("invoiceId"); } // order roles to invoice roles List<GenericValue> orderRoles = orderHeader.getRelated("OrderRole", null, null, false); Map<String, Object> createInvoiceRoleContext = FastMap.newInstance(); createInvoiceRoleContext.put("invoiceId", invoiceId); createInvoiceRoleContext.put("userLogin", userLogin); for (GenericValue orderRole : orderRoles) { createInvoiceRoleContext.put("partyId", orderRole.getString("partyId")); createInvoiceRoleContext.put("roleTypeId", orderRole.getString("roleTypeId")); Map<String, Object> createInvoiceRoleResult = dispatcher.runSync("createInvoiceRole", createInvoiceRoleContext); if (ServiceUtil.isError(createInvoiceRoleResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceFromOrder", locale), null, null, createInvoiceRoleResult); } } // order terms to invoice terms. // TODO: it might be nice to filter OrderTerms to only copy over financial terms. List<GenericValue> orderTerms = orh.getOrderTerms(); createInvoiceTerms(delegator, dispatcher, invoiceId, orderTerms, userLogin, locale); // billing accounts // List billingAccountTerms = null; // for billing accounts we will use related information if (billingAccount != null) { /* * jacopoc: billing account terms were already copied as order terms * when the order was created. // get the billing account terms billingAccountTerms = billingAccount.getRelated("BillingAccountTerm", null, null, false); // set the invoice terms as defined for the billing account createInvoiceTerms(delegator, dispatcher, invoiceId, billingAccountTerms, userLogin, locale); */ // set the invoice bill_to_customer from the billing account List<GenericValue> billToRoles = billingAccount.getRelated("BillingAccountRole", UtilMisc.toMap("roleTypeId", "BILL_TO_CUSTOMER"), null, false); for (GenericValue billToRole : billToRoles) { if (!(billToRole.getString("partyId").equals(billToCustomerPartyId))) { createInvoiceRoleContext = UtilMisc.toMap("invoiceId", invoiceId, "partyId", billToRole.get("partyId"), "roleTypeId", "BILL_TO_CUSTOMER", "userLogin", userLogin); Map<String, Object> createInvoiceRoleResult = dispatcher.runSync("createInvoiceRole", createInvoiceRoleContext); if (ServiceUtil.isError(createInvoiceRoleResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceRoleFromOrder", locale), null, null, createInvoiceRoleResult); } } } // set the bill-to contact mech as the contact mech of the billing account if (UtilValidate.isNotEmpty(billingAccount.getString("contactMechId"))) { Map<String, Object> createBillToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", billingAccount.getString("contactMechId"), "contactMechPurposeTypeId", "BILLING_LOCATION", "userLogin", userLogin); Map<String, Object> createBillToContactMechResult = dispatcher .runSync("createInvoiceContactMech", createBillToContactMechContext); if (ServiceUtil.isError(createBillToContactMechResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceContactMechFromOrder", locale), null, null, createBillToContactMechResult); } } } else { List<GenericValue> billingLocations = orh.getBillingLocations(); if (UtilValidate.isNotEmpty(billingLocations)) { for (GenericValue ocm : billingLocations) { Map<String, Object> createBillToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", ocm.getString("contactMechId"), "contactMechPurposeTypeId", "BILLING_LOCATION", "userLogin", userLogin); Map<String, Object> createBillToContactMechResult = dispatcher .runSync("createInvoiceContactMech", createBillToContactMechContext); if (ServiceUtil.isError(createBillToContactMechResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceContactMechFromOrder", locale), null, null, createBillToContactMechResult); } } } else { Debug.logWarning("No billing locations found for order [" + orderId + "] and none were created for Invoice [" + invoiceId + "]", module); } } // get a list of the payment method types //DEJ20050705 doesn't appear to be used: List paymentPreferences = orderHeader.getRelated("OrderPaymentPreference", null, null, false); // create the bill-from (or pay-to) contact mech as the primary PAYMENT_LOCATION of the party from the store GenericValue payToAddress = null; if (invoiceType.equals("PURCHASE_INVOICE")) { // for purchase orders, the pay to address is the BILLING_LOCATION of the vendor GenericValue billFromVendor = orh.getPartyFromRole("BILL_FROM_VENDOR"); if (billFromVendor != null) { List<GenericValue> billingContactMechs = billFromVendor.getRelatedOne("Party", false) .getRelated("PartyContactMechPurpose", UtilMisc.toMap("contactMechPurposeTypeId", "BILLING_LOCATION"), null, false); if (UtilValidate.isNotEmpty(billingContactMechs)) { payToAddress = EntityUtil.getFirst(billingContactMechs); } } } else { // for sales orders, it is the payment address on file for the store payToAddress = PaymentWorker.getPaymentAddress(delegator, productStore.getString("payToPartyId")); } if (payToAddress != null) { Map<String, Object> createPayToContactMechContext = UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", payToAddress.getString("contactMechId"), "contactMechPurposeTypeId", "PAYMENT_LOCATION", "userLogin", userLogin); Map<String, Object> createPayToContactMechResult = dispatcher.runSync("createInvoiceContactMech", createPayToContactMechContext); if (ServiceUtil.isError(createPayToContactMechResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceContactMechFromOrder", locale), null, null, createPayToContactMechResult); } } // sequence for items - all OrderItems or InventoryReservations + all Adjustments int invoiceItemSeqNum = 1; String invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); // create the item records for (GenericValue currentValue : billItems) { GenericValue itemIssuance = null; GenericValue orderItem = null; GenericValue shipmentReceipt = null; if ("ItemIssuance".equals(currentValue.getEntityName())) { itemIssuance = currentValue; } else if ("OrderItem".equals(currentValue.getEntityName())) { orderItem = currentValue; } else if ("ShipmentReceipt".equals(currentValue.getEntityName())) { shipmentReceipt = currentValue; } else { Debug.logError("Unexpected entity " + currentValue + " of type " + currentValue.getEntityName(), module); } if (orderItem == null && itemIssuance != null) { orderItem = itemIssuance.getRelatedOne("OrderItem", false); } else if ((orderItem == null) && (shipmentReceipt != null)) { orderItem = shipmentReceipt.getRelatedOne("OrderItem", false); } else if ((orderItem == null) && (itemIssuance == null) && (shipmentReceipt == null)) { Debug.logError( "Cannot create invoice when orderItem, itemIssuance, and shipmentReceipt are all null", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingIllegalValuesPassedToCreateInvoiceService", locale)); } GenericValue product = null; if (orderItem.get("productId") != null) { product = orderItem.getRelatedOne("Product", false); } // get some quantities BigDecimal billingQuantity = null; if (itemIssuance != null) { billingQuantity = itemIssuance.getBigDecimal("quantity"); BigDecimal cancelQty = itemIssuance.getBigDecimal("cancelQuantity"); if (cancelQty == null) { cancelQty = ZERO; } billingQuantity = billingQuantity.subtract(cancelQty).setScale(DECIMALS, ROUNDING); } else if (shipmentReceipt != null) { billingQuantity = shipmentReceipt.getBigDecimal("quantityAccepted"); } else { BigDecimal orderedQuantity = OrderReadHelper.getOrderItemQuantity(orderItem); BigDecimal invoicedQuantity = OrderReadHelper.getOrderItemInvoicedQuantity(orderItem); billingQuantity = orderedQuantity.subtract(invoicedQuantity); if (billingQuantity.compareTo(ZERO) < 0) { billingQuantity = ZERO; } } if (billingQuantity == null) billingQuantity = ZERO; // check if shipping applies to this item. Shipping is calculated for sales invoices, not purchase invoices. boolean shippingApplies = false; if ((product != null) && (ProductWorker.shippingApplies(product)) && (invoiceType.equals("SALES_INVOICE"))) { shippingApplies = true; } BigDecimal billingAmount = orderItem.getBigDecimal("unitPrice").setScale(invoiceTypeDecimals, ROUNDING); Map<String, Object> createInvoiceItemContext = FastMap.newInstance(); createInvoiceItemContext.put("invoiceId", invoiceId); createInvoiceItemContext.put("invoiceItemSeqId", invoiceItemSeqId); createInvoiceItemContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, (orderItem.getString("orderItemTypeId")), (product == null ? null : product.getString("productTypeId")), invoiceType, "INV_FPROD_ITEM")); createInvoiceItemContext.put("description", orderItem.get("itemDescription")); createInvoiceItemContext.put("quantity", billingQuantity); createInvoiceItemContext.put("amount", billingAmount); createInvoiceItemContext.put("productId", orderItem.get("productId")); createInvoiceItemContext.put("productFeatureId", orderItem.get("productFeatureId")); createInvoiceItemContext.put("overrideGlAccountId", orderItem.get("overrideGlAccountId")); //createInvoiceItemContext.put("uomId", ""); createInvoiceItemContext.put("userLogin", userLogin); String itemIssuanceId = null; if (itemIssuance != null && itemIssuance.get("inventoryItemId") != null) { itemIssuanceId = itemIssuance.getString("itemIssuanceId"); createInvoiceItemContext.put("inventoryItemId", itemIssuance.get("inventoryItemId")); } // similarly, tax only for purchase invoices if ((product != null) && (invoiceType.equals("SALES_INVOICE"))) { createInvoiceItemContext.put("taxableFlag", product.get("taxable")); } Map<String, Object> createInvoiceItemResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemContext); if (ServiceUtil.isError(createInvoiceItemResult)) { return ServiceUtil .returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceItemFromOrder", locale), null, null, createInvoiceItemResult); } // this item total BigDecimal thisAmount = billingAmount.multiply(billingQuantity).setScale(invoiceTypeDecimals, ROUNDING); // add to the ship amount only if it applies to this item if (shippingApplies) { invoiceShipProRateAmount = invoiceShipProRateAmount.add(thisAmount) .setScale(invoiceTypeDecimals, ROUNDING); } // increment the invoice subtotal invoiceSubTotal = invoiceSubTotal.add(thisAmount).setScale(100, ROUNDING); // increment the invoice quantity invoiceQuantity = invoiceQuantity.add(billingQuantity).setScale(invoiceTypeDecimals, ROUNDING); // create the OrderItemBilling record Map<String, Object> createOrderItemBillingContext = FastMap.newInstance(); createOrderItemBillingContext.put("invoiceId", invoiceId); createOrderItemBillingContext.put("invoiceItemSeqId", invoiceItemSeqId); createOrderItemBillingContext.put("orderId", orderItem.get("orderId")); createOrderItemBillingContext.put("orderItemSeqId", orderItem.get("orderItemSeqId")); createOrderItemBillingContext.put("itemIssuanceId", itemIssuanceId); createOrderItemBillingContext.put("quantity", billingQuantity); createOrderItemBillingContext.put("amount", billingAmount); createOrderItemBillingContext.put("userLogin", userLogin); if ((shipmentReceipt != null) && (shipmentReceipt.getString("receiptId") != null)) { createOrderItemBillingContext.put("shipmentReceiptId", shipmentReceipt.getString("receiptId")); } Map<String, Object> createOrderItemBillingResult = dispatcher.runSync("createOrderItemBilling", createOrderItemBillingContext); if (ServiceUtil.isError(createOrderItemBillingResult)) { return ServiceUtil .returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingOrderItemBillingFromOrder", locale), null, null, createOrderItemBillingResult); } if ("ItemIssuance".equals(currentValue.getEntityName())) { List<GenericValue> shipmentItemBillings = EntityQuery.use(delegator).from("ShipmentItemBilling") .where("shipmentId", currentValue.get("shipmentId"), "shipmentItemSeqId", currentValue.get("shipmentItemSeqId")) .queryList(); if (UtilValidate.isEmpty(shipmentItemBillings)) { // create the ShipmentItemBilling record GenericValue shipmentItemBilling = delegator.makeValue("ShipmentItemBilling", UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId)); shipmentItemBilling.put("shipmentId", currentValue.get("shipmentId")); shipmentItemBilling.put("shipmentItemSeqId", currentValue.get("shipmentItemSeqId")); shipmentItemBilling.create(); } } String parentInvoiceItemSeqId = invoiceItemSeqId; // increment the counter invoiceItemSeqNum++; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); // Get the original order item from the DB, in case the quantity has been overridden GenericValue originalOrderItem = EntityQuery.use(delegator).from("OrderItem") .where("orderId", orderId, "orderItemSeqId", orderItem.get("orderItemSeqId")).queryOne(); // create the item adjustment as line items List<GenericValue> itemAdjustments = OrderReadHelper.getOrderItemAdjustmentList(orderItem, orh.getAdjustments()); for (GenericValue adj : itemAdjustments) { // Check against OrderAdjustmentBilling to see how much of this adjustment has already been invoiced BigDecimal adjAlreadyInvoicedAmount = null; try { Map<String, Object> checkResult = dispatcher.runSync("calculateInvoicedAdjustmentTotal", UtilMisc.toMap("orderAdjustment", adj)); adjAlreadyInvoicedAmount = (BigDecimal) checkResult.get("invoicedTotal"); } catch (GenericServiceException e) { Debug.logError(e, "Accounting trouble calling calculateInvoicedAdjustmentTotal service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale)); } // If the absolute invoiced amount >= the abs of the adjustment amount, the full amount has already been invoiced, // so skip this adjustment if (adj.get("amount") == null) { // JLR 17/4/7 : fix a bug coming from POS in case of use of a discount (on item(s) or sale, item(s) here) and a cash amount higher than total (hence issuing change) continue; } if (adjAlreadyInvoicedAmount.abs().compareTo( adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING).abs()) > 0) { continue; } BigDecimal originalOrderItemQuantity = OrderReadHelper.getOrderItemQuantity(originalOrderItem); BigDecimal amount = ZERO; if (originalOrderItemQuantity.signum() != 0) { if (adj.get("amount") != null) { // pro-rate the amount // set decimals = 100 means we don't round this intermediate value, which is very important amount = adj.getBigDecimal("amount").divide(originalOrderItemQuantity, 100, ROUNDING); amount = amount.multiply(billingQuantity); // Tax needs to be rounded differently from other order adjustments if (adj.getString("orderAdjustmentTypeId").equals("SALES_TAX")) { amount = amount.setScale(TAX_DECIMALS, TAX_ROUNDING); } else { amount = amount.setScale(invoiceTypeDecimals, ROUNDING); } } else if (adj.get("sourcePercentage") != null) { // pro-rate the amount // set decimals = 100 means we don't round this intermediate value, which is very important BigDecimal percent = adj.getBigDecimal("sourcePercentage"); percent = percent.divide(new BigDecimal(100), 100, ROUNDING); amount = billingAmount.multiply(percent); amount = amount.divide(originalOrderItemQuantity, 100, ROUNDING); amount = amount.multiply(billingQuantity); amount = amount.setScale(invoiceTypeDecimals, ROUNDING); } } if (amount.signum() != 0) { Map<String, Object> createInvoiceItemAdjContext = FastMap.newInstance(); createInvoiceItemAdjContext.put("invoiceId", invoiceId); createInvoiceItemAdjContext.put("invoiceItemSeqId", invoiceItemSeqId); createInvoiceItemAdjContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, adj.getString("orderAdjustmentTypeId"), null, invoiceType, "INVOICE_ITM_ADJ")); createInvoiceItemAdjContext.put("quantity", BigDecimal.ONE); createInvoiceItemAdjContext.put("amount", amount); createInvoiceItemAdjContext.put("productId", orderItem.get("productId")); createInvoiceItemAdjContext.put("productFeatureId", orderItem.get("productFeatureId")); createInvoiceItemAdjContext.put("overrideGlAccountId", adj.get("overrideGlAccountId")); createInvoiceItemAdjContext.put("parentInvoiceId", invoiceId); createInvoiceItemAdjContext.put("parentInvoiceItemSeqId", parentInvoiceItemSeqId); //createInvoiceItemAdjContext.put("uomId", ""); createInvoiceItemAdjContext.put("userLogin", userLogin); createInvoiceItemAdjContext.put("taxAuthPartyId", adj.get("taxAuthPartyId")); createInvoiceItemAdjContext.put("taxAuthGeoId", adj.get("taxAuthGeoId")); createInvoiceItemAdjContext.put("taxAuthorityRateSeqId", adj.get("taxAuthorityRateSeqId")); // some adjustments fill out the comments field instead String description = (UtilValidate.isEmpty(adj.getString("description")) ? adj.getString("comments") : adj.getString("description")); createInvoiceItemAdjContext.put("description", description); // invoice items for sales tax are not taxable themselves // TODO: This is not an ideal solution. Instead, we need to use OrderAdjustment.includeInTax when it is implemented if (!(adj.getString("orderAdjustmentTypeId").equals("SALES_TAX"))) { createInvoiceItemAdjContext.put("taxableFlag", product.get("taxable")); } // If the OrderAdjustment is associated to a ProductPromo, // and the field ProductPromo.overrideOrgPartyId is set, // copy the value to InvoiceItem.overrideOrgPartyId: this // represent an organization override for the payToPartyId if (UtilValidate.isNotEmpty(adj.getString("productPromoId"))) { try { GenericValue productPromo = adj.getRelatedOne("ProductPromo", false); if (UtilValidate.isNotEmpty(productPromo.getString("overrideOrgPartyId"))) { createInvoiceItemAdjContext.put("overrideOrgPartyId", productPromo.getString("overrideOrgPartyId")); } } catch (GenericEntityException e) { Debug.logError(e, "Error looking up ProductPromo with id [" + adj.getString("productPromoId") + "]", module); } } Map<String, Object> createInvoiceItemAdjResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemAdjContext); if (ServiceUtil.isError(createInvoiceItemAdjResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceItemFromOrder", locale), null, null, createInvoiceItemAdjResult); } // Create the OrderAdjustmentBilling record Map<String, Object> createOrderAdjustmentBillingContext = FastMap.newInstance(); createOrderAdjustmentBillingContext.put("orderAdjustmentId", adj.getString("orderAdjustmentId")); createOrderAdjustmentBillingContext.put("invoiceId", invoiceId); createOrderAdjustmentBillingContext.put("invoiceItemSeqId", invoiceItemSeqId); createOrderAdjustmentBillingContext.put("amount", amount); createOrderAdjustmentBillingContext.put("userLogin", userLogin); Map<String, Object> createOrderAdjustmentBillingResult = dispatcher .runSync("createOrderAdjustmentBilling", createOrderAdjustmentBillingContext); if (ServiceUtil.isError(createOrderAdjustmentBillingResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingOrderAdjustmentBillingFromOrder", locale), null, null, createOrderAdjustmentBillingContext); } // this adjustment amount BigDecimal thisAdjAmount = amount; // adjustments only apply to totals when they are not tax or shipping adjustments if (!"SALES_TAX".equals(adj.getString("orderAdjustmentTypeId")) && !"SHIPPING_ADJUSTMENT".equals(adj.getString("orderAdjustmentTypeId"))) { // increment the invoice subtotal invoiceSubTotal = invoiceSubTotal.add(thisAdjAmount).setScale(100, ROUNDING); // add to the ship amount only if it applies to this item if (shippingApplies) { invoiceShipProRateAmount = invoiceShipProRateAmount.add(thisAdjAmount) .setScale(invoiceTypeDecimals, ROUNDING); } } // increment the counter invoiceItemSeqNum++; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); } } } // create header adjustments as line items -- always to tax/shipping last Map<GenericValue, BigDecimal> shipAdjustments = FastMap.newInstance(); Map<GenericValue, BigDecimal> taxAdjustments = FastMap.newInstance(); List<GenericValue> headerAdjustments = orh.getOrderHeaderAdjustments(); for (GenericValue adj : headerAdjustments) { // Check against OrderAdjustmentBilling to see how much of this adjustment has already been invoiced BigDecimal adjAlreadyInvoicedAmount = null; try { Map<String, Object> checkResult = dispatcher.runSync("calculateInvoicedAdjustmentTotal", UtilMisc.toMap("orderAdjustment", adj)); adjAlreadyInvoicedAmount = ((BigDecimal) checkResult.get("invoicedTotal")) .setScale(invoiceTypeDecimals, ROUNDING); } catch (GenericServiceException e) { Debug.logError(e, "Accounting trouble calling calculateInvoicedAdjustmentTotal service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale)); } // If the absolute invoiced amount >= the abs of the adjustment amount, the full amount has already been invoiced, // so skip this adjustment if (null == adj.get("amount")) { // JLR 17/4/7 : fix a bug coming from POS in case of use of a discount (on item(s) or sale, sale here) and a cash amount higher than total (hence issuing change) continue; } if (adjAlreadyInvoicedAmount.abs() .compareTo(adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING).abs()) > 0) { continue; } if ("SHIPPING_CHARGES".equals(adj.getString("orderAdjustmentTypeId"))) { shipAdjustments.put(adj, adjAlreadyInvoicedAmount); } else if ("SALES_TAX".equals(adj.getString("orderAdjustmentTypeId"))) { taxAdjustments.put(adj, adjAlreadyInvoicedAmount); } else { // these will effect the shipping pro-rate (unless commented) // other adjustment type calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, orderSubTotal, invoiceSubTotal, adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING), invoiceTypeDecimals, ROUNDING, userLogin, dispatcher, locale); // invoiceShipProRateAmount += adjAmount; // do adjustments compound or are they based off subtotal? Here we will (unless commented) // invoiceSubTotal += adjAmount; // increment the counter invoiceItemSeqNum++; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); } } // 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 // numerator/denominator problems when the shipping is not pro-rated but rather charged all on the first invoice for (GenericValue adj : shipAdjustments.keySet()) { BigDecimal adjAlreadyInvoicedAmount = shipAdjustments.get(adj); if ("N".equalsIgnoreCase(prorateShipping)) { // Set the divisor and multiplier to 1 to avoid prorating BigDecimal divisor = BigDecimal.ONE; BigDecimal multiplier = BigDecimal.ONE; // The base amount in this case is the adjustment amount minus the total already invoiced for that adjustment, since // it won't be prorated BigDecimal baseAmount = adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING) .subtract(adjAlreadyInvoicedAmount); calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, divisor, multiplier, baseAmount, invoiceTypeDecimals, ROUNDING, userLogin, dispatcher, locale); } else { // Pro-rate the shipping amount based on shippable information BigDecimal divisor = shippableAmount; BigDecimal multiplier = invoiceShipProRateAmount; // The base amount in this case is the adjustment amount, since we want to prorate based on the full amount BigDecimal baseAmount = adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING); calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, divisor, multiplier, baseAmount, invoiceTypeDecimals, ROUNDING, userLogin, dispatcher, locale); } // Increment the counter invoiceItemSeqNum++; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); } // last do the tax adjustments String prorateTaxes = productStore != null ? productStore.getString("prorateTaxes") : "Y"; if (prorateTaxes == null) { prorateTaxes = "Y"; } for (Map.Entry<GenericValue, BigDecimal> entry : taxAdjustments.entrySet()) { GenericValue adj = entry.getKey(); BigDecimal adjAlreadyInvoicedAmount = entry.getValue(); BigDecimal adjAmount = null; if ("N".equalsIgnoreCase(prorateTaxes)) { // Set the divisor and multiplier to 1 to avoid prorating BigDecimal divisor = BigDecimal.ONE; BigDecimal multiplier = BigDecimal.ONE; // The base amount in this case is the adjustment amount minus the total already invoiced for that adjustment, since // it won't be prorated BigDecimal baseAmount = adj.getBigDecimal("amount").setScale(TAX_DECIMALS, TAX_ROUNDING) .subtract(adjAlreadyInvoicedAmount); adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, divisor, multiplier, baseAmount, TAX_DECIMALS, TAX_ROUNDING, userLogin, dispatcher, locale); } else { // Pro-rate the tax amount based on shippable information BigDecimal divisor = orderSubTotal; BigDecimal multiplier = invoiceSubTotal; // The base amount in this case is the adjustment amount, since we want to prorate based on the full amount BigDecimal baseAmount = adj.getBigDecimal("amount"); adjAmount = calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, divisor, multiplier, baseAmount, TAX_DECIMALS, TAX_ROUNDING, userLogin, dispatcher, locale); } invoiceSubTotal = invoiceSubTotal.add(adjAmount).setScale(invoiceTypeDecimals, ROUNDING); // Increment the counter invoiceItemSeqNum++; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); } // check for previous order payments List<GenericValue> orderPaymentPrefs = EntityQuery.use(delegator).from("OrderPaymentPreference").where( EntityCondition.makeCondition("orderId", EntityOperator.EQUALS, orderId), EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "PAYMENT_CANCELLED")) .queryList(); List<GenericValue> currentPayments = FastList.newInstance(); for (GenericValue paymentPref : orderPaymentPrefs) { List<GenericValue> payments = paymentPref.getRelated("Payment", null, null, false); currentPayments.addAll(payments); } // apply these payments to the invoice if they have any remaining amount to apply for (GenericValue payment : currentPayments) { if ("PMNT_VOID".equals(payment.getString("statusId")) || "PMNT_CANCELLED".equals(payment.getString("statusId"))) { continue; } BigDecimal notApplied = PaymentWorker.getPaymentNotApplied(payment); if (notApplied.signum() > 0) { Map<String, Object> appl = FastMap.newInstance(); appl.put("paymentId", payment.get("paymentId")); appl.put("invoiceId", invoiceId); appl.put("billingAccountId", billingAccountId); appl.put("amountApplied", notApplied); appl.put("userLogin", userLogin); Map<String, Object> createPayApplResult = dispatcher.runSync("createPaymentApplication", appl); if (ServiceUtil.isError(createPayApplResult)) { return ServiceUtil .returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceFromOrder", locale), null, null, createPayApplResult); } } } // Should all be in place now. Depending on the ProductStore.autoApproveInvoice setting, set status to INVOICE_READY (unless it's a purchase invoice, which we set to INVOICE_IN_PROCESS) String autoApproveInvoice = productStore != null ? productStore.getString("autoApproveInvoice") : "Y"; if (!"N".equals(autoApproveInvoice)) { String nextStatusId = "PURCHASE_INVOICE".equals(invoiceType) ? "INVOICE_IN_PROCESS" : "INVOICE_READY"; Map<String, Object> setInvoiceStatusResult = dispatcher.runSync("setInvoiceStatus", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "statusId", nextStatusId, "userLogin", userLogin)); if (ServiceUtil.isError(setInvoiceStatusResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceFromOrder", locale), null, null, setInvoiceStatusResult); } } Map<String, Object> resp = ServiceUtil.returnSuccess(); resp.put("invoiceId", invoiceId); resp.put("invoiceTypeId", invoiceType); return resp; } catch (GenericEntityException e) { Debug.logError(e, "Entity/data problem creating invoice from order items: " + e.toString(), module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingEntityDataProblemCreatingInvoiceFromOrderItems", UtilMisc.toMap("reason", e.toString()), locale)); } catch (GenericServiceException e) { Debug.logError(e, "Service/other problem creating invoice from order items: " + e.toString(), module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingServiceOtherProblemCreatingInvoiceFromOrderItems", UtilMisc.toMap("reason", e.toString()), locale)); } } // Service for creating commission invoices public static Map<String, Object> createCommissionInvoices(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); List<String> salesInvoiceIds = UtilGenerics.checkList(context.get("invoiceIds")); List<Map<String, String>> invoicesCreated = FastList.newInstance(); Map<String, List<Map<String, Object>>> commissionParties = FastMap.newInstance(); for (String salesInvoiceId : salesInvoiceIds) { List<String> salesRepPartyIds = UtilGenerics.checkList(context.get("partyIds")); BigDecimal amountTotal = InvoiceWorker.getInvoiceTotal(delegator, salesInvoiceId); if (amountTotal.signum() == 0) { Debug.logWarning("Invoice [" + salesInvoiceId + "] has an amount total of [" + amountTotal + "], so no commission invoice will be created", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingInvoiceCommissionZeroInvoiceAmount", locale)); } BigDecimal appliedFraction = amountTotal.divide(amountTotal, 12, ROUNDING); GenericValue invoice = null; boolean isReturn = false; List<String> billFromVendorInvoiceRoles = new ArrayList<String>(); List<GenericValue> invoiceItems = new ArrayList<GenericValue>(); try { List<EntityExpr> invoiceRoleConds = UtilMisc.toList( EntityCondition.makeCondition("invoiceId", EntityOperator.EQUALS, salesInvoiceId), EntityCondition.makeCondition("roleTypeId", EntityOperator.EQUALS, "BILL_FROM_VENDOR")); EntityQuery roleQuery = EntityQuery.use(delegator).select("partyId").from("InvoiceRole") .where(invoiceRoleConds); billFromVendorInvoiceRoles = EntityUtil.getFieldListFromEntityList(roleQuery.queryList(), "partyId", true); invoiceRoleConds = UtilMisc.toList( EntityCondition.makeCondition("invoiceId", EntityOperator.EQUALS, salesInvoiceId), EntityCondition.makeCondition("roleTypeId", EntityOperator.EQUALS, "SALES_REP")); // if the receiving parties is empty then we will create commission invoices for all sales agent associated to sales invoice. if (UtilValidate.isEmpty(salesRepPartyIds)) { salesRepPartyIds = EntityUtil.getFieldListFromEntityList( roleQuery.where(invoiceRoleConds).queryList(), "partyId", true); if (UtilValidate.isEmpty(salesRepPartyIds)) { return ServiceUtil.returnError(UtilProperties.getMessage(resource, "No party found with role sales representative for sales invoice " + salesInvoiceId, locale)); } } else { List<String> salesInvoiceRolePartyIds = EntityUtil.getFieldListFromEntityList( roleQuery.where(invoiceRoleConds).queryList(), "partyId", true); if (UtilValidate.isNotEmpty(salesInvoiceRolePartyIds)) { salesRepPartyIds = UtilGenerics.checkList( CollectionUtils.intersection(salesRepPartyIds, salesInvoiceRolePartyIds)); } } invoice = EntityQuery.use(delegator).from("Invoice").where("invoiceId", salesInvoiceId).queryOne(); String invoiceTypeId = invoice.getString("invoiceTypeId"); if ("CUST_RTN_INVOICE".equals(invoiceTypeId)) { isReturn = true; } else if (!"SALES_INVOICE".equals(invoiceTypeId)) { Debug.logWarning("This type of invoice has no commission; returning success", module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingInvoiceCommissionInvalid", locale)); } invoiceItems = EntityQuery.use(delegator).from("InvoiceItem").where("invoiceId", salesInvoiceId) .queryList(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } // Map of commission Lists (of Maps) for each party. // Determine commissions for various parties. for (GenericValue invoiceItem : invoiceItems) { BigDecimal amount = ZERO; BigDecimal quantity = ZERO; quantity = invoiceItem.getBigDecimal("quantity"); amount = invoiceItem.getBigDecimal("amount"); amount = isReturn ? amount.negate() : amount; String productId = invoiceItem.getString("productId"); String invoiceItemSeqId = invoiceItem.getString("invoiceItemSeqId"); String invoiceId = invoiceItem.getString("invoiceId"); // Determine commission parties for this invoiceItem if (UtilValidate.isNotEmpty(productId)) { Map<String, Object> resultMap = null; try { resultMap = dispatcher.runSync("getCommissionForProduct", UtilMisc.<String, Object>toMap("productId", productId, "invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId, "invoiceItemTypeId", invoiceItem.getString("invoiceItemTypeId"), "amount", amount, "quantity", quantity, "userLogin", userLogin)); } catch (GenericServiceException e) { return ServiceUtil.returnError(e.getMessage()); } // build a Map of partyIds (both to and from) in a commission and the amounts // Note that getCommissionForProduct returns a List of Maps with a lot values. See services.xml definition for reference. List<Map<String, Object>> itemCommissions = UtilGenerics .checkList(resultMap.get("commissions")); if (UtilValidate.isNotEmpty(itemCommissions)) { for (Map<String, Object> commissionMap : itemCommissions) { commissionMap.put("invoice", invoice); commissionMap.put("appliedFraction", appliedFraction); if (!billFromVendorInvoiceRoles.contains(commissionMap.get("partyIdFrom")) || !salesRepPartyIds.contains(commissionMap.get("partyIdTo"))) { continue; } String partyIdFromTo = (String) commissionMap.get("partyIdFrom") + (String) commissionMap.get("partyIdTo"); if (!commissionParties.containsKey(partyIdFromTo)) { commissionParties.put(partyIdFromTo, UtilMisc.toList(commissionMap)); } else { (commissionParties.get(partyIdFromTo)).add(commissionMap); } } } } } } Timestamp now = UtilDateTime.nowTimestamp(); // Create invoice for each commission receiving party for (Map.Entry<String, List<Map<String, Object>>> commissionParty : commissionParties.entrySet()) { List<GenericValue> toStore = FastList.newInstance(); List<Map<String, Object>> commList = commissionParty.getValue(); // get the billing parties if (UtilValidate.isEmpty(commList)) { continue; } // From and To are reversed between commission and invoice String partyIdBillTo = (String) (commList.get(0)).get("partyIdFrom"); String partyIdBillFrom = (String) (commList.get(0)).get("partyIdTo"); GenericValue invoice = (GenericValue) (commList.get(0)).get("invoice"); BigDecimal appliedFraction = (BigDecimal) (commList.get(0)).get("appliedFraction"); Long days = (Long) (commList.get(0)).get("days"); // create the invoice record // To and From are in commission's sense, opposite for invoice Map<String, Object> createInvoiceMap = FastMap.newInstance(); createInvoiceMap.put("partyId", partyIdBillTo); createInvoiceMap.put("partyIdFrom", partyIdBillFrom); createInvoiceMap.put("invoiceDate", now); // if there were days associated with the commission agreement, then set a dueDate for the invoice. if (days != null) { createInvoiceMap.put("dueDate", UtilDateTime.getDayEnd(now, days)); } createInvoiceMap.put("invoiceTypeId", "COMMISSION_INVOICE"); // start with INVOICE_IN_PROCESS, in the INVOICE_READY we can't change the invoice (or shouldn't be able to...) createInvoiceMap.put("statusId", "INVOICE_IN_PROCESS"); createInvoiceMap.put("currencyUomId", invoice.getString("currencyUomId")); createInvoiceMap.put("userLogin", userLogin); // store the invoice first Map<String, Object> createInvoiceResult = null; try { createInvoiceResult = dispatcher.runSync("createInvoice", createInvoiceMap); } catch (GenericServiceException e) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingInvoiceCommissionError", locale), null, null, createInvoiceResult); } String invoiceId = (String) createInvoiceResult.get("invoiceId"); // create the bill-from (or pay-to) contact mech as the primary PAYMENT_LOCATION of the party from the store GenericValue partyContactMechPurpose = null; try { partyContactMechPurpose = EntityQuery.use(delegator).from("PartyContactMechPurpose") .where("partyId", partyIdBillTo, "contactMechPurposeTypeId", "BILLING_LOCATION") .queryFirst(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (partyContactMechPurpose != null) { GenericValue invoiceContactMech = delegator.makeValue("InvoiceContactMech", UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", partyContactMechPurpose.getString("contactMechId"), "contactMechPurposeTypeId", "BILLING_LOCATION")); toStore.add(invoiceContactMech); } try { partyContactMechPurpose = EntityQuery.use(delegator).from("PartyContactMechPurpose") .where("partyId", partyIdBillTo, "contactMechPurposeTypeId", "PAYMENT_LOCATION") .queryFirst(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (partyContactMechPurpose != null) { GenericValue invoiceContactMech = delegator.makeValue("InvoiceContactMech", UtilMisc.toMap("invoiceId", invoiceId, "contactMechId", partyContactMechPurpose.getString("contactMechId"), "contactMechPurposeTypeId", "PAYMENT_LOCATION")); toStore.add(invoiceContactMech); } // create the item records for (Map<String, Object> commissionMap : commList) { BigDecimal elemAmount = ((BigDecimal) commissionMap.get("commission")).multiply(appliedFraction); BigDecimal quantity = (BigDecimal) commissionMap.get("quantity"); String invoiceIdFrom = (String) commissionMap.get("invoiceId"); String invoiceItemSeqIdFrom = (String) commissionMap.get("invoiceItemSeqId"); elemAmount = elemAmount.setScale(DECIMALS, ROUNDING); Map<String, Object> resMap = null; try { resMap = dispatcher.runSync("createInvoiceItem", UtilMisc.toMap("invoiceId", invoiceId, "productId", commissionMap.get("productId"), "invoiceItemTypeId", "COMM_INV_ITEM", "quantity", quantity, "amount", elemAmount, "userLogin", userLogin)); dispatcher.runSync("createInvoiceItemAssoc", UtilMisc.toMap("invoiceIdFrom", invoiceIdFrom, "invoiceItemSeqIdFrom", invoiceItemSeqIdFrom, "invoiceIdTo", invoiceId, "invoiceItemSeqIdTo", resMap.get("invoiceItemSeqId"), "invoiceItemAssocTypeId", "COMMISSION_INVOICE", "partyIdFrom", partyIdBillFrom, "partyIdTo", partyIdBillTo, "quantity", quantity, "amount", elemAmount, "userLogin", userLogin)); } catch (GenericServiceException e) { return ServiceUtil.returnError(e.getMessage()); } if (ServiceUtil.isError(resMap)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingInvoiceCommissionErrorItem", locale), null, null, resMap); } } // store value objects try { delegator.storeAll(toStore); } catch (GenericEntityException e) { Debug.logError(e, "Entity/data problem creating commission invoice: " + e.toString(), module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingInvoiceCommissionEntityDataProblem", UtilMisc.toMap("reason", e.toString()), locale)); } invoicesCreated.add(UtilMisc.<String, String>toMap("commissionInvoiceId", invoiceId, "salesRepresentative ", partyIdBillFrom)); } String invCreated = new Integer(invoicesCreated.size()).toString(); Map<String, Object> result = ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "AccountingCommissionInvoicesCreated", UtilMisc.toMap("invoicesCreated", invCreated), locale)); Debug.logInfo("Created Commission invoices for each commission receiving parties " + invCreated, module); result.put("invoicesCreated", invoicesCreated); return result; } public static Map<String, Object> readyInvoices(DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); // Get invoices to make ready List<String> invoicesCreated = UtilGenerics.checkList(context.get("invoicesCreated")); String nextStatusId = "INVOICE_READY"; try { for (String invoiceId : invoicesCreated) { Map<String, Object> setInvoiceStatusResult = dispatcher.runSync("setInvoiceStatus", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "statusId", nextStatusId, "userLogin", userLogin)); if (ServiceUtil.isError(setInvoiceStatusResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingInvoiceCommissionError", locale), null, null, setInvoiceStatusResult); } } } catch (GenericServiceException e) { Debug.logError(e, "Entity/data problem creating commission invoice: " + e.toString(), module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingInvoiceCommissionError", UtilMisc.toMap("reason", e.toString()), locale)); } return ServiceUtil.returnSuccess(); } public static Map<String, Object> createInvoicesFromShipment(DispatchContext dctx, Map<String, Object> context) { //Delegator delegator = dctx.getDelegator(); Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); String shipmentId = (String) context.get("shipmentId"); Locale locale = (Locale) context.get("locale"); List<String> invoicesCreated = FastList.newInstance(); Map<String, Object> response = ServiceUtil.returnSuccess(); GenericValue orderShipment = null; String invoicePerShipment = null; try { orderShipment = EntityQuery.use(delegator).from("OrderShipment").where("shipmentId", shipmentId) .queryFirst(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (orderShipment != null) { String orderId = orderShipment.getString("orderId"); try { GenericValue orderHeader = EntityQuery.use(delegator).from("OrderHeader").where("orderId", orderId) .queryOne(); invoicePerShipment = orderHeader.getString("invoicePerShipment"); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } // Either no orderShipment exists, or there's a null invoicePerShipment in the OrderHeader. // In either case, use the default value from the properties if (invoicePerShipment == null) { invoicePerShipment = EntityUtilProperties.getPropertyValue("AccountingConfig", "create.invoice.per.shipment", delegator); } if ("Y".equals(invoicePerShipment)) { Map<String, Object> serviceContext = UtilMisc.toMap("shipmentIds", UtilMisc.toList(shipmentId), "eventDate", context.get("eventDate"), "userLogin", context.get("userLogin")); try { Map<String, Object> result = dispatcher.runSync("createInvoicesFromShipments", serviceContext); invoicesCreated = UtilGenerics.checkList(result.get("invoicesCreated")); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling createInvoicesFromShipment service; invoice not created for shipment [" + shipmentId + "]", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentService", UtilMisc.toMap("shipmentId", shipmentId), locale)); } response.put("invoicesCreated", invoicesCreated); } return response; } public static Map<String, Object> setInvoicesToReadyFromShipment(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); String shipmentId = (String) context.get("shipmentId"); Locale locale = (Locale) context.get("locale"); GenericValue userLogin = (GenericValue) context.get("userLogin"); // 1. Find all the orders for this shipment // 2. For every order check the invoice // 2.a If the invoice is in In-Process status, then move its status to ready and capture the payment. // 2.b If the invoice is in status other then IN-Process, skip this. These would be already paid and captured. try { EntityQuery.use(delegator).from("Shipment").where("shipmentId", shipmentId).queryOne(); } catch (GenericEntityException e) { Debug.logError(e, "Trouble getting Shipment entity for shipment " + shipmentId, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleGettingShipmentEntity", UtilMisc.toMap("shipmentId", shipmentId), locale)); } List<GenericValue> itemIssuances = FastList.newInstance(); try { itemIssuances = EntityQuery.use(delegator).select("orderId", "shipmentId").from("ItemIssuance") .orderBy("orderId").distinct().queryList(); } catch (GenericEntityException e) { Debug.logError(e, "Problem getting issued items from shipments", module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemGettingItemsFromShipments", locale)); } if (itemIssuances.size() == 0) { Debug.logInfo("No items issued for shipments", module); return ServiceUtil.returnSuccess(); } // The orders can now be placed in separate groups, each for // 1. The group of orders for which payment is already captured. No grouping and action required. // 2. The group of orders for which invoice is IN-Process status. Map<String, GenericValue> ordersWithInProcessInvoice = FastMap.newInstance(); for (GenericValue itemIssuance : itemIssuances) { String orderId = itemIssuance.getString("orderId"); Map<String, Object> billFields = FastMap.newInstance(); billFields.put("orderId", orderId); GenericValue orderItemBilling = null; try { orderItemBilling = EntityQuery.use(delegator).from("OrderItemBilling").where(billFields) .queryFirst(); } catch (GenericEntityException e) { Debug.logError(e, "Problem looking up OrderItemBilling records for " + billFields, module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemLookingUpOrderItemBilling", UtilMisc.toMap("billFields", billFields), locale)); } // if none found, the order does not have any invoice if (orderItemBilling != null) { // orders already have an invoice GenericValue invoice = null; try { invoice = orderItemBilling.getRelatedOne("Invoice", false); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (invoice != null) { if ("INVOICE_IN_PROCESS".equals(invoice.getString("statusId"))) { ordersWithInProcessInvoice.put(orderId, invoice); } } } } // For In-Process invoice, move the status to ready and capture the payment for (GenericValue invoice : ordersWithInProcessInvoice.values()) { String invoiceId = invoice.getString("invoiceId"); Map<String, Object> setInvoiceStatusResult = FastMap.newInstance(); try { setInvoiceStatusResult = dispatcher.runSync("setInvoiceStatus", UtilMisc.<String, Object>toMap( "invoiceId", invoiceId, "statusId", "INVOICE_READY", "userLogin", userLogin)); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (ServiceUtil.isError(setInvoiceStatusResult)) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceFromOrder", locale), null, null, setInvoiceStatusResult); } } return ServiceUtil.returnSuccess(); } public static Map<String, Object> createSalesInvoicesFromDropShipment(DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); String shipmentId = (String) context.get("shipmentId"); Locale locale = (Locale) context.get("locale"); Map<String, Object> serviceContext = UtilMisc.toMap("shipmentIds", UtilMisc.toList(shipmentId), "createSalesInvoicesForDropShipments", Boolean.TRUE, "userLogin", context.get("userLogin")); Map<String, Object> serviceResult; try { serviceResult = dispatcher.runSync("createInvoicesFromShipments", serviceContext); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling createInvoicesFromShipment service; invoice not created for shipment " + shipmentId, module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentService", UtilMisc.toMap("shipmentId", shipmentId), locale)); } return serviceResult; } public static Map<String, Object> createInvoicesFromShipments(DispatchContext dctx, Map<String, ? extends Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); List<String> shipmentIds = UtilGenerics.checkList(context.get("shipmentIds")); Locale locale = (Locale) context.get("locale"); Boolean createSalesInvoicesForDropShipments = (Boolean) context.get("createSalesInvoicesForDropShipments"); if (UtilValidate.isEmpty(createSalesInvoicesForDropShipments)) createSalesInvoicesForDropShipments = Boolean.FALSE; boolean salesShipmentFound = false; boolean purchaseShipmentFound = false; boolean dropShipmentFound = false; List<String> invoicesCreated = FastList.newInstance(); //DEJ20060520: not used? planned to be used? List shipmentIdList = new LinkedList(); for (String tmpShipmentId : shipmentIds) { try { GenericValue shipment = EntityQuery.use(delegator).from("Shipment") .where("shipmentId", tmpShipmentId).queryOne(); if ((shipment.getString("shipmentTypeId") != null) && (shipment.getString("shipmentTypeId").equals("PURCHASE_SHIPMENT"))) { purchaseShipmentFound = true; } else if ((shipment.getString("shipmentTypeId") != null) && (shipment.getString("shipmentTypeId").equals("DROP_SHIPMENT"))) { dropShipmentFound = true; } else { salesShipmentFound = true; } if (purchaseShipmentFound && salesShipmentFound && dropShipmentFound) { return ServiceUtil.returnError(UtilProperties.getMessage( resource, "AccountingShipmentsOfDifferentTypes", UtilMisc.toMap("tmpShipmentId", tmpShipmentId, "shipmentTypeId", shipment.getString("shipmentTypeId")), locale)); } } catch (GenericEntityException e) { Debug.logError(e, "Trouble getting Shipment entity for shipment " + tmpShipmentId, module); return ServiceUtil .returnError(UtilProperties.getMessage(resource, "AccountingTroubleGettingShipmentEntity", UtilMisc.toMap("tmpShipmentId", tmpShipmentId), locale)); } } EntityQuery shipmentQuery = EntityQuery.use(delegator) .where(EntityCondition.makeCondition("shipmentId", EntityOperator.IN, shipmentIds)) .orderBy("shipmentId"); // check the status of the shipment // get the items of the shipment. They can come from ItemIssuance if the shipment were from a sales order, ShipmentReceipt // if it were a purchase order or from the order items of the (possibly linked) orders if the shipment is a drop shipment List<GenericValue> items = null; List<GenericValue> orderItemAssocs = null; try { if (purchaseShipmentFound) { items = shipmentQuery.from("ShipmentReceipt").queryList(); // filter out items which have been received but are not actually owned by an internal organization, so they should not be on a purchase invoice Iterator<GenericValue> itemsIter = items.iterator(); while (itemsIter.hasNext()) { GenericValue item = itemsIter.next(); GenericValue inventoryItem = item.getRelatedOne("InventoryItem", false); GenericValue ownerPartyRole = EntityQuery.use(delegator).from("PartyRole").where("partyId", inventoryItem.get("ownerPartyId"), "roleTypeId", "INTERNAL_ORGANIZATIO").cache() .queryOne(); if (UtilValidate.isEmpty(ownerPartyRole)) { itemsIter.remove(); } } } else if (dropShipmentFound) { List<GenericValue> shipments = shipmentQuery.from("Shipment").queryList(); // Get the list of purchase order IDs related to the shipments List<String> purchaseOrderIds = EntityUtil.getFieldListFromEntityList(shipments, "primaryOrderId", true); if (createSalesInvoicesForDropShipments) { // If a sales invoice is being created for a drop shipment, we have to reference the original sales order items // Get the list of the linked orderIds (original sales orders) orderItemAssocs = EntityQuery.use(delegator).from("OrderItemAssoc") .where(EntityCondition.makeCondition("toOrderId", EntityOperator.IN, purchaseOrderIds)) .queryList(); // Get only the order items which are indirectly related to the purchase order - this limits the list to the drop ship group(s) items = EntityUtil.getRelated("FromOrderItem", null, orderItemAssocs, false); } else { // If it's a purchase invoice being created, the order items for that purchase orders can be used directly items = EntityQuery.use(delegator).from("OrderItem") .where(EntityCondition.makeCondition("orderId", EntityOperator.IN, purchaseOrderIds)) .queryList(); } } else { items = shipmentQuery.from("ItemIssuance").queryList(); } } catch (GenericEntityException e) { Debug.logError(e, "Problem getting issued items from shipments", module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemGettingItemsFromShipments", locale)); } if (items.size() == 0) { Debug.logInfo("No items issued for shipments", module); return ServiceUtil.returnSuccess(); } // group items by order Map<String, List<GenericValue>> shippedOrderItems = FastMap.newInstance(); for (GenericValue item : items) { String orderId = item.getString("orderId"); String orderItemSeqId = item.getString("orderItemSeqId"); List<GenericValue> itemsByOrder = shippedOrderItems.get(orderId); if (itemsByOrder == null) { itemsByOrder = FastList.newInstance(); } // check and make sure we haven't already billed for this issuance or shipment receipt List<EntityCondition> billFields = FastList.newInstance(); billFields.add(EntityCondition.makeCondition("orderId", orderId)); billFields.add(EntityCondition.makeCondition("orderItemSeqId", orderItemSeqId)); billFields .add(EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "INVOICE_CANCELLED")); if (dropShipmentFound) { // Drop shipments have neither issuances nor receipts, so this check is meaningless itemsByOrder.add(item); shippedOrderItems.put(orderId, itemsByOrder); continue; } else if (item.getEntityName().equals("ItemIssuance")) { billFields.add(EntityCondition.makeCondition("itemIssuanceId", item.get("itemIssuanceId"))); } else if (item.getEntityName().equals("ShipmentReceipt")) { billFields.add(EntityCondition.makeCondition("shipmentReceiptId", item.getString("receiptId"))); } List<GenericValue> itemBillings = null; try { itemBillings = EntityQuery.use(delegator).from("OrderItemBillingAndInvoiceAndItem") .where(billFields).queryList(); } catch (GenericEntityException e) { Debug.logError(e, "Problem looking up OrderItemBilling records for " + billFields, module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemLookingUpOrderItemBilling", UtilMisc.toMap("billFields", billFields), locale)); } // if none found, then okay to bill if (itemBillings.size() == 0) { itemsByOrder.add(item); } // update the map with modified list shippedOrderItems.put(orderId, itemsByOrder); } // make sure we aren't billing items already invoiced i.e. items billed as digital (FINDIG) Set<String> orders = shippedOrderItems.keySet(); for (String orderId : orders) { // we'll only use this list to figure out which ones to send List<GenericValue> billItems = shippedOrderItems.get(orderId); // a new list to be used to pass to the create invoice service List<GenericValue> toBillItems = FastList.newInstance(); // map of available quantities so we only have to calc once Map<String, BigDecimal> itemQtyAvail = FastMap.newInstance(); // now we will check each issuance and make sure it hasn't already been billed for (GenericValue issue : billItems) { BigDecimal issueQty = ZERO; if (issue.getEntityName().equals("ShipmentReceipt")) { issueQty = issue.getBigDecimal("quantityAccepted"); } else { issueQty = issue.getBigDecimal("quantity"); } BigDecimal billAvail = itemQtyAvail.get(issue.getString("orderItemSeqId")); if (billAvail == null) { List<EntityCondition> lookup = FastList.newInstance(); lookup.add(EntityCondition.makeCondition("orderId", orderId)); lookup.add(EntityCondition.makeCondition("orderItemSeqId", issue.get("orderItemSeqId"))); lookup.add(EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "INVOICE_CANCELLED")); GenericValue orderItem = null; List<GenericValue> billed = null; BigDecimal orderedQty = null; try { orderItem = issue.getEntityName().equals("OrderItem") ? issue : issue.getRelatedOne("OrderItem", false); // total ordered orderedQty = orderItem.getBigDecimal("quantity"); if (dropShipmentFound && createSalesInvoicesForDropShipments.booleanValue()) { // Override the issueQty with the quantity from the purchase order item GenericValue orderItemAssoc = EntityUtil.getFirst(EntityUtil.filterByAnd( orderItemAssocs, UtilMisc.toMap("orderId", issue.getString("orderId"), "orderItemSeqId", issue.getString("orderItemSeqId")))); GenericValue purchaseOrderItem = orderItemAssoc.getRelatedOne("ToOrderItem", false); orderItem.set("quantity", purchaseOrderItem.getBigDecimal("quantity")); issueQty = purchaseOrderItem.getBigDecimal("quantity"); } billed = EntityQuery.use(delegator).from("OrderItemBillingAndInvoiceAndItem").where(lookup) .queryList(); } catch (GenericEntityException e) { Debug.logError(e, "Problem getting OrderItem/OrderItemBilling records " + lookup, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingProblemGettingOrderItemOrderItemBilling", UtilMisc.toMap("lookup", lookup), locale)); } // add up the already billed total if (billed.size() > 0) { BigDecimal billedQuantity = ZERO; for (GenericValue oib : billed) { BigDecimal qty = oib.getBigDecimal("quantity"); if (qty != null) { billedQuantity = billedQuantity.add(qty).setScale(DECIMALS, ROUNDING); } } BigDecimal leftToBill = orderedQty.subtract(billedQuantity).setScale(DECIMALS, ROUNDING); billAvail = leftToBill; } else { billAvail = orderedQty; } } // no available means we cannot bill anymore if (billAvail != null && billAvail.signum() == 1) { // this checks if billAvail is a positive non-zero number if (issueQty != null && issueQty.compareTo(billAvail) > 0) { // can only bill some of the issuance; others have been billed already if ("ShipmentReceipt".equals(issue.getEntityName())) { issue.set("quantityAccepted", billAvail); } else { issue.set("quantity", billAvail); } billAvail = ZERO; } else { // now have been billed billAvail = billAvail.subtract(issueQty).setScale(DECIMALS, ROUNDING); } // okay to bill these items; but none else toBillItems.add(issue); } // update the available to bill quantity for the next pass itemQtyAvail.put(issue.getString("orderItemSeqId"), billAvail); } OrderReadHelper orh = new OrderReadHelper(delegator, orderId); GenericValue productStore = orh.getProductStore(); String prorateShipping = productStore != null ? productStore.getString("prorateShipping") : "N"; // If shipping charges are not prorated, the shipments need to be examined for additional shipping charges if ("N".equalsIgnoreCase(prorateShipping)) { // Get the set of filtered shipments List<GenericValue> invoiceableShipments = null; try { if (dropShipmentFound) { List<String> invoiceablePrimaryOrderIds = null; if (createSalesInvoicesForDropShipments) { // If a sales invoice is being created for the drop shipment, we need to reference back to the original purchase order IDs // Get the IDs for orders which have billable items List<String> invoiceableLinkedOrderIds = EntityUtil .getFieldListFromEntityList(toBillItems, "orderId", true); // Get back the IDs of the purchase orders - this will be a list of the purchase order items which are billable by virtue of not having been // invoiced in a previous sales invoice List<GenericValue> reverseOrderItemAssocs = EntityUtil .filterByCondition(orderItemAssocs, EntityCondition.makeCondition("orderId", EntityOperator.IN, invoiceableLinkedOrderIds)); invoiceablePrimaryOrderIds = EntityUtil .getFieldListFromEntityList(reverseOrderItemAssocs, "toOrderId", true); } else { // If a purchase order is being created for a drop shipment, the purchase order IDs can be used directly invoiceablePrimaryOrderIds = EntityUtil.getFieldListFromEntityList(toBillItems, "orderId", true); } // Get the list of shipments which are associated with the filtered purchase orders if (!UtilValidate.isEmpty(invoiceablePrimaryOrderIds)) { invoiceableShipments = EntityQuery.use(delegator).from("Shipment") .where(UtilMisc.toList( EntityCondition.makeCondition("primaryOrderId", EntityOperator.IN, invoiceablePrimaryOrderIds), EntityCondition.makeCondition("shipmentId", EntityOperator.IN, shipmentIds))) .queryList(); } } else { List<String> invoiceableShipmentIds = EntityUtil.getFieldListFromEntityList(toBillItems, "shipmentId", true); if (UtilValidate.isNotEmpty(invoiceableShipmentIds)) { invoiceableShipments = EntityQuery .use(delegator).from("Shipment").where(EntityCondition .makeCondition("shipmentId", EntityOperator.IN, invoiceableShipmentIds)) .queryList(); } } } catch (GenericEntityException e) { Debug.logError(e, "Trouble calling createInvoicesFromShipments service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentsService", locale)); } // Total the additional shipping charges for the shipments Map<GenericValue, BigDecimal> additionalShippingCharges = FastMap.newInstance(); BigDecimal totalAdditionalShippingCharges = ZERO; if (UtilValidate.isNotEmpty(invoiceableShipments)) { for (GenericValue shipment : invoiceableShipments) { if (shipment.get("additionalShippingCharge") == null) continue; BigDecimal shipmentAdditionalShippingCharges = shipment .getBigDecimal("additionalShippingCharge").setScale(DECIMALS, ROUNDING); additionalShippingCharges.put(shipment, shipmentAdditionalShippingCharges); totalAdditionalShippingCharges = totalAdditionalShippingCharges .add(shipmentAdditionalShippingCharges); } } // If the additional shipping charges are greater than zero, process them if (totalAdditionalShippingCharges.signum() == 1) { // Add an OrderAdjustment to the order for each additional shipping charge for (Map.Entry<GenericValue, BigDecimal> entry : additionalShippingCharges.entrySet()) { GenericValue shipment = entry.getKey(); BigDecimal additionalShippingCharge = entry.getValue(); String shipmentId = shipment.getString("shipmentId"); Map<String, Object> createOrderAdjustmentContext = FastMap.newInstance(); createOrderAdjustmentContext.put("orderId", orderId); createOrderAdjustmentContext.put("orderAdjustmentTypeId", "SHIPPING_CHARGES"); String addtlChargeDescription = shipment.getString("addtlShippingChargeDesc"); if (UtilValidate.isEmpty(addtlChargeDescription)) { addtlChargeDescription = UtilProperties.getMessage(resource, "AccountingAdditionalShippingChargeForShipment", UtilMisc.toMap("shipmentId", shipmentId), locale); } createOrderAdjustmentContext.put("description", addtlChargeDescription); createOrderAdjustmentContext.put("sourceReferenceId", shipmentId); createOrderAdjustmentContext.put("amount", additionalShippingCharge); createOrderAdjustmentContext.put("userLogin", context.get("userLogin")); String shippingOrderAdjustmentId = null; try { Map<String, Object> createOrderAdjustmentResult = dispatcher .runSync("createOrderAdjustment", createOrderAdjustmentContext); shippingOrderAdjustmentId = (String) createOrderAdjustmentResult .get("orderAdjustmentId"); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling createOrderAdjustment service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateOrderAdjustmentService", locale)); } // Obtain a list of OrderAdjustments due to tax on the shipping charges, if any GenericValue billToParty = orh.getBillToParty(); GenericValue payToParty = orh.getBillFromParty(); GenericValue destinationContactMech = null; try { destinationContactMech = shipment.getRelatedOne("DestinationPostalAddress", false); } catch (GenericEntityException e) { Debug.logError(e, "Trouble calling createInvoicesFromShipment service; invoice not created for shipment " + shipmentId, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoicesFromShipmentService", locale)); } List<Object> emptyList = FastList.newInstance(); Map<String, Object> calcTaxContext = FastMap.newInstance(); calcTaxContext.put("productStoreId", orh.getProductStoreId()); calcTaxContext.put("payToPartyId", payToParty.getString("partyId")); calcTaxContext.put("billToPartyId", billToParty.getString("partyId")); calcTaxContext.put("orderShippingAmount", totalAdditionalShippingCharges); calcTaxContext.put("shippingAddress", destinationContactMech); // These parameters don't matter if we're only worried about adjustments on the shipping charges calcTaxContext.put("itemProductList", emptyList); calcTaxContext.put("itemAmountList", emptyList); calcTaxContext.put("itemPriceList", emptyList); calcTaxContext.put("itemQuantityList", emptyList); calcTaxContext.put("itemShippingList", emptyList); Map<String, Object> calcTaxResult = null; try { calcTaxResult = dispatcher.runSync("calcTax", calcTaxContext); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling calcTaxService", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalcTaxService", locale)); } List<GenericValue> orderAdjustments = UtilGenerics .checkList(calcTaxResult.get("orderAdjustments")); // If we have any OrderAdjustments due to tax on shipping, store them and add them to the total if (orderAdjustments != null) { for (GenericValue orderAdjustment : orderAdjustments) { totalAdditionalShippingCharges = totalAdditionalShippingCharges .add(orderAdjustment.getBigDecimal("amount").setScale(DECIMALS, ROUNDING)); orderAdjustment.set("orderAdjustmentId", delegator.getNextSeqId("OrderAdjustment")); orderAdjustment.set("orderId", orderId); orderAdjustment.set("orderItemSeqId", "_NA_"); orderAdjustment.set("shipGroupSeqId", shipment.getString("primaryShipGroupSeqId")); orderAdjustment.set("originalAdjustmentId", shippingOrderAdjustmentId); } try { delegator.storeAll(orderAdjustments); } catch (GenericEntityException e) { Debug.logError(e, "Problem storing OrderAdjustments: " + orderAdjustments, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingProblemStoringOrderAdjustments", UtilMisc.toMap("orderAdjustments", orderAdjustments), locale)); } } // If part of the order was paid via credit card, try to charge it for the additional shipping List<GenericValue> orderPaymentPreferences = null; try { orderPaymentPreferences = EntityQuery.use(delegator).from("OrderPaymentPreference") .where("orderId", orderId, "paymentMethodTypeId", "CREDIT_CARD").queryList(); } catch (GenericEntityException e) { Debug.logError(e, "Problem getting OrderPaymentPreference records", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingProblemGettingOrderPaymentPreferences", locale)); } // Use the first credit card we find, for the sake of simplicity String paymentMethodId = null; GenericValue cardOrderPaymentPref = EntityUtil.getFirst(orderPaymentPreferences); if (cardOrderPaymentPref != null) { paymentMethodId = cardOrderPaymentPref.getString("paymentMethodId"); } if (paymentMethodId != null) { // Release all outstanding (not settled or cancelled) authorizations, while keeping a running // total of their amounts so that the total plus the additional shipping charges can be authorized again // all at once. BigDecimal totalNewAuthAmount = totalAdditionalShippingCharges.setScale(DECIMALS, ROUNDING); for (GenericValue orderPaymentPreference : orderPaymentPreferences) { if (!(orderPaymentPreference.getString("statusId").equals("PAYMENT_SETTLED") || orderPaymentPreference.getString("statusId") .equals("PAYMENT_CANCELLED"))) { GenericValue authTransaction = PaymentGatewayServices .getAuthTransaction(orderPaymentPreference); if (authTransaction != null && authTransaction.get("amount") != null) { // Update the total authorized amount totalNewAuthAmount = totalNewAuthAmount.add(authTransaction .getBigDecimal("amount").setScale(DECIMALS, ROUNDING)); // Release the authorization for the OrderPaymentPreference Map<String, Object> prefReleaseResult = null; try { prefReleaseResult = dispatcher.runSync("releaseOrderPaymentPreference", UtilMisc.toMap("orderPaymentPreferenceId", orderPaymentPreference .getString("orderPaymentPreferenceId"), "userLogin", context.get("userLogin"))); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling releaseOrderPaymentPreference service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingReleaseOrderPaymentPreferenceService", locale)); } if (ServiceUtil.isError(prefReleaseResult) || ServiceUtil.isFailure(prefReleaseResult)) { String errMsg = ServiceUtil.getErrorMessage(prefReleaseResult); Debug.logError(errMsg, module); return ServiceUtil.returnError(errMsg); } } } } // Create a new OrderPaymentPreference for the order to handle the new (totalled) charge. Don't // set the maxAmount so that it doesn't interfere with other authorizations Map<String, Object> serviceContext = UtilMisc.toMap("orderId", orderId, "paymentMethodId", paymentMethodId, "paymentMethodTypeId", "CREDIT_CARD", "userLogin", context.get("userLogin")); String orderPaymentPreferenceId = null; try { Map<String, Object> result = dispatcher.runSync("createOrderPaymentPreference", serviceContext); orderPaymentPreferenceId = (String) result.get("orderPaymentPreferenceId"); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling createOrderPaymentPreference service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateOrderPaymentPreferenceService", locale)); } // Attempt to authorize the new orderPaymentPreference Map<String, Object> authResult = null; try { // Use an overrideAmount because the maxAmount wasn't set on the OrderPaymentPreference authResult = dispatcher.runSync("authOrderPaymentPreference", UtilMisc.toMap("orderPaymentPreferenceId", orderPaymentPreferenceId, "overrideAmount", totalNewAuthAmount, "userLogin", context.get("userLogin"))); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling authOrderPaymentPreference service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingAuthOrderPaymentPreferenceService", locale)); } // If the authorization fails, create the invoice anyway, but make a note of it boolean authFinished = ((Boolean) authResult.get("finished")).booleanValue(); boolean authErrors = ((Boolean) authResult.get("errors")).booleanValue(); if (authErrors || !authFinished) { String errMsg = UtilProperties.getMessage(resource, "AccountingUnableToAuthAdditionalShipCharges", UtilMisc.toMap("shipmentId", shipmentId, "paymentMethodId", paymentMethodId, "orderPaymentPreferenceId", orderPaymentPreferenceId), locale); Debug.logError(errMsg, module); } } } } } else { Debug.logInfo(UtilProperties.getMessage(resource, "AccountingIgnoringAdditionalShipCharges", UtilMisc.toMap("productStoreId", orh.getProductStoreId()), locale), module); } String invoiceId = null; GenericValue shipmentItemBilling = null; String shipmentId = shipmentIds.get(0); try { shipmentItemBilling = EntityQuery.use(delegator).from("ShipmentItemBilling") .where("shipmentId", shipmentId).queryFirst(); } catch (GenericEntityException e) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemGettingShipmentItemBilling", locale)); } if (shipmentItemBilling != null) { invoiceId = shipmentItemBilling.getString("invoiceId"); } // call the createInvoiceForOrder service for each order Map<String, Object> serviceContext = UtilMisc.toMap("orderId", orderId, "billItems", toBillItems, "invoiceId", invoiceId, "eventDate", context.get("eventDate"), "userLogin", context.get("userLogin")); try { Map<String, Object> result = dispatcher.runSync("createInvoiceForOrder", serviceContext); invoicesCreated.add((String) result.get("invoiceId")); } catch (GenericServiceException e) { Debug.logError(e, "Trouble calling createInvoiceForOrder service; invoice not created for shipment", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCreateInvoiceForOrderService", locale)); } } Map<String, Object> response = ServiceUtil.returnSuccess(); response.put("invoicesCreated", invoicesCreated); return response; } private static String getInvoiceItemType(Delegator delegator, String key1, String key2, String invoiceTypeId, String defaultValue) { GenericValue itemMap = null; try { if (UtilValidate.isNotEmpty(key1)) { itemMap = EntityQuery.use(delegator).from("InvoiceItemTypeMap") .where("invoiceItemMapKey", key1, "invoiceTypeId", invoiceTypeId).cache().queryOne(); } if (itemMap == null && UtilValidate.isNotEmpty(key2)) { itemMap = EntityQuery.use(delegator).from("InvoiceItemTypeMap") .where("invoiceItemMapKey", key2, "invoiceTypeId", invoiceTypeId).cache().queryOne(); } } catch (GenericEntityException e) { Debug.logError(e, "Trouble getting InvoiceItemTypeMap entity record", module); return defaultValue; } if (itemMap != null) { return itemMap.getString("invoiceItemTypeId"); } else { return defaultValue; } } public static Map<String, Object> createInvoicesFromReturnShipment(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); Locale locale = (Locale) context.get("locale"); String shipmentId = (String) context.get("shipmentId"); String errorMsg = UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceForShipment", UtilMisc.toMap("shipmentId", shipmentId), locale); boolean salesReturnFound = false; boolean purchaseReturnFound = false; List<String> invoicesCreated = FastList.newInstance(); try { // get the shipment and validate that it is a sales return GenericValue shipment = EntityQuery.use(delegator).from("Shipment").where("shipmentId", shipmentId) .queryOne(); if (shipment == null) { return ServiceUtil.returnError( errorMsg + UtilProperties.getMessage(resource, "AccountingShipmentNotFound", locale)); } if (shipment.getString("shipmentTypeId").equals("SALES_RETURN")) { salesReturnFound = true; } else if ("PURCHASE_RETURN".equals(shipment.getString("shipmentTypeId"))) { purchaseReturnFound = true; } if (!(salesReturnFound || purchaseReturnFound)) { return ServiceUtil.returnError(errorMsg + UtilProperties.getMessage(resource, "AccountingShipmentNotSalesReturnAndPurchaseReturn", locale)); } // get the items of the shipment. They can come from ItemIssuance if the shipment were from a purchase return, ShipmentReceipt if it were from a sales return List<GenericValue> shippedItems = null; if (salesReturnFound) { shippedItems = shipment.getRelated("ShipmentReceipt", null, null, false); } else if (purchaseReturnFound) { shippedItems = shipment.getRelated("ItemIssuance", null, null, false); } if (shippedItems == null) { Debug.logInfo("No items issued for shipments", module); return ServiceUtil.returnSuccess(); } // group the shipments by returnId (because we want a seperate itemized invoice for each return) Map<String, List<GenericValue>> itemsShippedGroupedByReturn = FastMap.newInstance(); for (GenericValue item : shippedItems) { String returnId = null; String returnItemSeqId = null; if (item.getEntityName().equals("ShipmentReceipt")) { returnId = item.getString("returnId"); } else if (item.getEntityName().equals("ItemIssuance")) { GenericValue returnItemShipment = EntityQuery.use(delegator).from("ReturnItemShipment") .where("shipmentId", item.get("shipmentId"), "shipmentItemSeqId", item.get("shipmentItemSeqId")) .queryFirst(); returnId = returnItemShipment.getString("returnId"); returnItemSeqId = returnItemShipment.getString("returnItemSeqId"); } // see if there are ReturnItemBillings for this item Long billingCount = 0L; if (item.getEntityName().equals("ShipmentReceipt")) { billingCount = EntityQuery.use(delegator).from("ReturnItemBilling") .where("shipmentReceiptId", item.get("receiptId"), "returnId", returnId, "returnItemSeqId", item.get("returnItemSeqId")) .queryCount(); } else if (item.getEntityName().equals("ItemIssuance")) { billingCount = EntityQuery.use(delegator).from("ReturnItemBilling") .where("returnId", returnId, "returnItemSeqId", returnItemSeqId).queryCount(); } // if there are billings, we have already billed the item, so skip it if (billingCount > 0) continue; // get the List of items shipped to/from this returnId List<GenericValue> billItems = itemsShippedGroupedByReturn.get(returnId); if (billItems == null) { billItems = FastList.newInstance(); } // add our item to the group and put it back in the map billItems.add(item); itemsShippedGroupedByReturn.put(returnId, billItems); } // loop through the returnId keys in the map and invoke the createInvoiceFromReturn service for each for (Map.Entry<String, List<GenericValue>> entry : itemsShippedGroupedByReturn.entrySet()) { String returnId = entry.getKey(); List<GenericValue> billItems = entry.getValue(); if (Debug.verboseOn()) { Debug.logVerbose( "Creating invoice for return [" + returnId + "] with items: " + billItems.toString(), module); } Map<String, Object> input = UtilMisc.toMap("returnId", returnId, "billItems", billItems, "userLogin", context.get("userLogin")); Map<String, Object> serviceResults = dispatcher.runSync("createInvoiceFromReturn", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } // put the resulting invoiceId in the return list invoicesCreated.add((String) serviceResults.get("invoiceId")); } } catch (GenericServiceException e) { Debug.logError(e, errorMsg + e.getMessage(), module); return ServiceUtil.returnError(errorMsg + e.getMessage()); } catch (GenericEntityException e) { Debug.logError(e, errorMsg + e.getMessage(), module); return ServiceUtil.returnError(errorMsg + e.getMessage()); } Map<String, Object> result = ServiceUtil.returnSuccess(); result.put("invoicesCreated", invoicesCreated); return result; } public static Map<String, Object> createInvoiceFromReturn(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); String returnId = (String) context.get("returnId"); List<GenericValue> billItems = UtilGenerics.checkList(context.get("billItems")); String errorMsg = UtilProperties.getMessage(resource, "AccountingErrorCreatingInvoiceForReturn", UtilMisc.toMap("returnId", returnId), locale); // List invoicesCreated = new ArrayList(); try { String invoiceTypeId; String description; // get the return header GenericValue returnHeader = EntityQuery.use(delegator).from("ReturnHeader").where("returnId", returnId) .queryOne(); if (returnHeader == null || returnHeader.get("returnHeaderTypeId") == null) { return ServiceUtil.returnError("Return type cannot be null"); } if (returnHeader.getString("returnHeaderTypeId").startsWith("CUSTOMER_")) { invoiceTypeId = "CUST_RTN_INVOICE"; description = "Return Invoice for Customer Return #" + returnId; } else { invoiceTypeId = "PURC_RTN_INVOICE"; description = "Return Invoice for Vendor Return #" + returnId; } // set the invoice data Map<String, Object> input = UtilMisc.<String, Object>toMap("invoiceTypeId", invoiceTypeId, "statusId", "INVOICE_IN_PROCESS"); input.put("partyId", returnHeader.get("toPartyId")); input.put("partyIdFrom", returnHeader.get("fromPartyId")); input.put("currencyUomId", returnHeader.get("currencyUomId")); input.put("invoiceDate", UtilDateTime.nowTimestamp()); input.put("description", description); input.put("billingAccountId", returnHeader.get("billingAccountId")); input.put("userLogin", userLogin); // call the service to create the invoice Map<String, Object> serviceResults = dispatcher.runSync("createInvoice", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } String invoiceId = (String) serviceResults.get("invoiceId"); // keep track of the invoice total vs the promised return total (how much the customer promised to return) BigDecimal invoiceTotal = ZERO; BigDecimal promisedTotal = ZERO; // loop through shipment receipts to create invoice items and return item billings for each item and adjustment int invoiceItemSeqNum = 1; String invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); for (GenericValue item : billItems) { boolean shipmentReceiptFound = false; boolean itemIssuanceFound = false; if ("ShipmentReceipt".equals(item.getEntityName())) { shipmentReceiptFound = true; } else if ("ItemIssuance".equals(item.getEntityName())) { itemIssuanceFound = true; } else { Debug.logError("Unexpected entity " + item + " of type " + item.getEntityName(), module); } // we need the related return item and product GenericValue returnItem = null; if (shipmentReceiptFound) { returnItem = item.getRelatedOne("ReturnItem", true); } else if (itemIssuanceFound) { GenericValue shipmentItem = item.getRelatedOne("ShipmentItem", true); GenericValue returnItemShipment = EntityUtil .getFirst(shipmentItem.getRelated("ReturnItemShipment", null, null, false)); returnItem = returnItemShipment.getRelatedOne("ReturnItem", true); } if (returnItem == null) continue; // Just to prevent NPE GenericValue product = returnItem.getRelatedOne("Product", true); // extract the return price as a big decimal for convenience BigDecimal returnPrice = returnItem.getBigDecimal("returnPrice"); // determine invoice item type from the return item type String invoiceItemTypeId = getInvoiceItemType(delegator, returnItem.getString("returnItemTypeId"), null, invoiceTypeId, null); if (invoiceItemTypeId == null) { return ServiceUtil.returnError(errorMsg + UtilProperties.getMessage(resource, "AccountingNoKnownInvoiceItemTypeReturnItemType", UtilMisc.toMap("returnItemTypeId", returnItem.getString("returnItemTypeId")), locale)); } BigDecimal quantity = BigDecimal.ZERO; if (shipmentReceiptFound) { quantity = item.getBigDecimal("quantityAccepted"); } else if (itemIssuanceFound) { quantity = item.getBigDecimal("quantity"); } // create the invoice item for this shipment receipt input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", quantity); input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack input.put("amount", returnItem.get("returnPrice")); input.put("productId", returnItem.get("productId")); input.put("taxableFlag", product.get("taxable")); input.put("description", returnItem.get("description")); // TODO: what about the productFeatureId? input.put("userLogin", userLogin); serviceResults = dispatcher.runSync("createInvoiceItem", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } // copy the return item information into ReturnItemBilling input = UtilMisc.toMap("returnId", returnId, "returnItemSeqId", returnItem.get("returnItemSeqId"), "invoiceId", invoiceId); input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack input.put("quantity", quantity); input.put("amount", returnItem.get("returnPrice")); input.put("userLogin", userLogin); if (shipmentReceiptFound) { input.put("shipmentReceiptId", item.get("receiptId")); } serviceResults = dispatcher.runSync("createReturnItemBilling", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } if (Debug.verboseOn()) { Debug.logVerbose("Creating Invoice Item with amount " + returnPrice + " and quantity " + quantity + " for shipment [" + item.getString("shipmentId") + ":" + item.getString("shipmentItemSeqId") + "]", module); } String parentInvoiceItemSeqId = invoiceItemSeqId; // increment the seqId counter after creating the invoice item and return item billing invoiceItemSeqNum += 1; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); // keep a running total (note: a returnItem may have many receipts. hence, the promised total quantity is the receipt quantityAccepted + quantityRejected) BigDecimal cancelQuantity = ZERO; if (shipmentReceiptFound) { cancelQuantity = item.getBigDecimal("quantityRejected"); } else if (itemIssuanceFound) { cancelQuantity = item.getBigDecimal("cancelQuantity"); } if (cancelQuantity == null) cancelQuantity = ZERO; BigDecimal actualAmount = returnPrice.multiply(quantity).setScale(DECIMALS, ROUNDING); BigDecimal promisedAmount = returnPrice.multiply(quantity.add(cancelQuantity)).setScale(DECIMALS, ROUNDING); invoiceTotal = invoiceTotal.add(actualAmount).setScale(DECIMALS, ROUNDING); promisedTotal = promisedTotal.add(promisedAmount).setScale(DECIMALS, ROUNDING); // for each adjustment related to this ReturnItem, create a separate invoice item List<GenericValue> adjustments = returnItem.getRelated("ReturnAdjustment", null, null, true); for (GenericValue adjustment : adjustments) { if (adjustment.get("amount") == null) { Debug.logWarning("Return adjustment [" + adjustment.get("returnAdjustmentId") + "] has null amount and will be skipped", module); continue; } // determine invoice item type from the return item type invoiceItemTypeId = getInvoiceItemType(delegator, adjustment.getString("returnAdjustmentTypeId"), null, invoiceTypeId, null); if (invoiceItemTypeId == null) { return ServiceUtil .returnError( errorMsg + UtilProperties .getMessage(resource, "AccountingNoKnownInvoiceItemTypeReturnAdjustmentType", UtilMisc.toMap("returnAdjustmentTypeId", adjustment.getString("returnAdjustmentTypeId")), locale)); } // prorate the adjustment amount by the returned amount; do not round ratio BigDecimal ratio = quantity.divide(returnItem.getBigDecimal("returnQuantity"), 100, ROUNDING); BigDecimal amount = adjustment.getBigDecimal("amount"); amount = amount.multiply(ratio).setScale(DECIMALS, ROUNDING); if (Debug.verboseOn()) { Debug.logVerbose("Creating Invoice Item with amount " + adjustment.getBigDecimal("amount") + " prorated to " + amount + " for return adjustment [" + adjustment.getString("returnAdjustmentId") + "]", module); } // prepare invoice item data for this adjustment input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", BigDecimal.ONE); input.put("amount", amount); input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack input.put("productId", returnItem.get("productId")); input.put("description", adjustment.get("description")); input.put("overrideGlAccountId", adjustment.get("overrideGlAccountId")); input.put("parentInvoiceId", invoiceId); input.put("parentInvoiceItemSeqId", parentInvoiceItemSeqId); input.put("taxAuthPartyId", adjustment.get("taxAuthPartyId")); input.put("taxAuthGeoId", adjustment.get("taxAuthGeoId")); input.put("userLogin", userLogin); // only set taxable flag when the adjustment is not a tax // TODO: Note that we use the value of Product.taxable here. This is not an ideal solution. Instead, use returnAdjustment.includeInTax if (adjustment.get("returnAdjustmentTypeId").equals("RET_SALES_TAX_ADJ")) { input.put("taxableFlag", "N"); } // create the invoice item serviceResults = dispatcher.runSync("createInvoiceItem", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } // increment the seqId counter invoiceItemSeqNum += 1; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); // keep a running total (promised adjustment in this case is the same as the invoice adjustment) invoiceTotal = invoiceTotal.add(amount).setScale(DECIMALS, ROUNDING); promisedTotal = promisedTotal.add(amount).setScale(DECIMALS, ROUNDING); } } // ratio of the invoice total to the promised total so far or zero if the amounts were zero BigDecimal actualToPromisedRatio = ZERO; if (invoiceTotal.signum() != 0) { actualToPromisedRatio = invoiceTotal.divide(promisedTotal, 100, ROUNDING); // do not round ratio } // loop through return-wide adjustments and create invoice items for each List<GenericValue> adjustments = returnHeader.getRelated("ReturnAdjustment", UtilMisc.toMap("returnItemSeqId", "_NA_"), null, true); for (GenericValue adjustment : adjustments) { // determine invoice item type from the return item type String invoiceItemTypeId = getInvoiceItemType(delegator, adjustment.getString("returnAdjustmentTypeId"), null, invoiceTypeId, null); if (invoiceItemTypeId == null) { return ServiceUtil .returnError( errorMsg + UtilProperties .getMessage(resource, "AccountingNoKnownInvoiceItemTypeReturnAdjustmentType", UtilMisc.toMap("returnAdjustmentTypeId", adjustment.getString("returnAdjustmentTypeId")), locale)); } // prorate the adjustment amount by the actual to promised ratio BigDecimal amount = adjustment.getBigDecimal("amount").multiply(actualToPromisedRatio) .setScale(DECIMALS, ROUNDING); if (Debug.verboseOn()) { Debug.logVerbose("Creating Invoice Item with amount " + adjustment.getBigDecimal("amount") + " prorated to " + amount + " for return adjustment [" + adjustment.getString("returnAdjustmentId") + "]", module); } // prepare the invoice item for the return-wide adjustment input = UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemTypeId", invoiceItemTypeId, "quantity", BigDecimal.ONE); input.put("amount", amount); input.put("invoiceItemSeqId", "" + invoiceItemSeqId); // turn the int into a string with ("" + int) hack input.put("description", adjustment.get("description")); input.put("overrideGlAccountId", adjustment.get("overrideGlAccountId")); input.put("taxAuthPartyId", adjustment.get("taxAuthPartyId")); input.put("taxAuthGeoId", adjustment.get("taxAuthGeoId")); input.put("userLogin", userLogin); // XXX TODO Note: we need to implement ReturnAdjustment.includeInTax for this to work properly input.put("taxableFlag", adjustment.get("includeInTax")); // create the invoice item serviceResults = dispatcher.runSync("createInvoiceItem", input); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } // increment the seqId counter invoiceItemSeqNum += 1; invoiceItemSeqId = UtilFormatOut.formatPaddedNumber(invoiceItemSeqNum, INVOICE_ITEM_SEQUENCE_ID_DIGITS); } // Set the invoice to READY serviceResults = dispatcher.runSync("setInvoiceStatus", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "statusId", "INVOICE_READY", "userLogin", userLogin)); if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(errorMsg, null, null, serviceResults); } // return the invoiceId Map<String, Object> results = ServiceUtil.returnSuccess(); results.put("invoiceId", invoiceId); return results; } catch (GenericServiceException e) { Debug.logError(e, errorMsg + e.getMessage(), module); return ServiceUtil.returnError(errorMsg + e.getMessage()); } catch (GenericEntityException e) { Debug.logError(e, errorMsg + e.getMessage(), module); return ServiceUtil.returnError(errorMsg + e.getMessage()); } } public static Map<String, Object> checkInvoicePaymentApplications(DispatchContext ctx, Map<String, Object> context) { Delegator delegator = ctx.getDelegator(); LocalDispatcher dispatcher = ctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); if (DECIMALS == -1 || ROUNDING == -1) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingAritmeticPropertiesNotConfigured", locale)); } String invoiceId = (String) context.get("invoiceId"); GenericValue invoice = null; try { invoice = EntityQuery.use(delegator).from("Invoice").where("invoiceId", invoiceId).queryOne(); } catch (GenericEntityException e) { Debug.logError(e, "Problem getting Invoice for Invoice ID" + invoiceId, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingInvoiceNotFound", UtilMisc.toMap("invoiceId", invoiceId), locale)); } // Ignore invoices that aren't ready yet if (!invoice.getString("statusId").equals("INVOICE_READY")) { return ServiceUtil.returnSuccess(); } // Get the payment applications that can be used to pay the invoice List<GenericValue> paymentAppl = null; try { paymentAppl = EntityQuery.use(delegator).from("PaymentAndApplication").where("invoiceId", invoiceId) .queryList(); // For each payment application, select only those that are RECEIVED or SENT based on whether the payment is a RECEIPT or DISBURSEMENT respectively for (Iterator<GenericValue> iter = paymentAppl.iterator(); iter.hasNext();) { GenericValue payment = iter.next(); if ("PMNT_RECEIVED".equals(payment.get("statusId")) && UtilAccounting.isReceipt(payment)) { continue; // keep } if ("PMNT_SENT".equals(payment.get("statusId")) && UtilAccounting.isDisbursement(payment)) { continue; // keep } // all other cases, remove the payment application iter.remove(); } } catch (GenericEntityException e) { Debug.logError(e, "Problem getting PaymentApplication(s) for Invoice ID " + invoiceId, module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingProblemGettingPaymentApplication", UtilMisc.toMap("invoiceId", invoiceId), locale)); } Map<String, BigDecimal> payments = FastMap.newInstance(); Timestamp paidDate = null; for (GenericValue payAppl : paymentAppl) { payments.put(payAppl.getString("paymentId"), payAppl.getBigDecimal("amountApplied")); // paidDate will be the last date (chronologically) of all the Payments applied to this invoice Timestamp paymentDate = payAppl.getTimestamp("effectiveDate"); if (paymentDate != null) { if ((paidDate == null) || (paidDate.before(paymentDate))) { paidDate = paymentDate; } } } BigDecimal totalPayments = ZERO; for (BigDecimal amount : payments.values()) { if (amount == null) amount = ZERO; totalPayments = totalPayments.add(amount).setScale(DECIMALS, ROUNDING); } if (totalPayments.signum() == 1) { BigDecimal invoiceTotal = InvoiceWorker.getInvoiceTotal(delegator, invoiceId); if (Debug.verboseOn()) { Debug.logVerbose("Invoice #" + invoiceId + " total: " + invoiceTotal, module); Debug.logVerbose("Total payments : " + totalPayments, module); } if (totalPayments.compareTo(invoiceTotal) >= 0) { // this checks that totalPayments is greater than or equal to invoiceTotal // this invoice is paid Map<String, Object> svcCtx = UtilMisc.toMap("statusId", "INVOICE_PAID", "invoiceId", invoiceId, "paidDate", paidDate, "userLogin", userLogin); try { dispatcher.runSync("setInvoiceStatus", svcCtx); } catch (GenericServiceException e) { Debug.logError(e, "Problem changing invoice status to INVOICE_PAID" + svcCtx, module); return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingProblemChangingInvoiceStatusTo", UtilMisc.toMap("newStatus", "INVOICE_PAID"), locale)); } } } else { Debug.logInfo("No payments found for Invoice #" + invoiceId, module); } return ServiceUtil.returnSuccess(); } private static BigDecimal calcHeaderAdj(Delegator delegator, GenericValue adj, String invoiceTypeId, String invoiceId, String invoiceItemSeqId, BigDecimal divisor, BigDecimal multiplier, BigDecimal baseAmount, int decimals, int rounding, GenericValue userLogin, LocalDispatcher dispatcher, Locale locale) { BigDecimal adjAmount = ZERO; if (adj.get("amount") != null) { // pro-rate the amount BigDecimal amount = ZERO; // make sure the divisor is not 0 to avoid NaN problems; just leave the amount as 0 and skip it in essense if (divisor.signum() != 0) { // multiply first then divide to avoid rounding errors amount = baseAmount.multiply(multiplier).divide(divisor, decimals, rounding); } if (amount.signum() != 0) { Map<String, Object> createInvoiceItemContext = FastMap.newInstance(); createInvoiceItemContext.put("invoiceId", invoiceId); createInvoiceItemContext.put("invoiceItemSeqId", invoiceItemSeqId); createInvoiceItemContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, adj.getString("orderAdjustmentTypeId"), null, invoiceTypeId, "INVOICE_ADJ")); createInvoiceItemContext.put("description", adj.get("description")); createInvoiceItemContext.put("quantity", BigDecimal.ONE); createInvoiceItemContext.put("amount", amount); createInvoiceItemContext.put("overrideGlAccountId", adj.get("overrideGlAccountId")); //createInvoiceItemContext.put("productId", orderItem.get("productId")); //createInvoiceItemContext.put("productFeatureId", orderItem.get("productFeatureId")); //createInvoiceItemContext.put("uomId", ""); //createInvoiceItemContext.put("taxableFlag", product.get("taxable")); createInvoiceItemContext.put("taxAuthPartyId", adj.get("taxAuthPartyId")); createInvoiceItemContext.put("taxAuthGeoId", adj.get("taxAuthGeoId")); createInvoiceItemContext.put("taxAuthorityRateSeqId", adj.get("taxAuthorityRateSeqId")); createInvoiceItemContext.put("userLogin", userLogin); Map<String, Object> createInvoiceItemResult = null; try { createInvoiceItemResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemContext); } catch (GenericServiceException e) { Debug.logError(e, "Service/other problem creating InvoiceItem from order header adjustment", module); return adjAmount; } if (ServiceUtil.isError(createInvoiceItemResult)) { return adjAmount; } // Create the OrderAdjustmentBilling record Map<String, Object> createOrderAdjustmentBillingContext = FastMap.newInstance(); createOrderAdjustmentBillingContext.put("orderAdjustmentId", adj.getString("orderAdjustmentId")); createOrderAdjustmentBillingContext.put("invoiceId", invoiceId); createOrderAdjustmentBillingContext.put("invoiceItemSeqId", invoiceItemSeqId); createOrderAdjustmentBillingContext.put("amount", amount); createOrderAdjustmentBillingContext.put("userLogin", userLogin); try { dispatcher.runSync("createOrderAdjustmentBilling", createOrderAdjustmentBillingContext); } catch (GenericServiceException e) { return adjAmount; } } amount = amount.setScale(decimals, rounding); adjAmount = amount; } else if (adj.get("sourcePercentage") != null) { // pro-rate the amount BigDecimal percent = adj.getBigDecimal("sourcePercentage"); percent = percent.divide(new BigDecimal(100), 100, rounding); BigDecimal amount = ZERO; // make sure the divisor is not 0 to avoid NaN problems; just leave the amount as 0 and skip it in essense if (divisor.signum() != 0) { // multiply first then divide to avoid rounding errors amount = percent.multiply(divisor); } if (amount.signum() != 0) { Map<String, Object> createInvoiceItemContext = FastMap.newInstance(); createInvoiceItemContext.put("invoiceId", invoiceId); createInvoiceItemContext.put("invoiceItemSeqId", invoiceItemSeqId); createInvoiceItemContext.put("invoiceItemTypeId", getInvoiceItemType(delegator, adj.getString("orderAdjustmentTypeId"), null, invoiceTypeId, "INVOICE_ADJ")); createInvoiceItemContext.put("description", adj.get("description")); createInvoiceItemContext.put("quantity", BigDecimal.ONE); createInvoiceItemContext.put("amount", amount); createInvoiceItemContext.put("overrideGlAccountId", adj.get("overrideGlAccountId")); //createInvoiceItemContext.put("productId", orderItem.get("productId")); //createInvoiceItemContext.put("productFeatureId", orderItem.get("productFeatureId")); //createInvoiceItemContext.put("uomId", ""); //createInvoiceItemContext.put("taxableFlag", product.get("taxable")); createInvoiceItemContext.put("taxAuthPartyId", adj.get("taxAuthPartyId")); createInvoiceItemContext.put("taxAuthGeoId", adj.get("taxAuthGeoId")); createInvoiceItemContext.put("taxAuthorityRateSeqId", adj.get("taxAuthorityRateSeqId")); createInvoiceItemContext.put("userLogin", userLogin); Map<String, Object> createInvoiceItemResult = null; try { createInvoiceItemResult = dispatcher.runSync("createInvoiceItem", createInvoiceItemContext); } catch (GenericServiceException e) { Debug.logError(e, "Service/other problem creating InvoiceItem from order header adjustment", module); return adjAmount; } if (ServiceUtil.isError(createInvoiceItemResult)) { return adjAmount; } // Create the OrderAdjustmentBilling record Map<String, Object> createOrderAdjustmentBillingContext = FastMap.newInstance(); createOrderAdjustmentBillingContext.put("orderAdjustmentId", adj.getString("orderAdjustmentId")); createOrderAdjustmentBillingContext.put("invoiceId", invoiceId); createOrderAdjustmentBillingContext.put("invoiceItemSeqId", invoiceItemSeqId); createOrderAdjustmentBillingContext.put("amount", amount); createOrderAdjustmentBillingContext.put("userLogin", userLogin); try { dispatcher.runSync("createOrderAdjustmentBilling", createOrderAdjustmentBillingContext); } catch (GenericServiceException e) { return adjAmount; } } amount = amount.setScale(decimals, rounding); adjAmount = amount; } Debug.logInfo("adjAmount: " + adjAmount + ", divisor: " + divisor + ", multiplier: " + multiplier + ", invoiceTypeId: " + invoiceTypeId + ", invoiceId: " + invoiceId + ", itemSeqId: " + invoiceItemSeqId + ", decimals: " + decimals + ", rounding: " + rounding + ", adj: " + adj, module); return adjAmount; } /* Creates InvoiceTerm entries for a list of terms, which can be BillingAccountTerms, OrderTerms, etc. */ private static void createInvoiceTerms(Delegator delegator, LocalDispatcher dispatcher, String invoiceId, List<GenericValue> terms, GenericValue userLogin, Locale locale) { if (terms != null) { for (GenericValue term : terms) { Map<String, Object> createInvoiceTermContext = FastMap.newInstance(); createInvoiceTermContext.put("invoiceId", invoiceId); createInvoiceTermContext.put("invoiceItemSeqId", "_NA_"); createInvoiceTermContext.put("termTypeId", term.get("termTypeId")); createInvoiceTermContext.put("termValue", term.get("termValue")); createInvoiceTermContext.put("termDays", term.get("termDays")); if (!"BillingAccountTerm".equals(term.getEntityName())) { createInvoiceTermContext.put("textValue", term.get("textValue")); createInvoiceTermContext.put("description", term.get("description")); } createInvoiceTermContext.put("uomId", term.get("uomId")); createInvoiceTermContext.put("userLogin", userLogin); Map<String, Object> createInvoiceTermResult = null; try { createInvoiceTermResult = dispatcher.runSync("createInvoiceTerm", createInvoiceTermContext); } catch (GenericServiceException e) { Debug.logError(e, "Service/other problem creating InvoiceItem from order header adjustment", module); } if (ServiceUtil.isError(createInvoiceTermResult)) { Debug.logError("Service/other problem creating InvoiceItem from order header adjustment", module); } } } } /** * Service to add payment application records to indicate which invoices * have been paid/received. For invoice processing, this service works on * the invoice level when 'invoiceProcessing' parameter is set to "Y" else * it works on the invoice item level. */ public static Map<String, Object> updatePaymentApplication(DispatchContext dctx, Map<String, Object> context) { if (!context.containsKey("useHighestAmount")) { context.put("useHighestAmount", "N"); } BigDecimal amountApplied = (BigDecimal) context.get("amountApplied"); if (amountApplied != null) { context.put("amountApplied", amountApplied); } else { context.put("amountApplied", ZERO); } return updatePaymentApplicationDefBd(dctx, context); } /** * Service to add payment application records to indicate which invoices * have been paid/received. For invoice processing, this service works on * the invoice level when 'invoiceProcessing' parameter is set to "Y" else * it works on the invoice item level. * * This version will apply as much as possible when no amountApplied is provided. */ public static Map<String, Object> updatePaymentApplicationDef(DispatchContext dctx, Map<String, Object> context) { if (!context.containsKey("useHighestAmount")) { context.put("useHighestAmount", "Y"); } return updatePaymentApplication(dctx, context); } public static Map<String, Object> updatePaymentApplicationDefBd(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); Locale locale = (Locale) context.get("locale"); if (DECIMALS == -1 || ROUNDING == -1) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingAritmeticPropertiesNotConfigured", locale)); } if (!context.containsKey("useHighestAmount")) { context.put("useHighestAmount", "Y"); } String defaultInvoiceProcessing = EntityUtilProperties.getPropertyValue("AccountingConfig", "invoiceProcessing", delegator); boolean debug = true; // show processing messages in the log..or not.... // a 'y' in invoiceProssesing will reverse the default processing String changeProcessing = (String) context.get("invoiceProcessing"); String invoiceId = (String) context.get("invoiceId"); String invoiceItemSeqId = (String) context.get("invoiceItemSeqId"); String paymentId = (String) context.get("paymentId"); String toPaymentId = (String) context.get("toPaymentId"); String paymentApplicationId = (String) context.get("paymentApplicationId"); BigDecimal amountApplied = (BigDecimal) context.get("amountApplied"); String billingAccountId = (String) context.get("billingAccountId"); String taxAuthGeoId = (String) context.get("taxAuthGeoId"); String useHighestAmount = (String) context.get("useHighestAmount"); List<String> errorMessageList = FastList.newInstance(); if (debug) Debug.logInfo("updatePaymentApplicationDefBd input parameters..." + " defaultInvoiceProcessing: " + defaultInvoiceProcessing + " changeDefaultInvoiceProcessing: " + changeProcessing + " useHighestAmount: " + useHighestAmount + " paymentApplicationId: " + paymentApplicationId + " PaymentId: " + paymentId + " InvoiceId: " + invoiceId + " InvoiceItemSeqId: " + invoiceItemSeqId + " BillingAccountId: " + billingAccountId + " toPaymentId: " + toPaymentId + " amountApplied: " + amountApplied + " TaxAuthGeoId: " + taxAuthGeoId, module); if (changeProcessing == null) { changeProcessing = "N"; // not provided, so no change } boolean invoiceProcessing = true; if (defaultInvoiceProcessing.equals("YY")) { invoiceProcessing = true; } else if (defaultInvoiceProcessing.equals("NN")) { invoiceProcessing = false; } else if (defaultInvoiceProcessing.equals("Y")) { invoiceProcessing = !"Y".equals(changeProcessing); } else if (defaultInvoiceProcessing.equals("N")) { invoiceProcessing = "Y".equals(changeProcessing); } // on a new paymentApplication check if only billing or invoice or tax // id is provided not 2,3... BUT a combination of billingAccountId and invoiceId is permitted - that's how you use a // Billing Account to pay for an Invoice if (paymentApplicationId == null) { int count = 0; if (invoiceId != null) count++; if (toPaymentId != null) count++; if (billingAccountId != null) count++; if (taxAuthGeoId != null) count++; if ((billingAccountId != null) && (invoiceId != null)) count--; if (count != 1) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingSpecifyInvoiceToPaymentBillingAccountTaxGeoId", locale)); } } // avoid null pointer exceptions. if (amountApplied == null) amountApplied = ZERO; // makes no sense to have an item numer without an invoice number if (invoiceId == null) invoiceItemSeqId = null; // retrieve all information and perform checking on the retrieved info..... // Payment..... BigDecimal paymentApplyAvailable = ZERO; // amount available on the payment reduced by the already applied amounts BigDecimal amountAppliedMax = ZERO; // the maximum that can be applied taking payment,invoice,invoiceitem,billing account in concideration // if maxApplied is missing, this value can be used, // Payment this should be checked after the invoice checking because it is possible the currency is changed GenericValue payment = null; String currencyUomId = null; if (paymentId == null || paymentId.equals("")) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingPaymentIdBlankNotSupplied", locale)); } else { try { payment = EntityQuery.use(delegator).from("Payment").where("paymentId", paymentId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (payment == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentRecordNotFound", UtilMisc.toMap("paymentId", paymentId), locale)); } paymentApplyAvailable = payment.getBigDecimal("amount") .subtract(PaymentWorker.getPaymentApplied(payment)).setScale(DECIMALS, ROUNDING); if (payment.getString("statusId").equals("PMNT_CANCELLED")) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentCancelled", UtilMisc.toMap("paymentId", paymentId), locale)); } if (payment.getString("statusId").equals("PMNT_CONFIRMED")) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentConfirmed", UtilMisc.toMap("paymentId", paymentId), locale)); } currencyUomId = payment.getString("currencyUomId"); // if the amount to apply is 0 give it amount the payment still need // to apply if (amountApplied.signum() == 0) { amountAppliedMax = paymentApplyAvailable; } } // the "TO" Payment..... BigDecimal toPaymentApplyAvailable = ZERO; GenericValue toPayment = null; if (toPaymentId != null && !toPaymentId.equals("")) { try { toPayment = EntityQuery.use(delegator).from("Payment").where("paymentId", toPaymentId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (toPayment == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentRecordNotFound", UtilMisc.toMap("paymentId", toPaymentId), locale)); } toPaymentApplyAvailable = toPayment.getBigDecimal("amount") .subtract(PaymentWorker.getPaymentApplied(toPayment)).setScale(DECIMALS, ROUNDING); if (toPayment.getString("statusId").equals("PMNT_CANCELLED")) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentCancelled", UtilMisc.toMap("paymentId", paymentId), locale)); } if (toPayment.getString("statusId").equals("PMNT_CONFIRMED")) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentConfirmed", UtilMisc.toMap("paymentId", paymentId), locale)); } // if the amount to apply is less then required by the payment reduce it if (amountAppliedMax.compareTo(toPaymentApplyAvailable) > 0) { amountAppliedMax = toPaymentApplyAvailable; } if (paymentApplicationId == null) { // only check for new application records, update on existing records is checked in the paymentApplication section if (toPaymentApplyAvailable.signum() == 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentAlreadyApplied", UtilMisc.toMap("paymentId", toPaymentId), locale)); } else { // check here for too much application if a new record is // added (paymentApplicationId == null) if (amountApplied.compareTo(toPaymentApplyAvailable) > 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentLessRequested", UtilMisc.<String, Object>toMap("paymentId", toPaymentId, "paymentApplyAvailable", toPaymentApplyAvailable, "amountApplied", amountApplied, "isoCode", currencyUomId), locale)); } } } // check if at least one send is the same as one receiver on the other payment if (!payment.getString("partyIdFrom").equals(toPayment.getString("partyIdTo")) && !payment.getString("partyIdTo").equals(toPayment.getString("partyIdFrom"))) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingFromPartySameToParty", locale)); } if (debug) Debug.logInfo("toPayment info retrieved and checked...", module); } // assign payment to billing account if the invoice is assigned to this billing account if (invoiceId != null) { GenericValue invoice = null; try { invoice = EntityQuery.use(delegator).from("Invoice").where("invoiceId", invoiceId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (invoice == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceNotFound", UtilMisc.toMap("invoiceId", invoiceId), locale)); } else { if (invoice.getString("billingAccountId") != null) { billingAccountId = invoice.getString("billingAccountId"); } } } // billing account GenericValue billingAccount = null; if (billingAccountId != null && !billingAccountId.equals("")) { try { billingAccount = EntityQuery.use(delegator).from("BillingAccount") .where("billingAccountId", billingAccountId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (billingAccount == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingBillingAccountNotFound", UtilMisc.toMap("billingAccountId", billingAccountId), locale)); } // check the currency if (billingAccount.get("accountCurrencyUomId") != null && currencyUomId != null && !billingAccount.getString("accountCurrencyUomId").equals(currencyUomId)) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingBillingAccountCurrencyProblem", UtilMisc.toMap("billingAccountId", billingAccountId, "accountCurrencyUomId", billingAccount.getString("accountCurrencyUomId"), "paymentId", paymentId, "paymentCurrencyUomId", currencyUomId), locale)); } if (debug) Debug.logInfo("Billing Account info retrieved and checked...", module); } // get the invoice (item) information BigDecimal invoiceApplyAvailable = ZERO; // amount available on the invoice reduced by the already applied amounts BigDecimal invoiceItemApplyAvailable = ZERO; // amount available on the invoiceItem reduced by the already applied amounts GenericValue invoice = null; GenericValue invoiceItem = null; if (invoiceId != null) { try { invoice = EntityQuery.use(delegator).from("Invoice").where("invoiceId", invoiceId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (invoice == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceNotFound", UtilMisc.toMap("invoiceId", invoiceId), locale)); } else { // check the invoice and when supplied the invoice item... if (invoice.getString("statusId").equals("INVOICE_CANCELLED")) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingInvoiceCancelledCannotApplyTo", UtilMisc.toMap("invoiceId", invoiceId), locale)); } // check the currency if (currencyUomId != null && invoice.get("currencyUomId") != null && !currencyUomId.equals(invoice.getString("currencyUomId"))) { Debug.logInfo( UtilProperties.getMessage(resource, "AccountingInvoicePaymentCurrencyProblem", UtilMisc.toMap("invoiceCurrency", invoice.getString("currencyUomId"), "paymentCurrency", payment.getString("currencyUomId")), locale), module); Debug.logInfo("will try to apply payment on the actualCurrency amount on payment", module); if (payment.get("actualCurrencyAmount") == null || payment.get("actualCurrencyUomId") == null) { errorMessageList.add( "Actual amounts are required in the currency of the invoice to make this work...."); } else { currencyUomId = payment.getString("actualCurrencyUomId"); if (!currencyUomId.equals(invoice.getString("currencyUomId"))) { errorMessageList.add("actual currency on payment (" + currencyUomId + ") not the same as original invoice currency (" + invoice.getString("currencyUomId") + ")"); } } paymentApplyAvailable = payment.getBigDecimal("actualCurrencyAmount") .subtract(PaymentWorker.getPaymentApplied(payment)).setScale(DECIMALS, ROUNDING); if (amountApplied.signum() == 0) { amountAppliedMax = paymentApplyAvailable; } } // check if the invoice already covered by payments BigDecimal invoiceTotal = InvoiceWorker.getInvoiceTotal(invoice); invoiceApplyAvailable = InvoiceWorker.getInvoiceNotApplied(invoice); // adjust the amountAppliedMax value if required.... if (invoiceApplyAvailable.compareTo(amountAppliedMax) < 0) { amountAppliedMax = invoiceApplyAvailable; } if (invoiceTotal.signum() == 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceTotalZero", UtilMisc.toMap("invoiceId", invoiceId), locale)); } else if (paymentApplicationId == null) { // only check for new records here...updates are checked in the paymentApplication section if (invoiceApplyAvailable.signum() == 0) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingInvoiceCompletelyApplied", UtilMisc.toMap("invoiceId", invoiceId), locale)); } // check here for too much application if a new record(s) are // added (paymentApplicationId == null) else if (amountApplied.compareTo(invoiceApplyAvailable) > 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceLessRequested", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "invoiceApplyAvailable", invoiceApplyAvailable, "amountApplied", amountApplied, "isoCode", invoice.getString("currencyUomId")), locale)); } } // check if at least one sender is the same as one receiver on the invoice if (!payment.getString("partyIdFrom").equals(invoice.getString("partyId")) && !payment.getString("partyIdTo").equals(invoice.getString("partyIdFrom"))) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingFromPartySameToParty", locale)); } if (debug) Debug.logInfo("Invoice info retrieved and checked ...", module); } // if provided check the invoice item. if (invoiceItemSeqId != null) { // when itemSeqNr not provided delay checking on invoiceItemSeqId try { invoiceItem = EntityQuery.use(delegator).from("InvoiceItem") .where("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (invoiceItem == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceItemNotFound", UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId), locale)); } else { if (invoice.get("currencyUomId") != null && currencyUomId != null && !invoice.getString("currencyUomId").equals(currencyUomId)) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoicePaymentCurrencyProblem", UtilMisc.toMap("paymentCurrencyId", currencyUomId, "itemCurrency", invoice.getString("currencyUomId")), locale)); } // get the invoice item applied value BigDecimal quantity = null; if (invoiceItem.get("quantity") == null) { quantity = BigDecimal.ONE; } else { quantity = invoiceItem.getBigDecimal("quantity").setScale(DECIMALS, ROUNDING); } invoiceItemApplyAvailable = invoiceItem.getBigDecimal("amount").multiply(quantity) .setScale(DECIMALS, ROUNDING) .subtract(InvoiceWorker.getInvoiceItemApplied(invoiceItem)); // check here for too much application if a new record is added // (paymentApplicationId == null) if (paymentApplicationId == null && amountApplied.compareTo(invoiceItemApplyAvailable) > 0) { // new record errorMessageList.add("Invoice(" + invoiceId + ") item(" + invoiceItemSeqId + ") has " + invoiceItemApplyAvailable + " to apply but " + amountApplied + " is requested\n"); String uomId = invoice.getString("currencyUomId"); errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceItemLessRequested", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId, "invoiceItemApplyAvailable", invoiceItemApplyAvailable, "amountApplied", amountApplied, "isoCode", uomId), locale)); } } if (debug) Debug.logInfo( "InvoiceItem info retrieved and checked against the Invoice (currency and amounts) ...", module); } } // check this at the end because the invoice can change the currency....... if (paymentApplicationId == null) { // only check for new application records, update on existing records is checked in the paymentApplication section if (paymentApplyAvailable.signum() == 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentAlreadyApplied", UtilMisc.toMap("paymentId", paymentId), locale)); } else { // check here for too much application if a new record is // added (paymentApplicationId == null) if (amountApplied.compareTo(paymentApplyAvailable) > 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentLessRequested", UtilMisc.<String, Object>toMap("paymentId", paymentId, "paymentApplyAvailable", paymentApplyAvailable, "amountApplied", amountApplied, "isoCode", currencyUomId), locale)); } } } // get the application record if the applicationId is supplied if not // create empty record. BigDecimal newInvoiceApplyAvailable = invoiceApplyAvailable; // amount available on the invoice taking into account if the invoiceItemnumber has changed BigDecimal newInvoiceItemApplyAvailable = invoiceItemApplyAvailable; // amount available on the invoiceItem taking into account if the itemnumber has changed BigDecimal newToPaymentApplyAvailable = toPaymentApplyAvailable; BigDecimal newPaymentApplyAvailable = paymentApplyAvailable; GenericValue paymentApplication = null; if (paymentApplicationId == null) { paymentApplication = delegator.makeValue("PaymentApplication"); // prepare for creation } else { // retrieve existing paymentApplication try { paymentApplication = EntityQuery.use(delegator).from("PaymentApplication") .where("paymentApplicationId", paymentApplicationId).queryOne(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (paymentApplication == null) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentApplicationNotFound", UtilMisc.toMap("paymentApplicationId", paymentApplicationId), locale)); paymentApplicationId = null; } else { // if both invoiceId and BillingId is entered there was // obviously a change // only take the newly entered item, same for tax authority and toPayment if (paymentApplication.get("invoiceId") == null && invoiceId != null) { billingAccountId = null; taxAuthGeoId = null; toPaymentId = null; } else if (paymentApplication.get("toPaymentId") == null && toPaymentId != null) { invoiceId = null; invoiceItemSeqId = null; taxAuthGeoId = null; billingAccountId = null; } else if (paymentApplication.get("billingAccountId") == null && billingAccountId != null) { invoiceId = null; invoiceItemSeqId = null; toPaymentId = null; taxAuthGeoId = null; } else if (paymentApplication.get("taxAuthGeoId") == null && taxAuthGeoId != null) { invoiceId = null; invoiceItemSeqId = null; toPaymentId = null; billingAccountId = null; } // check if the payment for too much application if an existing // application record is changed if (paymentApplyAvailable.compareTo(ZERO) == 0) { newPaymentApplyAvailable = paymentApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied) .setScale(DECIMALS, ROUNDING); } else { newPaymentApplyAvailable = paymentApplyAvailable.add(paymentApplyAvailable) .subtract(amountApplied).setScale(DECIMALS, ROUNDING); } if (newPaymentApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentNotEnough", UtilMisc.<String, Object>toMap("paymentId", paymentId, "paymentApplyAvailable", paymentApplyAvailable.add(paymentApplication.getBigDecimal("amountApplied")), "amountApplied", amountApplied), locale)); } if (invoiceId != null) { // only when we are processing an invoice on existing paymentApplication check invoice item for to much application if the invoice // number did not change if (invoiceId.equals(paymentApplication.getString("invoiceId"))) { // check if both the itemNumbers are null then this is a // record for the whole invoice if (invoiceItemSeqId == null && paymentApplication.get("invoiceItemSeqId") == null) { newInvoiceApplyAvailable = invoiceApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied) .setScale(DECIMALS, ROUNDING); if (invoiceApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", newInvoiceApplyAvailable.negate(), "invoiceId", invoiceId), locale)); } } else if (invoiceItemSeqId == null && paymentApplication.get("invoiceItemSeqId") != null) { // check if the item number changed from a real Item number to a null value newInvoiceApplyAvailable = invoiceApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied) .setScale(DECIMALS, ROUNDING); if (invoiceApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", newInvoiceApplyAvailable.negate(), "invoiceId", invoiceId), locale)); } } else if (invoiceItemSeqId != null && paymentApplication.get("invoiceItemSeqId") == null) { // check if the item number changed from a null value to // a real Item number newInvoiceItemApplyAvailable = invoiceItemApplyAvailable.subtract(amountApplied) .setScale(DECIMALS, ROUNDING); if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingItemInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", newInvoiceItemApplyAvailable.negate(), "invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId), locale)); } } else if (invoiceItemSeqId.equals(paymentApplication.getString("invoiceItemSeqId"))) { // check if the real item numbers the same // item number the same numeric value newInvoiceItemApplyAvailable = invoiceItemApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied")).subtract(amountApplied) .setScale(DECIMALS, ROUNDING); if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingItemInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", newInvoiceItemApplyAvailable.negate(), "invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId), locale)); } } else { // item number changed only check new item newInvoiceItemApplyAvailable = invoiceItemApplyAvailable.add(amountApplied) .setScale(DECIMALS, ROUNDING); if (newInvoiceItemApplyAvailable.compareTo(ZERO) < 0) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingItemInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", newInvoiceItemApplyAvailable.negate(), "invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId), locale)); } } // if the amountApplied = 0 give it the higest possible // value if (amountApplied.signum() == 0) { if (newInvoiceItemApplyAvailable.compareTo(newPaymentApplyAvailable) < 0) { amountApplied = newInvoiceItemApplyAvailable; // from the item number } else { amountApplied = newPaymentApplyAvailable; // from the payment } } // check the invoice newInvoiceApplyAvailable = invoiceApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied").subtract(amountApplied)) .setScale(DECIMALS, ROUNDING); if (newInvoiceApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingInvoiceNotEnough", UtilMisc.<String, Object>toMap("tooMuch", invoiceApplyAvailable .add(paymentApplication.getBigDecimal("amountApplied")) .subtract(amountApplied), "invoiceId", invoiceId), locale)); } } } // check the toPayment account when only the amountApplied has // changed, if (toPaymentId != null && toPaymentId.equals(paymentApplication.getString("toPaymentId"))) { newToPaymentApplyAvailable = toPaymentApplyAvailable .subtract(paymentApplication.getBigDecimal("amountApplied")).add(amountApplied) .setScale(DECIMALS, ROUNDING); if (newToPaymentApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentNotEnough", UtilMisc.<String, Object>toMap("paymentId", toPaymentId, "paymentApplyAvailable", newToPaymentApplyAvailable, "amountApplied", amountApplied), locale)); } } else if (toPaymentId != null) { // billing account entered number has changed so we have to // check the new billing account number. newToPaymentApplyAvailable = toPaymentApplyAvailable.add(amountApplied).setScale(DECIMALS, ROUNDING); if (newToPaymentApplyAvailable.compareTo(ZERO) < 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingPaymentNotEnough", UtilMisc.<String, Object>toMap("paymentId", toPaymentId, "paymentApplyAvailable", newToPaymentApplyAvailable, "amountApplied", amountApplied), locale)); } } } if (debug) Debug.logInfo("paymentApplication record info retrieved and checked...", module); } // show the maximumus what can be added in the payment application file. String toMessage = null; // prepare for success message if (debug) { String extra = ""; if (invoiceItemSeqId != null) { extra = " Invoice item(" + invoiceItemSeqId + ") amount not yet applied: " + newInvoiceItemApplyAvailable; } Debug.logInfo("checking finished, start processing with the following data... ", module); if (invoiceId != null) { Debug.logInfo(" Invoice(" + invoiceId + ") amount not yet applied: " + newInvoiceApplyAvailable + extra + " Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module); toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToInvoice", UtilMisc.toMap("invoiceId", invoiceId), locale); if (extra.length() > 0) toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToInvoiceItem", UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId), locale); } if (toPaymentId != null) { Debug.logInfo(" toPayment(" + toPaymentId + ") amount not yet applied: " + newToPaymentApplyAvailable + " Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module); toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToPayment", UtilMisc.toMap("paymentId", toPaymentId), locale); } if (taxAuthGeoId != null) { Debug.logInfo( " taxAuthGeoId(" + taxAuthGeoId + ") Payment(" + paymentId + ") amount not yet applied: " + newPaymentApplyAvailable + " Requested amount to apply:" + amountApplied, module); toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToTax", UtilMisc.toMap("taxAuthGeoId", taxAuthGeoId), locale); } } // if the amount to apply was not provided or was zero fill it with the maximum possible and provide information to the user if (amountApplied.signum() == 0 && useHighestAmount.equals("Y")) { amountApplied = newPaymentApplyAvailable; if (invoiceId != null && newInvoiceApplyAvailable.compareTo(amountApplied) < 0) { amountApplied = newInvoiceApplyAvailable; toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToInvoice", UtilMisc.toMap("invoiceId", invoiceId), locale); } if (toPaymentId != null && newToPaymentApplyAvailable.compareTo(amountApplied) < 0) { amountApplied = newToPaymentApplyAvailable; toMessage = UtilProperties.getMessage(resource, "AccountingApplicationToPayment", UtilMisc.toMap("paymentId", toPaymentId), locale); } } String successMessage = null; if (amountApplied.signum() == 0) { errorMessageList.add(UtilProperties.getMessage(resource, "AccountingNoAmount", locale)); } else { successMessage = UtilProperties.getMessage(resource, "AccountingApplicationSuccess", UtilMisc.<String, Object>toMap("amountApplied", amountApplied, "paymentId", paymentId, "isoCode", currencyUomId, "toMessage", toMessage), locale); } // report error messages if any if (errorMessageList.size() > 0) { return ServiceUtil.returnError(errorMessageList); } // ============ start processing ====================== // if the application is specified it is easy, update the existing record only if (paymentApplicationId != null) { // record is already retrieved previously if (debug) Debug.logInfo("Process an existing paymentApplication record: " + paymentApplicationId, module); // update the current record paymentApplication.set("invoiceId", invoiceId); paymentApplication.set("invoiceItemSeqId", invoiceItemSeqId); paymentApplication.set("paymentId", paymentId); paymentApplication.set("toPaymentId", toPaymentId); paymentApplication.set("amountApplied", amountApplied); paymentApplication.set("billingAccountId", billingAccountId); paymentApplication.set("taxAuthGeoId", taxAuthGeoId); return storePaymentApplication(delegator, paymentApplication, locale); } // if no invoice sequence number is provided it assumed the requested paymentAmount will be // spread over the invoice starting with the lowest sequence number if // itemprocessing is on otherwise create one record if (invoiceId != null && paymentId != null && (invoiceItemSeqId == null)) { if (invoiceProcessing) { // create only a single record with a null seqId if (debug) Debug.logInfo("Try to allocate the payment to the invoice as a whole", module); paymentApplication.set("paymentId", paymentId); paymentApplication.set("toPaymentId", null); paymentApplication.set("invoiceId", invoiceId); paymentApplication.set("invoiceItemSeqId", null); paymentApplication.set("toPaymentId", null); paymentApplication.set("amountApplied", amountApplied); paymentApplication.set("billingAccountId", billingAccountId); paymentApplication.set("taxAuthGeoId", null); if (debug) Debug.logInfo("creating new paymentapplication", module); return storePaymentApplication(delegator, paymentApplication, locale); } else { // spread the amount over every single item number if (debug) Debug.logInfo("Try to allocate the payment to the itemnumbers of the invoice", module); // get the invoice items List<GenericValue> invoiceItems = null; try { invoiceItems = EntityQuery.use(delegator).from("InvoiceItem").where("invoiceId", invoiceId) .queryList(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (invoiceItems.size() == 0) { errorMessageList .add(UtilProperties.getMessage(resource, "AccountingNoInvoiceItemsFoundForInvoice", UtilMisc.toMap("invoiceId", invoiceId), locale)); return ServiceUtil.returnError(errorMessageList); } else { // we found some invoice items, start processing.... // check if the user want to apply a smaller amount than the maximum possible on the payment if (amountApplied.signum() != 0 && amountApplied.compareTo(paymentApplyAvailable) < 0) { paymentApplyAvailable = amountApplied; } for (GenericValue currentInvoiceItem : invoiceItems) { if (paymentApplyAvailable.compareTo(ZERO) > 0) { break; } if (debug) Debug.logInfo( "Start processing item: " + currentInvoiceItem.getString("invoiceItemSeqId"), module); BigDecimal itemQuantity = BigDecimal.ONE; if (currentInvoiceItem.get("quantity") != null && currentInvoiceItem.getBigDecimal("quantity").signum() != 0) { itemQuantity = new BigDecimal(currentInvoiceItem.getString("quantity")) .setScale(DECIMALS, ROUNDING); } BigDecimal itemAmount = currentInvoiceItem.getBigDecimal("amount").setScale(DECIMALS, ROUNDING); BigDecimal itemTotal = itemAmount.multiply(itemQuantity).setScale(DECIMALS, ROUNDING); // get the application(s) already allocated to this // item, if available List<GenericValue> paymentApplications = null; try { paymentApplications = currentInvoiceItem.getRelated("PaymentApplication", null, null, false); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } BigDecimal tobeApplied = ZERO; // item total amount - already applied (if any) BigDecimal alreadyApplied = ZERO; if (UtilValidate.isNotEmpty(paymentApplications)) { // application(s) found, add them all together Iterator<GenericValue> p = paymentApplications.iterator(); while (p.hasNext()) { paymentApplication = p.next(); alreadyApplied = alreadyApplied.add(paymentApplication .getBigDecimal("amountApplied").setScale(DECIMALS, ROUNDING)); } tobeApplied = itemTotal.subtract(alreadyApplied).setScale(DECIMALS, ROUNDING); } else { // no application connected yet tobeApplied = itemTotal; } if (debug) Debug.logInfo("tobeApplied:(" + tobeApplied + ") = " + "itemTotal(" + itemTotal + ") - alreadyApplied(" + alreadyApplied + ") but not more then (nonapplied) paymentAmount(" + paymentApplyAvailable + ")", module); if (tobeApplied.signum() == 0) { // invoiceItem already fully applied so look at the next one.... continue; } if (paymentApplyAvailable.compareTo(tobeApplied) > 0) { paymentApplyAvailable = paymentApplyAvailable.subtract(tobeApplied); } else { tobeApplied = paymentApplyAvailable; paymentApplyAvailable = ZERO; } // create application payment record but check currency // first if supplied if (invoice.get("currencyUomId") != null && currencyUomId != null && !invoice.getString("currencyUomId").equals(currencyUomId)) { errorMessageList.add("Payment currency (" + currencyUomId + ") and invoice currency(" + invoice.getString("currencyUomId") + ") not the same\n"); } else { paymentApplication.set("paymentApplicationId", null); // make sure we get a new record paymentApplication.set("invoiceId", invoiceId); paymentApplication.set("invoiceItemSeqId", currentInvoiceItem.getString("invoiceItemSeqId")); paymentApplication.set("paymentId", paymentId); paymentApplication.set("toPaymentId", toPaymentId); paymentApplication.set("amountApplied", tobeApplied); paymentApplication.set("billingAccountId", billingAccountId); paymentApplication.set("taxAuthGeoId", taxAuthGeoId); storePaymentApplication(delegator, paymentApplication, locale); } // check if either the invoice or the payment is fully // applied, when yes change the status to paid // which triggers the ledger routines.... /* * if * (InvoiceWorker.getInvoiceTotal(invoice).equals(InvoiceWorker.getInvoiceApplied(invoice))) { * try { dispatcher.runSync("setInvoiceStatus", * UtilMisc.toMap("invoiceId",invoiceId,"statusId","INVOICE_PAID")); } * catch (GenericServiceException e1) { * Debug.logError(e1, "Error updating invoice status", * module); } } * * if * (payment.getBigDecimal("amount").equals(PaymentWorker.getPaymentApplied(payment))) { * GenericValue appliedPayment = (GenericValue) * delegator.makeValue("Payment", * UtilMisc.toMap("paymentId",paymentId,"statusId","INVOICE_PAID")); * try { appliedPayment.store(); } catch * (GenericEntityException e) { * ServiceUtil.returnError(e.getMessage()); } } */ } if (errorMessageList.size() > 0) { return ServiceUtil.returnError(errorMessageList); } else { if (successMessage != null) { return ServiceUtil.returnSuccess(successMessage); } else { return ServiceUtil.returnSuccess(); } } } } } // if no paymentApplicationId supplied create a new record with the data // supplied... if (paymentApplicationId == null && amountApplied != null) { paymentApplication.set("paymentApplicationId", paymentApplicationId); paymentApplication.set("invoiceId", invoiceId); paymentApplication.set("invoiceItemSeqId", invoiceItemSeqId); paymentApplication.set("paymentId", paymentId); paymentApplication.set("toPaymentId", toPaymentId); paymentApplication.set("amountApplied", amountApplied); paymentApplication.set("billingAccountId", billingAccountId); paymentApplication.set("taxAuthGeoId", taxAuthGeoId); return storePaymentApplication(delegator, paymentApplication, locale); } // should never come here... errorMessageList.add( UtilProperties.getMessage(resource, "AccountingPaymentApplicationParameterUnsuitable", locale)); errorMessageList .add(UtilProperties.getMessage(resource, "AccountingPaymentApplicationParameterListUnsuitable", UtilMisc.toMap("invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId, "paymentId", paymentId, "toPaymentId", toPaymentId, "paymentApplicationId", paymentApplicationId, "amountApplied", amountApplied), locale)); return ServiceUtil.returnError(errorMessageList); } public static Map<String, Object> calculateInvoicedAdjustmentTotal(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); Locale locale = (Locale) context.get("locale"); GenericValue orderAdjustment = (GenericValue) context.get("orderAdjustment"); Map<String, Object> result = ServiceUtil.returnSuccess(); BigDecimal invoicedTotal = ZERO; List<GenericValue> invoicedAdjustments = null; try { invoicedAdjustments = EntityQuery.use(delegator).from("OrderAdjustmentBilling") .where("orderAdjustmentId", orderAdjustment.get("orderAdjustmentId")).queryList(); } catch (GenericEntityException e) { Debug.logError(e, "Accounting trouble calling calculateInvoicedAdjustmentTotal service", module); return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService" + ": " + e.getMessage(), locale)); } for (GenericValue invoicedAdjustment : invoicedAdjustments) { invoicedTotal = invoicedTotal .add(invoicedAdjustment.getBigDecimal("amount").setScale(DECIMALS, ROUNDING)); } result.put("invoicedTotal", invoicedTotal); return result; } /** * Update/add to the paymentApplication table and making sure no duplicate * record exist * * @param delegator * @param paymentApplication * @return map results */ private static Map<String, Object> storePaymentApplication(Delegator delegator, GenericValue paymentApplication, Locale locale) { Map<String, Object> results = ServiceUtil .returnSuccess(UtilProperties.getMessage(resource, "AccountingSuccessful", locale)); boolean debug = true; if (debug) Debug.logInfo("Start updating the paymentApplication table ", module); if (DECIMALS == -1 || ROUNDING == -1) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingAritmeticPropertiesNotConfigured", locale)); } // check if a record already exists with this data List<GenericValue> checkAppls = null; try { checkAppls = EntityQuery.use(delegator).from("PaymentApplication") .where("invoiceId", paymentApplication.get("invoiceId"), "invoiceItemSeqId", paymentApplication.get("invoiceItemSeqId"), "billingAccountId", paymentApplication.get("billingAccountId"), "paymentId", paymentApplication.get("paymentId"), "toPaymentId", paymentApplication.get("toPaymentId"), "taxAuthGeoId", paymentApplication.get("taxAuthGeoId")) .queryList(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } if (checkAppls.size() > 0) { if (debug) Debug.logInfo(checkAppls.size() + " records already exist", module); // 1 record exists just update and if different ID delete other record and add together. GenericValue checkAppl = checkAppls.get(0); // if new record add to the already existing one. if (paymentApplication.get("paymentApplicationId") == null) { // add 2 amounts together checkAppl.set("amountApplied", paymentApplication.getBigDecimal("amountApplied") .add(checkAppl.getBigDecimal("amountApplied")).setScale(DECIMALS, ROUNDING)); if (debug) Debug.logInfo("Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module); try { checkAppl.store(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } else if (paymentApplication.getString("paymentApplicationId") .equals(checkAppl.getString("paymentApplicationId"))) { // update existing record in-place checkAppl.set("amountApplied", paymentApplication.getBigDecimal("amountApplied")); if (debug) Debug.logInfo("Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module); try { checkAppl.store(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } else { // two existing records, an updated one added to the existing one // add 2 amounts together checkAppl.set("amountApplied", paymentApplication.getBigDecimal("amountApplied") .add(checkAppl.getBigDecimal("amountApplied")).setScale(DECIMALS, ROUNDING)); // delete paymentApplication record and update the checkAppls one. if (debug) Debug.logInfo("Delete paymentApplication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module); try { paymentApplication.remove(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } // update amount existing record if (debug) Debug.logInfo("Update paymentApplication record: " + checkAppl.getString("paymentApplicationId") + " with appliedAmount:" + checkAppl.getBigDecimal("amountApplied"), module); try { checkAppl.store(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } } else { if (debug) Debug.logInfo("No records found with paymentId,invoiceid..etc probaly changed one of them...", module); // create record if ID null; if (paymentApplication.get("paymentApplicationId") == null) { paymentApplication.set("paymentApplicationId", delegator.getNextSeqId("PaymentApplication")); if (debug) Debug.logInfo("Create new paymentAppication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module); try { paymentApplication.create(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } else { // update existing record (could not be found because a non existing combination of paymentId/invoiceId/invoiceSeqId/ etc... was provided if (debug) Debug.logInfo("Update existing paymentApplication record: " + paymentApplication.getString("paymentApplicationId") + " with appliedAmount:" + paymentApplication.getBigDecimal("amountApplied"), module); try { paymentApplication.store(); } catch (GenericEntityException e) { return ServiceUtil.returnError(e.getMessage()); } } } return results; } public static Map<String, Object> checkPaymentInvoices(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); String paymentId = (String) context.get("paymentId"); try { GenericValue payment = EntityQuery.use(delegator).from("Payment").where("paymentId", paymentId) .queryOne(); if (payment == null) throw new GenericServiceException("Payment with ID [" + paymentId + "] not found!"); List<GenericValue> paymentApplications = payment.getRelated("PaymentApplication", null, null, false); if (UtilValidate.isEmpty(paymentApplications)) return ServiceUtil.returnSuccess(); // TODO: this is inefficient -- instead use HashSet to construct a distinct Set of invoiceIds, then iterate over it and call checkInvoicePaymentAppls for (GenericValue paymentApplication : paymentApplications) { String invoiceId = paymentApplication.getString("invoiceId"); if (invoiceId != null) { Map<String, Object> serviceResult = dispatcher.runSync("checkInvoicePaymentApplications", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "userLogin", userLogin)); if (ServiceUtil.isError(serviceResult)) return serviceResult; } } return ServiceUtil.returnSuccess(); } catch (GenericServiceException se) { Debug.logError(se, se.getMessage(), module); return ServiceUtil.returnError(se.getMessage()); } catch (GenericEntityException ee) { Debug.logError(ee, ee.getMessage(), module); return ServiceUtil.returnError(ee.getMessage()); } } public static Map<String, Object> importInvoice(DispatchContext dctx, Map<String, Object> context) { Delegator delegator = dctx.getDelegator(); LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); ByteBuffer fileBytes = (ByteBuffer) context.get("uploadedFile"); String organizationPartyId = (String) context.get("organizationPartyId"); String encoding = System.getProperty("file.encoding"); String csvString = Charset.forName(encoding).decode(fileBytes).toString(); final BufferedReader csvReader = new BufferedReader(new StringReader(csvString)); CSVFormat fmt = CSVFormat.DEFAULT.withHeader(); List<String> errMsgs = FastList.newInstance(); List<String> newErrMsgs = FastList.newInstance(); String lastInvoiceId = null; String currentInvoiceId = null; String newInvoiceId = null; int invoicesCreated = 0; if (fileBytes == null) { return ServiceUtil.returnError("Uploaded file data not found"); } try { for (final CSVRecord rec : fmt.parse(csvReader)) { currentInvoiceId = rec.get("invoiceId"); if (lastInvoiceId == null || !currentInvoiceId.equals(lastInvoiceId)) { newInvoiceId = null; Map<String, Object> invoice = UtilMisc.toMap("invoiceTypeId", rec.get("invoiceTypeId"), "partyIdFrom", rec.get("partyIdFrom"), "partyId", rec.get("partyId"), "invoiceDate", rec.get("invoiceDate"), "dueDate", rec.get("dueDate"), "currencyUomId", rec.get("currencyUomId"), "description", rec.get("description"), "referenceNumber", rec.get("referenceNumber") + " Imported: orginal InvoiceId: " + currentInvoiceId, "userLogin", userLogin); // replace values if required if (UtilValidate.isNotEmpty(rec.get("partyIdFromTrans"))) { invoice.put("partyIdFrom", rec.get("partyIdFromTrans")); } if (UtilValidate.isNotEmpty(rec.get("partyIdTrans"))) { invoice.put("partyId", rec.get("partyIdTrans")); } // invoice validation try { newErrMsgs = FastList.newInstance(); if (UtilValidate.isEmpty(invoice.get("partyIdFrom"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Mandatory Party Id From and Party Id From Trans missing for invoice: " + currentInvoiceId); } else if (EntityQuery.use(delegator).from("Party") .where("partyId", invoice.get("partyIdFrom")).queryOne() == null) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": partyIdFrom: " + invoice.get("partyIdFrom") + " not found for invoice: " + currentInvoiceId); } if (UtilValidate.isEmpty(invoice.get("partyId"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Mandatory Party Id and Party Id Trans missing for invoice: " + currentInvoiceId); } else if (EntityQuery.use(delegator).from("Party").where("partyId", invoice.get("partyId")) .queryOne() == null) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": partyId: " + invoice.get("partyId") + " not found for invoice: " + currentInvoiceId); } if (UtilValidate.isEmpty(invoice.get("invoiceTypeId"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Mandatory Invoice Type missing for invoice: " + currentInvoiceId); } else if (EntityQuery.use(delegator).from("InvoiceType") .where("invoiceTypeId", invoice.get("invoiceTypeId")).queryOne() == null) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": InvoiceItem type id: " + invoice.get("invoiceTypeId") + " not found for invoice: " + currentInvoiceId); } GenericValue invoiceType = EntityQuery.use(delegator).from("InvoiceType") .where("invoiceTypeId", invoice.get("invoiceTypeId")).queryOne(); if ("PURCHASE_INVOICE".equals(invoiceType.getString("parentTypeId")) && !invoice.get("partyId").equals(organizationPartyId)) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": A purchase type invoice should have the partyId 'To' being the organizationPartyId(=" + organizationPartyId + ")! however is " + invoice.get("partyId") + "! invoice: " + currentInvoiceId); } if ("SALES_INVOICE".equals(invoiceType.getString("parentTypeId")) && !invoice.get("partyIdFrom").equals(organizationPartyId)) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": A sales type invoice should have the partyId 'from' being the organizationPartyId(=" + organizationPartyId + ")! however is " + invoice.get("partyIdFrom") + "! invoice: " + currentInvoiceId); } } catch (GenericEntityException e) { Debug.logError("Valication checking problem against database. due to " + e.getMessage(), module); } if (newErrMsgs.size() > 0) { errMsgs.addAll(newErrMsgs); } else { Map<String, Object> invoiceResult = null; try { invoiceResult = dispatcher.runSync("createInvoice", invoice); } catch (GenericServiceException e) { csvReader.close(); Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } newInvoiceId = (String) invoiceResult.get("invoiceId"); invoicesCreated++; } lastInvoiceId = currentInvoiceId; } if (newInvoiceId != null) { Map<String, Object> invoiceItem = UtilMisc.toMap("invoiceId", newInvoiceId, "invoiceItemSeqId", rec.get("invoiceItemSeqId"), "invoiceItemTypeId", rec.get("invoiceItemTypeId"), "productId", rec.get("productId"), "description", rec.get("itemDescription"), "amount", rec.get("amount"), "quantity", rec.get("quantity"), "userLogin", userLogin); if (UtilValidate.isNotEmpty(rec.get("productIdTrans"))) { invoiceItem.put("productId", rec.get("productIdTrans")); } // invoice item validation try { newErrMsgs = FastList.newInstance(); if (UtilValidate.isEmpty(invoiceItem.get("invoiceItemSeqId"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Mandatory item sequence Id missing for invoice: " + currentInvoiceId); } if (UtilValidate.isEmpty(invoiceItem.get("invoiceItemTypeId"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Mandatory invoice item type missing for invoice: " + currentInvoiceId); } else if (EntityQuery.use(delegator).from("InvoiceItemType") .where("invoiceItemTypeId", invoiceItem.get("invoiceItemTypeId")) .queryOne() == null) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": InvoiceItem Item type id: " + invoiceItem.get("invoiceItemTypeId") + " not found for invoice: " + currentInvoiceId + " Item seqId:" + invoiceItem.get("invoiceItemSeqId")); } if (UtilValidate.isEmpty(invoiceItem.get("productId")) && UtilValidate.isEmpty(invoiceItem.get("description"))) { } if (UtilValidate.isNotEmpty(invoiceItem.get("productId")) && EntityQuery.use(delegator).from("Product") .where("productId", invoiceItem.get("productId")).queryOne() == null) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Product Id: " + invoiceItem.get("productId") + " not found for invoice: " + currentInvoiceId + " Item seqId:" + invoiceItem.get("invoiceItemSeqId")); } if (UtilValidate.isEmpty(invoiceItem.get("amount")) && UtilValidate.isEmpty(invoiceItem.get("quantity"))) { newErrMsgs.add("Line number " + rec.getRecordNumber() + ": Either or both quantity and amount is required for invoice: " + currentInvoiceId + " Item seqId:" + invoiceItem.get("invoiceItemSeqId")); } } catch (GenericEntityException e) { Debug.logError("Validation checking problem against database. due to " + e.getMessage(), module); } if (newErrMsgs.size() > 0) { errMsgs.addAll(newErrMsgs); } else { try { dispatcher.runSync("createInvoiceItem", invoiceItem); } catch (GenericServiceException e) { csvReader.close(); Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } } } } catch (IOException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (errMsgs.size() > 0) { return ServiceUtil.returnError(errMsgs); } Map<String, Object> result = ServiceUtil.returnSuccess(invoicesCreated + " new invoice(s) created"); result.put("organizationPartyId", organizationPartyId); return result; } }