in applications/accounting/src/main/java/org/apache/ofbiz/accounting/invoice/InvoiceServices.java [2076:2388]
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.cast(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(UtilProperties.getMessage(RESOURCE, "AccountingReturnTypeCannotBeNull", locale));
}
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;
}
List<GenericValue> returnItems = returnHeader.getRelated("ReturnItem", null, null, false);
if (!returnItems.isEmpty()) {
for (GenericValue returnItem : returnItems) {
if ("RETURN_COMPLETED".equals(returnItem.getString("statusId"))) {
GenericValue product = returnItem.getRelatedOne("Product", false);
if (!ProductWorker.isPhysical(product)) {
boolean isNonPhysicalItemToReturn = false;
List<GenericValue> returnItemBillings = returnItem.getRelated("ReturnItemBilling", null, null, false);
if (!returnItemBillings.isEmpty()) {
GenericValue invoice = EntityUtil.getFirst(returnItemBillings).getRelatedOne("Invoice", false);
if ("INVOICE_CANCELLED".equals(invoice.getString("statusId"))) {
isNonPhysicalItemToReturn = true;
}
} else {
isNonPhysicalItemToReturn = true;
}
if (isNonPhysicalItemToReturn) {
if (UtilValidate.isEmpty(billItems)) {
billItems = new ArrayList<>();
}
billItems.add(returnItem);
}
}
}
}
}
Map<String, Object> results = ServiceUtil.returnSuccess();
if (UtilValidate.isNotEmpty(billItems)) {
// 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(ServiceUtil.getErrorMessage(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 = BigDecimal.ZERO;
BigDecimal promisedTotal = BigDecimal.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;
GenericValue returnItem = null;
BigDecimal quantity = BigDecimal.ZERO;
if ("ShipmentReceipt".equals(item.getEntityName())) {
shipmentReceiptFound = true;
} else if ("ItemIssuance".equals(item.getEntityName())) {
itemIssuanceFound = true;
} else if ("ReturnItem".equals(item.getEntityName())) {
quantity = item.getBigDecimal("returnQuantity");
returnItem = item;
} else {
Debug.logError("Unexpected entity " + item + " of type " + item.getEntityName(), MODULE);
}
// we need the related return item and product
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));
}
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(ServiceUtil.getErrorMessage(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(ServiceUtil.getErrorMessage(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 = BigDecimal.ZERO;
if (shipmentReceiptFound) {
cancelQuantity = item.getBigDecimal("quantityRejected");
} else if (itemIssuanceFound) {
cancelQuantity = item.getBigDecimal("cancelQuantity");
}
if (cancelQuantity == null) {
cancelQuantity = BigDecimal.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 ("RET_SALES_TAX_ADJ".equals(adjustment.get("returnAdjustmentTypeId"))) {
input.put("taxableFlag", "N");
}
// create the invoice item
serviceResults = dispatcher.runSync("createInvoiceItem", input);
if (ServiceUtil.isError(serviceResults)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(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 = BigDecimal.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(ServiceUtil.getErrorMessage(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(ServiceUtil.getErrorMessage(serviceResults));
}
// return the invoiceId
results.put("invoiceId", invoiceId);
}
return results;
} catch (GenericServiceException | GenericEntityException e) {
Debug.logError(e, errorMsg + e.getMessage(), MODULE);
return ServiceUtil.returnError(errorMsg + e.getMessage());
}
}