use of org.apache.ofbiz.order.order.OrderReadHelper in project ofbiz-framework by apache.
the class InvoiceServices method createInvoiceForOrder.
/* 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 == null) {
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)) {
if (Debug.verboseOn())
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 ("SALES_ORDER".equals(orderType)) {
invoiceType = "SALES_INVOICE";
} else if ("PURCHASE_ORDER".equals(orderType)) {
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 shippableQuantity = orh.getShippableQuantity(null);
BigDecimal orderSubTotal = orh.getOrderItemsSubTotal();
BigDecimal orderQuantity = orh.getTotalOrderItemsQuantity();
// these variables are for pro-rating order amounts across invoices, so they should not be rounded off for maximum accuracy
BigDecimal invoiceShipProRateAmount = BigDecimal.ZERO;
BigDecimal invoiceShippableQuantity = BigDecimal.ZERO;
BigDecimal invoiceSubTotal = BigDecimal.ZERO;
BigDecimal invoiceQuantity = BigDecimal.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 = new HashMap<>();
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 = new HashMap<>();
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);
// 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 ("PURCHASE_INVOICE".equals(invoiceType)) {
// 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(EntityUtil.filterByDate(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);
}
if (orderItem == 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 = BigDecimal.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(BigDecimal.ZERO) < 0) {
billingQuantity = BigDecimal.ZERO;
}
}
if (billingQuantity == null) {
billingQuantity = BigDecimal.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)) && ("SALES_INVOICE".equals(invoiceType))) {
shippingApplies = true;
}
BigDecimal billingAmount = BigDecimal.ZERO;
GenericValue OrderAdjustment = EntityUtil.getFirst(orderItem.getRelated("OrderAdjustment", UtilMisc.toMap("orderAdjustmentTypeId", "VAT_TAX"), null, false));
/* Apply formula to get actual product price to set amount in invoice item
Formula is: productPrice = (productPriceWithTax.multiply(100)) / (orderAdj sourcePercentage + 100))
product price = (43*100) / (20+100) = 35.83 (Here product price is 43 with VAT)
*/
if (UtilValidate.isNotEmpty(OrderAdjustment) && (OrderAdjustment.getBigDecimal("amount").signum() == 0) && UtilValidate.isNotEmpty(OrderAdjustment.getBigDecimal("amountAlreadyIncluded")) && OrderAdjustment.getBigDecimal("amountAlreadyIncluded").signum() != 0) {
BigDecimal sourcePercentageTotal = OrderAdjustment.getBigDecimal("sourcePercentage").add(new BigDecimal(100));
billingAmount = orderItem.getBigDecimal("unitPrice").divide(sourcePercentageTotal, 100, ROUNDING).multiply(new BigDecimal(100)).setScale(invoiceTypeDecimals, ROUNDING);
} else {
billingAmount = orderItem.getBigDecimal("unitPrice").setScale(invoiceTypeDecimals, ROUNDING);
}
Map<String, Object> createInvoiceItemContext = new HashMap<>();
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("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) && ("SALES_INVOICE".equals(invoiceType))) {
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);
invoiceShippableQuantity = invoiceQuantity.add(billingQuantity).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 = new HashMap<>();
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())) {
/* Find ShipmentItemBilling based on shipmentId, shipmentItemSeqId, invoiceId, invoiceItemSeqId as
because if any order item has multiple quantity and reserved by multiple inventories then there will be multiple invoice items.
In that case ShipmentItemBilling was creating only for one invoice item. Fixed under OFBIZ-6806.
*/
List<GenericValue> shipmentItemBillings = EntityQuery.use(delegator).from("ShipmentItemBilling").where("shipmentId", currentValue.get("shipmentId"), "shipmentItemSeqId", currentValue.get("shipmentItemSeqId"), "invoiceId", invoiceId, "invoiceItemSeqId", invoiceItemSeqId).queryList();
if (UtilValidate.isEmpty(shipmentItemBillings)) {
// create the ShipmentItemBilling record
Map<String, Object> shipmentItemBillingCtx = new HashMap<>();
shipmentItemBillingCtx.put("invoiceId", invoiceId);
shipmentItemBillingCtx.put("invoiceItemSeqId", invoiceItemSeqId);
shipmentItemBillingCtx.put("shipmentId", currentValue.get("shipmentId"));
shipmentItemBillingCtx.put("shipmentItemSeqId", currentValue.get("shipmentItemSeqId"));
shipmentItemBillingCtx.put("userLogin", userLogin);
Map<String, Object> result = dispatcher.runSync("createShipmentItemBilling", shipmentItemBillingCtx);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(result));
}
}
}
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));
if (ServiceUtil.isError(checkResult)) {
Debug.logError("Accounting trouble calling calculateInvoicedAdjustmentTotal service", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale));
}
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));
}
// Set adjustment amount as amountAlreadyIncluded to continue invoice item creation process
Boolean isTaxIncludedInPrice = "VAT_TAX".equals(adj.getString("orderAdjustmentTypeId")) && UtilValidate.isNotEmpty(adj.getBigDecimal("amountAlreadyIncluded")) && adj.getBigDecimal("amountAlreadyIncluded").signum() != 0;
if ((adj.getBigDecimal("amount").signum() == 0) && isTaxIncludedInPrice) {
adj.set("amount", adj.getBigDecimal("amountAlreadyIncluded"));
}
// If the absolute invoiced amount >= the abs of the adjustment amount, the full amount has already been invoiced, so skip this adjustment
if (adjAlreadyInvoicedAmount.abs().compareTo(adj.getBigDecimal("amount").setScale(invoiceTypeDecimals, ROUNDING).abs()) > 0) {
continue;
}
BigDecimal originalOrderItemQuantity = OrderReadHelper.getOrderItemQuantity(originalOrderItem);
BigDecimal amount = BigDecimal.ZERO;
if (originalOrderItemQuantity.signum() != 0) {
if (adj.get("amount") != null) {
if ("PROMOTION_ADJUSTMENT".equals(adj.getString("orderAdjustmentTypeId")) && adj.get("productPromoId") != null) {
/* Find negative amountAlreadyIncluded in OrderAdjustment to subtract it from discounted amount.
As we stored negative sales tax amount in order adjustment for discounted item.
*/
List<EntityExpr> exprs = UtilMisc.toList(EntityCondition.makeCondition("orderId", EntityOperator.EQUALS, orderItem.getString("orderId")), EntityCondition.makeCondition("orderItemSeqId", EntityOperator.EQUALS, orderItem.getString("orderItemSeqId")), EntityCondition.makeCondition("orderAdjustmentTypeId", EntityOperator.EQUALS, "VAT_TAX"), EntityCondition.makeCondition("amountAlreadyIncluded", EntityOperator.LESS_THAN, BigDecimal.ZERO));
EntityCondition andCondition = EntityCondition.makeCondition(exprs, EntityOperator.AND);
GenericValue orderAdjustment = EntityUtil.getFirst(delegator.findList("OrderAdjustment", andCondition, null, null, null, false));
if (UtilValidate.isNotEmpty(orderAdjustment)) {
amount = adj.getBigDecimal("amount").subtract(orderAdjustment.getBigDecimal("amountAlreadyIncluded")).setScale(100, ROUNDING);
} else {
amount = adj.getBigDecimal("amount");
}
} else {
// set decimals = 100 means we don't round this intermediate value, which is very important
if (isTaxIncludedInPrice) {
BigDecimal priceWithTax = originalOrderItem.getBigDecimal("unitPrice");
// Get tax included in item price
amount = priceWithTax.subtract(billingAmount);
amount = amount.multiply(billingQuantity);
// get adjustment amount
/* Get tax amount of other invoice and calculate remaining amount need to store in invoice item(Handle case of of partial shipment and promotional item)
to adjust tax amount in invoice item.
*/
BigDecimal otherInvoiceTaxAmount = BigDecimal.ZERO;
GenericValue orderAdjBilling = EntityQuery.use(delegator).from("OrderAdjustmentBilling").where("orderAdjustmentId", adj.getString("orderAdjustmentId")).queryFirst();
if (UtilValidate.isNotEmpty(orderAdjBilling)) {
// FIXME: Need to check here isTaxIncludedInPrice pass to use cache
List<GenericValue> invoiceItems = EntityQuery.use(delegator).from("InvoiceItem").where("invoiceId", orderAdjBilling.getString("invoiceId"), "invoiceItemTypeId", "ITM_SALES_TAX", "productId", originalOrderItem.getString("productId")).cache(isTaxIncludedInPrice).queryList();
for (GenericValue invoiceItem : invoiceItems) {
otherInvoiceTaxAmount = otherInvoiceTaxAmount.add(invoiceItem.getBigDecimal("amount"));
}
if (otherInvoiceTaxAmount.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal remainingAmount = adj.getBigDecimal("amountAlreadyIncluded").subtract(otherInvoiceTaxAmount);
amount = amount.min(remainingAmount);
}
}
amount = amount.min(adj.getBigDecimal("amountAlreadyIncluded")).setScale(100, ROUNDING);
} else {
amount = adj.getBigDecimal("amount").divide(originalOrderItemQuantity, 100, ROUNDING);
amount = amount.multiply(billingQuantity);
}
}
// Tax needs to be rounded differently from other order adjustments
if ("SALES_TAX".equals(adj.getString("orderAdjustmentTypeId"))) {
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 = new HashMap<>();
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("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);
// TODO: This is not an ideal solution. Instead, we need to use OrderAdjustment.includeInTax when it is implemented
if (!("SALES_TAX".equals(adj.getString("orderAdjustmentTypeId")))) {
createInvoiceItemAdjContext.put("taxableFlag", product.get("taxable"));
}
// 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 = new HashMap<>();
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 = new HashMap<>();
Map<GenericValue, BigDecimal> taxAdjustments = new HashMap<>();
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));
if (ServiceUtil.isError(checkResult)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource, "AccountingTroubleCallingCalculateInvoicedAdjustmentTotalService", locale));
}
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 (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
BigDecimal divisor = orderSubTotal;
BigDecimal multiplier = invoiceSubTotal;
if (BigDecimal.ZERO.compareTo(multiplier) == 0 && BigDecimal.ZERO.compareTo(divisor) == 0) {
// if multiplier and divisor are equal to zero then use the quantities instead of the amounts
// this is useful when the order has free items and misc charges
divisor = orderQuantity;
multiplier = invoiceQuantity;
}
calcHeaderAdj(delegator, adj, invoiceType, invoiceId, invoiceItemSeqId, divisor, multiplier, 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);
}
}
// numerator/denominator problems when the shipping is not pro-rated but rather charged all on the first invoice
for (Map.Entry<GenericValue, BigDecimal> set : shipAdjustments.entrySet()) {
BigDecimal adjAlreadyInvoicedAmount = set.getValue();
GenericValue adj = set.getKey();
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;
if (BigDecimal.ZERO.compareTo(multiplier) == 0 && BigDecimal.ZERO.compareTo(divisor) == 0) {
// if multiplier and divisor are equal to zero then use the quantities instead of the amounts
// this is useful when the order has free items and shipping charges
divisor = shippableQuantity;
multiplier = invoiceShippableQuantity;
}
// 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 = new LinkedList<>();
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 = new HashMap<>();
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));
}
}
use of org.apache.ofbiz.order.order.OrderReadHelper in project ofbiz-framework by apache.
the class GiftCertificateServices method giftCertificateProcessor.
// Fullfilment Services
public static Map<String, Object> giftCertificateProcessor(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
BigDecimal amount = (BigDecimal) context.get("processAmount");
String currency = (String) context.get("currency");
// make sure we have a currency
if (currency == null) {
currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
}
// get the authorizations
GenericValue orderPaymentPreference = (GenericValue) context.get("orderPaymentPreference");
GenericValue authTransaction = (GenericValue) context.get("authTrans");
if (authTransaction == null) {
authTransaction = PaymentGatewayServices.getAuthTransaction(orderPaymentPreference);
}
if (authTransaction == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingFinAccountCannotCapture", locale));
}
// get the gift certificate and its authorization from the authorization
String finAccountAuthId = authTransaction.getString("referenceNum");
try {
GenericValue finAccountAuth = EntityQuery.use(delegator).from("FinAccountAuth").where("finAccountAuthId", finAccountAuthId).queryOne();
GenericValue giftCard = finAccountAuth.getRelatedOne("FinAccount", false);
// make sure authorization has not expired
Timestamp authExpiration = finAccountAuth.getTimestamp("thruDate");
if ((authExpiration != null) && (authExpiration.before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingFinAccountAuthorizationExpired", UtilMisc.toMap("paymentGatewayResponseId", authTransaction.getString("paymentGatewayResponseId"), "authExpiration", authExpiration), locale));
}
// make sure the fin account itself has not expired
if ((giftCard.getTimestamp("thruDate") != null) && (giftCard.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberExpired", UtilMisc.toMap("thruDate", giftCard.getTimestamp("thruDate")), locale));
}
// obtain the order information
OrderReadHelper orh = new OrderReadHelper(delegator, orderPaymentPreference.getString("orderId"));
Map<String, Object> redeemCtx = new HashMap<>();
redeemCtx.put("userLogin", userLogin);
redeemCtx.put("productStoreId", orh.getProductStoreId());
redeemCtx.put("cardNumber", giftCard.get("finAccountId"));
redeemCtx.put("pinNumber", giftCard.get("finAccountCode"));
redeemCtx.put("currency", currency);
if (orh.getBillToParty() != null) {
redeemCtx.put("partyId", orh.getBillToParty().get("partyId"));
}
redeemCtx.put("amount", amount);
// invoke the redeem service
Map<String, Object> redeemResult = null;
redeemResult = dispatcher.runSync("redeemGiftCertificate", redeemCtx);
if (ServiceUtil.isError(redeemResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(redeemResult));
}
// now release the authorization should this use the gift card release service?
Map<String, Object> releaseResult = dispatcher.runSync("expireFinAccountAuth", UtilMisc.<String, Object>toMap("userLogin", userLogin, "finAccountAuthId", finAccountAuthId));
if (ServiceUtil.isError(releaseResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(releaseResult));
}
String authRefNum = authTransaction.getString("referenceNum");
Map<String, Object> result = ServiceUtil.returnSuccess();
if (redeemResult != null) {
Boolean processResult = (Boolean) redeemResult.get("processResult");
result.put("processAmount", amount);
result.put("captureResult", processResult);
result.put("captureCode", "C");
result.put("captureRefNum", redeemResult.get("referenceNum"));
result.put("authRefNum", authRefNum);
}
return result;
} catch (GenericEntityException | GenericServiceException ex) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberCannotProcess", UtilMisc.toMap("errorString", ex.getMessage()), locale));
}
}
use of org.apache.ofbiz.order.order.OrderReadHelper in project ofbiz-framework by apache.
the class GiftCertificateServices method giftCertificateReload.
public static Map<String, Object> giftCertificateReload(DispatchContext dctx, Map<String, ? extends Object> context) {
// this service should always be called via FULFILLMENT_EXTSYNC
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
GenericValue orderItem = (GenericValue) context.get("orderItem");
Locale locale = (Locale) context.get("locale");
// order ID for tracking
String orderId = orderItem.getString("orderId");
// the order header for store info
GenericValue orderHeader = null;
try {
orderHeader = orderItem.getRelatedOne("OrderHeader", false);
} catch (GenericEntityException e) {
Debug.logError(e, "Unable to get OrderHeader from OrderItem", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "OrderCannotGetOrderHeader", UtilMisc.toMap("orderId", orderId), locale));
}
// get the order read helper
OrderReadHelper orh = new OrderReadHelper(orderHeader);
// get the currency
String currency = orh.getCurrency();
// make sure we have a currency
if (currency == null) {
currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
}
// get the product store
String productStoreId = null;
if (orderHeader != null) {
productStoreId = orh.getProductStoreId();
}
if (productStoreId == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "AccountingGiftCerticateNumberCannotReload", UtilMisc.toMap("orderId", orderId), locale));
}
// payment config
GenericValue paymentSetting = ProductStoreWorker.getProductStorePaymentSetting(delegator, productStoreId, "GIFT_CARD", null, true);
String paymentConfig = null;
if (paymentSetting != null) {
paymentConfig = paymentSetting.getString("paymentPropertiesPath");
}
if (paymentConfig == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "AccountingGiftCerticateNumberCannotGetPaymentConfiguration", locale));
}
// party ID for tracking
GenericValue placingParty = orh.getPlacingParty();
String partyId = null;
if (placingParty != null) {
partyId = placingParty.getString("partyId");
}
// amount of the gift card reload
BigDecimal amount = orderItem.getBigDecimal("unitPrice");
// survey information
String surveyId = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.surveyId", delegator);
// get the survey response
GenericValue surveyResponse = null;
try {
// there should be only one
surveyResponse = EntityQuery.use(delegator).from("SurveyResponse").where("orderId", orderId, "orderItemSeqId", orderItem.get("orderItemSeqId"), "surveyId", surveyId).orderBy("-responseDate").queryFirst();
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "AccountingGiftCerticateNumberCannotReload", locale));
}
// get the response answers
List<GenericValue> responseAnswers = null;
try {
responseAnswers = surveyResponse.getRelated("SurveyResponseAnswer", null, null, false);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "AccountingGiftCerticateNumberCannotReloadFromSurveyAnswers", locale));
}
// make a map of answer info
Map<String, Object> answerMap = new HashMap<>();
if (responseAnswers != null) {
for (GenericValue answer : responseAnswers) {
GenericValue question = null;
try {
question = answer.getRelatedOne("SurveyQuestion", false);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrderError, "AccountingGiftCerticateNumberCannotReloadFromSurveyAnswers", locale));
}
if (question != null) {
String desc = question.getString("description");
// only support text response types for now
String ans = answer.getString("textResponse");
answerMap.put(desc, ans);
}
}
}
String cardNumberKey = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.survey.cardNumber", delegator);
String pinNumberKey = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.survey.pinNumber", delegator);
String cardNumber = (String) answerMap.get(cardNumberKey);
String pinNumber = (String) answerMap.get(pinNumberKey);
// reload the gift card
Map<String, Object> reloadCtx = new HashMap<>();
reloadCtx.put("productStoreId", productStoreId);
reloadCtx.put("currency", currency);
reloadCtx.put("partyId", partyId);
reloadCtx.put("cardNumber", cardNumber);
reloadCtx.put("pinNumber", pinNumber);
reloadCtx.put("amount", amount);
reloadCtx.put("userLogin", userLogin);
String errorMessage = null;
Map<String, Object> reloadGcResult = null;
try {
reloadGcResult = dispatcher.runSync("addFundsToGiftCertificate", reloadCtx);
} catch (GenericServiceException e) {
Debug.logError(e, module);
errorMessage = "Unable to call reload service!";
}
if (ServiceUtil.isError(reloadGcResult)) {
errorMessage = ServiceUtil.getErrorMessage(reloadGcResult);
}
// create the fulfillment record
Map<String, Object> gcFulFill = new HashMap<>();
gcFulFill.put("typeEnumId", "GC_RELOAD");
gcFulFill.put("userLogin", userLogin);
gcFulFill.put("partyId", partyId);
gcFulFill.put("orderId", orderId);
gcFulFill.put("orderItemSeqId", orderItem.get("orderItemSeqId"));
gcFulFill.put("surveyResponseId", surveyResponse.get("surveyResponseId"));
gcFulFill.put("cardNumber", cardNumber);
gcFulFill.put("pinNumber", pinNumber);
gcFulFill.put("amount", amount);
if (reloadGcResult != null) {
gcFulFill.put("responseCode", reloadGcResult.get("responseCode"));
gcFulFill.put("referenceNum", reloadGcResult.get("referenceNum"));
}
try {
dispatcher.runAsync("createGcFulFillmentRecord", gcFulFill, true);
} catch (GenericServiceException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberCannotStoreFulfillmentInfo", UtilMisc.toMap("errorString", e.getMessage()), locale));
}
if (errorMessage != null) {
// there was a problem
Debug.logError("Reload Failed Need to Refund : " + reloadGcResult, module);
// process the return
try {
Map<String, Object> refundCtx = UtilMisc.toMap("orderItem", orderItem, "partyId", partyId, "userLogin", userLogin);
dispatcher.runAsync("refundGcPurchase", refundCtx, null, true, 300, true);
} catch (GenericServiceException e) {
Debug.logError(e, "ERROR! Unable to call create refund service; this failed reload will NOT be refunded", module);
}
return ServiceUtil.returnError(errorMessage);
}
// add some information to the answerMap for the email
answerMap.put("processResult", reloadGcResult.get("processResult"));
answerMap.put("responseCode", reloadGcResult.get("responseCode"));
answerMap.put("previousAmount", reloadGcResult.get("previousBalance"));
answerMap.put("amount", reloadGcResult.get("amount"));
// get the email setting for this email type
GenericValue productStoreEmail = null;
String emailType = "PRDS_GC_RELOAD";
try {
productStoreEmail = EntityQuery.use(delegator).from("ProductStoreEmailSetting").where("productStoreId", productStoreId, "emailType", emailType).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, "Unable to get product store email setting for gift card purchase", module);
}
if (productStoreEmail == null) {
Debug.logError("No gift card purchase email setting found for this store; cannot send gift card information", module);
} else {
answerMap.put("locale", locale);
Map<String, Object> emailCtx = new HashMap<>();
String bodyScreenLocation = productStoreEmail.getString("bodyScreenLocation");
if (UtilValidate.isEmpty(bodyScreenLocation)) {
bodyScreenLocation = ProductStoreWorker.getDefaultProductStoreEmailScreenLocation(emailType);
}
emailCtx.put("bodyScreenUri", bodyScreenLocation);
emailCtx.put("bodyParameters", answerMap);
emailCtx.put("sendTo", orh.getOrderEmailString());
emailCtx.put("contentType", productStoreEmail.get("contentType"));
emailCtx.put("sendFrom", productStoreEmail.get("fromAddress"));
emailCtx.put("sendCc", productStoreEmail.get("ccAddress"));
emailCtx.put("sendBcc", productStoreEmail.get("bccAddress"));
emailCtx.put("subject", productStoreEmail.getString("subject"));
emailCtx.put("userLogin", userLogin);
// send off the email async so we will retry on failed attempts
try {
dispatcher.runAsync("sendMailFromScreen", emailCtx);
} catch (GenericServiceException e) {
Debug.logError(e, "Problem sending mail", module);
// this is fatal; we will rollback and try again later
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberCannotSendEmailNotice", UtilMisc.toMap("errorString", e.toString()), locale));
}
}
return ServiceUtil.returnSuccess();
}
use of org.apache.ofbiz.order.order.OrderReadHelper in project ofbiz-framework by apache.
the class GiftCertificateServices method giftCertificateAuthorize.
public static Map<String, Object> giftCertificateAuthorize(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
Delegator delegator = dctx.getDelegator();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
GenericValue giftCard = (GenericValue) context.get("giftCard");
String currency = (String) context.get("currency");
String orderId = (String) context.get("orderId");
BigDecimal amount = (BigDecimal) context.get("processAmount");
// make sure we have a currency
if (currency == null) {
currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
}
// obtain the order information
OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
String productStoreId = orh.getProductStoreId();
try {
// if the store requires pin codes, then validate pin code against card number, and the gift certificate's finAccountId is the gift card's card number
// otherwise, the gift card's card number is an ecrypted string, which must be decoded to find the FinAccount
GenericValue giftCertSettings = EntityQuery.use(delegator).from("ProductStoreFinActSetting").where("productStoreId", productStoreId, "finAccountTypeId", FinAccountHelper.giftCertFinAccountTypeId).cache().queryOne();
GenericValue finAccount = null;
String finAccountId = null;
if (UtilValidate.isNotEmpty(giftCertSettings)) {
if ("Y".equals(giftCertSettings.getString("requirePinCode"))) {
if (validatePin(delegator, giftCard.getString("cardNumber"), giftCard.getString("pinNumber"))) {
finAccountId = giftCard.getString("cardNumber");
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
}
} else {
finAccount = FinAccountHelper.getFinAccountFromCode(giftCard.getString("cardNumber"), delegator);
if (finAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberNotFound", UtilMisc.toMap("finAccountId", ""), locale));
}
finAccountId = finAccount.getString("finAccountId");
}
} else {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingFinAccountSetting", UtilMisc.toMap("productStoreId", productStoreId, "finAccountTypeId", FinAccountHelper.giftCertFinAccountTypeId), locale));
}
if (finAccountId == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberPinNotValid", locale));
}
// check for expiration date
if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberExpired", UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale));
}
// check the amount to authorize against the available balance of fin account, which includes active authorizations as well as transactions
BigDecimal availableBalance = finAccount.getBigDecimal("availableBalance");
Boolean processResult = null;
String refNum = null;
Map<String, Object> result = ServiceUtil.returnSuccess();
// make sure to round and scale it to the same as availableBalance
amount = amount.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding);
// if availableBalance equal to or greater than amount, then auth
if (UtilValidate.isNotEmpty(availableBalance) && availableBalance.compareTo(amount) >= 0) {
Timestamp thruDate = null;
if (giftCertSettings.getLong("authValidDays") != null) {
thruDate = UtilDateTime.getDayEnd(UtilDateTime.nowTimestamp(), giftCertSettings.getLong("authValidDays"));
}
Map<String, Object> tmpResult = dispatcher.runSync("createFinAccountAuth", UtilMisc.<String, Object>toMap("finAccountId", finAccountId, "amount", amount, "currencyUomId", currency, "thruDate", thruDate, "userLogin", userLogin));
if (ServiceUtil.isError(tmpResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(tmpResult));
} else {
refNum = (String) tmpResult.get("finAccountAuthId");
processResult = Boolean.TRUE;
}
} else {
Debug.logError("Attempted to authorize [" + amount + "] against a balance of only [" + availableBalance + "]", module);
// a refNum is always required from authorization
refNum = "N/A";
processResult = Boolean.FALSE;
}
result.put("processAmount", amount);
result.put("authResult", processResult);
result.put("processAmount", amount);
result.put("authFlag", "2");
result.put("authCode", "A");
result.put("captureCode", "C");
result.put("authRefNum", refNum);
return result;
} catch (GenericEntityException | GenericServiceException ex) {
Debug.logError(ex, "Cannot authorize gift certificate", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingGiftCerticateNumberCannotAuthorize", UtilMisc.toMap("errorString", ex.getMessage()), locale));
}
}
use of org.apache.ofbiz.order.order.OrderReadHelper in project ofbiz-framework by apache.
the class PaymentGatewayServices method captureOrderPayments.
/**
* Captures payments through service calls to the defined processing service for the ProductStore/PaymentMethodType
* @return COMPLETE|FAILED|ERROR for complete processing of ALL payment methods.
*/
public static Map<String, Object> captureOrderPayments(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String orderId = (String) context.get("orderId");
String invoiceId = (String) context.get("invoiceId");
String billingAccountId = (String) context.get("billingAccountId");
BigDecimal amountToCapture = (BigDecimal) context.get("captureAmount");
Locale locale = (Locale) context.get("locale");
amountToCapture = amountToCapture.setScale(decimals, rounding);
// get the order header and payment preferences
GenericValue orderHeader = null;
List<GenericValue> paymentPrefs = null;
List<GenericValue> paymentPrefsBa = null;
try {
orderHeader = EntityQuery.use(delegator).from("OrderHeader").where("orderId", orderId).queryOne();
// get the payment prefs
paymentPrefs = EntityQuery.use(delegator).from("OrderPaymentPreference").where("orderId", orderId, "statusId", "PAYMENT_AUTHORIZED").orderBy("-maxAmount").queryList();
if (UtilValidate.isNotEmpty(billingAccountId)) {
paymentPrefsBa = EntityQuery.use(delegator).from("OrderPaymentPreference").where("orderId", orderId, "paymentMethodTypeId", "EXT_BILLACT", "statusId", "PAYMENT_NOT_RECEIVED").orderBy("-maxAmount").queryList();
}
} catch (GenericEntityException gee) {
Debug.logError(gee, "Problems getting entity record(s), see stack trace", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrder, "OrderOrderNotFound", UtilMisc.toMap("orderId", orderId), locale) + " " + gee.toString());
}
// error if no order was found
if (orderHeader == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrder, "OrderOrderNotFound", UtilMisc.toMap("orderId", orderId), locale));
}
// Check if the outstanding amount for the order is greater than the
// amount that we are going to capture.
OrderReadHelper orh = new OrderReadHelper(orderHeader);
BigDecimal orderGrandTotal = orh.getOrderGrandTotal();
orderGrandTotal = orderGrandTotal.setScale(decimals, rounding);
BigDecimal totalPayments = PaymentWorker.getPaymentsTotal(orh.getOrderPayments());
totalPayments = totalPayments.setScale(decimals, rounding);
BigDecimal remainingTotal = orderGrandTotal.subtract(totalPayments);
if (Debug.infoOn()) {
Debug.logInfo("The Remaining Total for order: " + orderId + " is: " + remainingTotal, module);
}
// The amount to capture cannot be greater than the remaining total
amountToCapture = amountToCapture.min(remainingTotal);
if (Debug.infoOn()) {
Debug.logInfo("Actual Expected Capture Amount : " + amountToCapture, module);
}
// Process billing accounts payments
if (UtilValidate.isNotEmpty(paymentPrefsBa)) {
Iterator<GenericValue> paymentsBa = paymentPrefsBa.iterator();
while (paymentsBa.hasNext()) {
GenericValue paymentPref = paymentsBa.next();
BigDecimal authAmount = paymentPref.getBigDecimal("maxAmount");
if (authAmount == null) {
authAmount = ZERO;
}
authAmount = authAmount.setScale(decimals, rounding);
if (authAmount.compareTo(ZERO) == 0) {
// nothing to capture
Debug.logInfo("Nothing to capture; authAmount = 0", module);
continue;
}
// the amount for *this* capture
BigDecimal amountThisCapture = amountToCapture.min(authAmount);
// decrease amount of next payment preference to capture
amountToCapture = amountToCapture.subtract(amountThisCapture);
// to the billing account and we apply them to the invoice
if (UtilValidate.isNotEmpty(invoiceId)) {
Map<String, Object> captureResult = null;
try {
captureResult = dispatcher.runSync("captureBillingAccountPayments", UtilMisc.<String, Object>toMap("invoiceId", invoiceId, "billingAccountId", billingAccountId, "captureAmount", amountThisCapture, "orderId", orderId, "userLogin", userLogin));
if (ServiceUtil.isError(captureResult)) {
return ServiceUtil.returnError(ServiceUtil.getErrorMessage(captureResult));
}
} catch (GenericServiceException ex) {
return ServiceUtil.returnError(ex.getMessage());
}
if (captureResult != null) {
BigDecimal amountCaptured = (BigDecimal) captureResult.get("captureAmount");
if (Debug.infoOn()) {
Debug.logInfo("Amount captured for order [" + orderId + "] from unapplied payments associated to billing account [" + billingAccountId + "] is: " + amountCaptured, module);
}
amountCaptured = amountCaptured.setScale(decimals, rounding);
if (amountCaptured.compareTo(BigDecimal.ZERO) == 0) {
continue;
}
// add the invoiceId to the result for processing
captureResult.put("invoiceId", invoiceId);
captureResult.put("captureResult", Boolean.TRUE);
captureResult.put("orderPaymentPreference", paymentPref);
if (context.get("captureRefNum") == null) {
// FIXME: this is an hack to avoid a service validation error for processCaptureResult (captureRefNum is mandatory, but it is not used for billing accounts)
captureResult.put("captureRefNum", "");
}
// process the capture's results
try {
// the following method will set on the OrderPaymentPreference:
// maxAmount = amountCaptured and
// statusId = PAYMENT_RECEIVED
processResult(dctx, captureResult, userLogin, paymentPref, locale);
} catch (GeneralException e) {
Debug.logError(e, "Trouble processing the result; captureResult: " + captureResult, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrder, "AccountingPaymentCannotBeCaptured", locale) + " " + captureResult);
}
// create any splits which are needed
if (authAmount.compareTo(amountCaptured) > 0) {
BigDecimal splitAmount = authAmount.subtract(amountCaptured);
try {
Map<String, Object> splitCtx = UtilMisc.<String, Object>toMap("userLogin", userLogin, "orderPaymentPreference", paymentPref, "splitAmount", splitAmount);
dispatcher.addCommitService("processCaptureSplitPayment", splitCtx, true);
} catch (GenericServiceException e) {
Debug.logWarning(e, "Problem processing the capture split payment", module);
}
if (Debug.infoOn()) {
Debug.logInfo("Captured: " + amountThisCapture + " Remaining (re-auth): " + splitAmount, module);
}
}
} else {
Debug.logError("Payment not captured for order [" + orderId + "] from billing account [" + billingAccountId + "]", module);
}
}
}
}
// iterate over the prefs and capture each one until we meet our total
if (UtilValidate.isNotEmpty(paymentPrefs)) {
Iterator<GenericValue> payments = paymentPrefs.iterator();
while (payments.hasNext()) {
// DEJ20060708: Do we really want to just log and ignore the errors like this? I've improved a few of these in a review today, but it is being done all over...
GenericValue paymentPref = payments.next();
GenericValue authTrans = getAuthTransaction(paymentPref);
if (authTrans == null) {
Debug.logWarning("Authorized OrderPaymentPreference has no corresponding PaymentGatewayResponse, cannot capture payment: " + paymentPref, module);
continue;
}
// check for an existing capture
GenericValue captureTrans = getCaptureTransaction(paymentPref);
if (captureTrans != null) {
Debug.logWarning("Attempt to capture and already captured preference: " + captureTrans, module);
continue;
}
BigDecimal authAmount = authTrans.getBigDecimal("amount");
if (authAmount == null) {
authAmount = ZERO;
}
authAmount = authAmount.setScale(decimals, rounding);
if (authAmount.compareTo(ZERO) == 0) {
// nothing to capture
Debug.logInfo("Nothing to capture; authAmount = 0", module);
continue;
}
// the amount for *this* capture
BigDecimal amountThisCapture;
// determine how much for *this* capture
if (isReplacementOrder(orderHeader)) {
// if it is a replacement order then just capture the auth amount
amountThisCapture = authAmount;
} else if (authAmount.compareTo(amountToCapture) >= 0) {
// if the auth amount is more then expected capture just capture what is expected
amountThisCapture = amountToCapture;
} else if (payments.hasNext()) {
// if we have more payments to capture; just capture what was authorized
amountThisCapture = authAmount;
} else {
// we need to capture more then what was authorized; re-auth for the new amount
// TODO: add what the billing account cannot support to the re-auth amount
// TODO: add support for re-auth for additional funds
// just in case; we will capture the authorized amount here; until this is implemented
Debug.logError("The amount to capture was more then what was authorized; we only captured the authorized amount : " + paymentPref, module);
amountThisCapture = authAmount;
}
Map<String, Object> captureResult = capturePayment(dctx, userLogin, orh, paymentPref, amountThisCapture, locale);
if (captureResult != null && ServiceUtil.isSuccess(captureResult)) {
// credit card processors return captureAmount, but gift certificate processors return processAmount
BigDecimal amountCaptured = (BigDecimal) captureResult.get("captureAmount");
if (amountCaptured == null) {
amountCaptured = (BigDecimal) captureResult.get("processAmount");
}
amountCaptured = amountCaptured.setScale(decimals, rounding);
// decrease amount of next payment preference to capture
amountToCapture = amountToCapture.subtract(amountCaptured);
// add the invoiceId to the result for processing, not for a replacement order
if (!isReplacementOrder(orderHeader)) {
captureResult.put("invoiceId", invoiceId);
}
// process the capture's results
try {
processResult(dctx, captureResult, userLogin, paymentPref, locale);
} catch (GeneralException e) {
Debug.logError(e, "Trouble processing the result; captureResult: " + captureResult, module);
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrder, "AccountingPaymentCannotBeCaptured", locale) + " " + captureResult);
}
// create any splits which are needed
if (authAmount.compareTo(amountCaptured) > 0) {
BigDecimal splitAmount = authAmount.subtract(amountCaptured);
try {
Map<String, Object> splitCtx = UtilMisc.<String, Object>toMap("userLogin", userLogin, "orderPaymentPreference", paymentPref, "splitAmount", splitAmount);
dispatcher.addCommitService("processCaptureSplitPayment", splitCtx, true);
} catch (GenericServiceException e) {
Debug.logWarning(e, "Problem processing the capture split payment", module);
}
if (Debug.infoOn()) {
Debug.logInfo("Captured: " + amountThisCapture + " Remaining (re-auth): " + splitAmount, module);
}
}
} else {
Debug.logError("Payment not captured", module);
}
}
}
if (amountToCapture.compareTo(ZERO) > 0) {
GenericValue productStore = orh.getProductStore();
if (UtilValidate.isNotEmpty(productStore)) {
boolean shipIfCaptureFails = UtilValidate.isEmpty(productStore.get("shipIfCaptureFails")) || "Y".equalsIgnoreCase(productStore.getString("shipIfCaptureFails"));
if (!shipIfCaptureFails) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceOrder, "AccountingPaymentCannotBeCaptured", locale));
} else {
Debug.logWarning("Payment capture failed, shipping order anyway as per ProductStore setting (shipIfCaptureFails)", module);
}
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("processResult", "FAILED");
return result;
} else {
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("processResult", "COMPLETE");
return result;
}
}
Aggregations