public static Map createInvoiceFromReturn()

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());
        }
    }