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 = delegator.findByPrimaryKey("ReturnHeader", UtilMisc.toMap("returnId", returnId));
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.getRelatedOneCache("ReturnItem");
} else if (itemIssuanceFound) {
GenericValue shipmentItem = item.getRelatedOneCache("ShipmentItem");
GenericValue returnItemShipment = EntityUtil.getFirst(shipmentItem.getRelated("ReturnItemShipment"));
returnItem = returnItemShipment.getRelatedOneCache("ReturnItem");
}
if (returnItem == null) continue; // Just to prevent NPE
GenericValue product = returnItem.getRelatedOneCache("Product");
// 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.getRelatedCache("ReturnAdjustment");
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.getRelatedByAndCache("ReturnAdjustment", UtilMisc.toMap("returnItemSeqId", "_NA_"));
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