blob: c0fcf161b463a13d9dd1416d8fb2e16faa0c21ca [file] [log] [blame]
/*******************************************************************************
* 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;
}
}