/** * Method to return the amount applied converted to the currency of payment * * @param paymentApplicationId the payment application id * @return appliedAmount the applied amount as BigDecimal */ public static BigDecimal getPaymentAppliedAmount( Delegator delegator, String paymentApplicationId) { GenericValue paymentApplication = null; BigDecimal appliedAmount = BigDecimal.ZERO; try { paymentApplication = delegator.findByPrimaryKey( "PaymentApplication", UtilMisc.toMap("paymentApplicationId", paymentApplicationId)); appliedAmount = paymentApplication.getBigDecimal("amountApplied"); if (paymentApplication.get("paymentId") != null) { GenericValue payment = paymentApplication.getRelatedOne("Payment"); if (paymentApplication.get("invoiceId") != null && payment.get("actualCurrencyAmount") != null && payment.get("actualCurrencyUomId") != null) { GenericValue invoice = paymentApplication.getRelatedOne("Invoice"); if (payment.getString("actualCurrencyUomId").equals(invoice.getString("currencyUomId"))) { appliedAmount = appliedAmount .multiply(payment.getBigDecimal("amount")) .divide(payment.getBigDecimal("actualCurrencyAmount"), new MathContext(100)); } } } } catch (GenericEntityException e) { Debug.logError(e, "Problem getting Payment", module); } return appliedAmount; }
/* * Returns the value of a given ShipmentPackageContent record. Calculated by working out the total value (from the OrderItems) of all ItemIssuances * for the ShipmentItem then dividing that by the total quantity issued for the same to get an average item value then multiplying that by the package * content quantity. * Note: No rounding of the calculation is performed so you will need to round it to the accuracy that you require */ public static BigDecimal getShipmentPackageContentValue(GenericValue shipmentPackageContent) { BigDecimal quantity = shipmentPackageContent.getBigDecimal("quantity"); BigDecimal value = new BigDecimal("0"); // lookup the issuance to find the order List<GenericValue> issuances = null; try { GenericValue shipmentItem = shipmentPackageContent.getRelatedOne("ShipmentItem"); issuances = shipmentItem.getRelated("ItemIssuance"); } catch (GenericEntityException e) { Debug.logError(e, module); } BigDecimal totalIssued = BigDecimal.ZERO; BigDecimal totalValue = BigDecimal.ZERO; if (UtilValidate.isNotEmpty(issuances)) { for (GenericValue issuance : issuances) { // we only need one BigDecimal issuanceQuantity = issuance.getBigDecimal("quantity"); BigDecimal issuanceCancelQuantity = issuance.getBigDecimal("cancelQuantity"); if (issuanceCancelQuantity != null) { issuanceQuantity = issuanceQuantity.subtract(issuanceCancelQuantity); } // get the order item GenericValue orderItem = null; try { orderItem = issuance.getRelatedOne("OrderItem"); } catch (GenericEntityException e) { Debug.logError(e, module); } if (orderItem != null) { // get the value per unit - (base price * amount) BigDecimal selectedAmount = orderItem.getBigDecimal("selectedAmount"); if (selectedAmount == null || selectedAmount.compareTo(BigDecimal.ZERO) <= 0) { selectedAmount = BigDecimal.ONE; } BigDecimal unitPrice = orderItem.getBigDecimal("unitPrice"); BigDecimal itemValue = unitPrice.multiply(selectedAmount); // total value for package (per unit * quantity) totalIssued = totalIssued.add(issuanceQuantity); totalValue = totalValue.add(itemValue.multiply(issuanceQuantity)); } } } // take the average value of the issuances and multiply it by the shipment package content // quantity value = totalValue.divide(totalIssued, 10, BigDecimal.ROUND_HALF_EVEN).multiply(quantity); return value; }
public static BigDecimal getPaymentNotApplied(GenericValue payment, Boolean actual) { if (actual.equals(Boolean.TRUE) && UtilValidate.isNotEmpty(payment.getBigDecimal("actualCurrencyAmount"))) { return payment .getBigDecimal("actualCurrencyAmount") .subtract(getPaymentApplied(payment, actual)) .setScale(decimals, rounding); } return payment .getBigDecimal("amount") .subtract(getPaymentApplied(payment)) .setScale(decimals, rounding); }
/** * Method to return the total amount of a payment which is applied to a payment * * @param payment GenericValue object of the Payment * @param actual false for currency of the payment, true for the actual currency * @return the applied total as BigDecimal in the currency of the payment */ public static BigDecimal getPaymentApplied(GenericValue payment, Boolean actual) { BigDecimal paymentApplied = BigDecimal.ZERO; List<GenericValue> paymentApplications = null; try { List<EntityExpr> cond = UtilMisc.toList( EntityCondition.makeCondition( "paymentId", EntityOperator.EQUALS, payment.getString("paymentId")), EntityCondition.makeCondition( "toPaymentId", EntityOperator.EQUALS, payment.getString("paymentId"))); EntityCondition partyCond = EntityCondition.makeCondition(cond, EntityOperator.OR); paymentApplications = payment .getDelegator() .findList( "PaymentApplication", partyCond, null, UtilMisc.toList("invoiceId", "billingAccountId"), null, false); if (UtilValidate.isNotEmpty(paymentApplications)) { for (GenericValue paymentApplication : paymentApplications) { BigDecimal amountApplied = paymentApplication.getBigDecimal("amountApplied"); // check currency invoice and if different convert amount applied for display if (actual.equals(Boolean.FALSE) && paymentApplication.get("invoiceId") != null && payment.get("actualCurrencyAmount") != null && payment.get("actualCurrencyUomId") != null) { GenericValue invoice = paymentApplication.getRelatedOne("Invoice"); if (payment .getString("actualCurrencyUomId") .equals(invoice.getString("currencyUomId"))) { amountApplied = amountApplied .multiply(payment.getBigDecimal("amount")) .divide(payment.getBigDecimal("actualCurrencyAmount"), new MathContext(100)); } } paymentApplied = paymentApplied.add(amountApplied).setScale(decimals, rounding); } } } catch (GenericEntityException e) { Debug.logError(e, "Trouble getting entities", module); } return paymentApplied; }
public static BigDecimal getPaymentNotApplied(GenericValue payment) { if (payment != null) { return payment .getBigDecimal("amount") .subtract(getPaymentApplied(payment)) .setScale(decimals, rounding); } return BigDecimal.ZERO; }
/** * Returns the total from a list of Payment entities * * @param payments List of Payment GenericValue items * @return total payments as BigDecimal */ public static BigDecimal getPaymentsTotal(List<GenericValue> payments) { if (payments == null) { throw new IllegalArgumentException("Payment list cannot be null"); } BigDecimal paymentsTotal = BigDecimal.ZERO; for (GenericValue payment : payments) { paymentsTotal = paymentsTotal.add(payment.getBigDecimal("amount")).setScale(decimals, rounding); } return paymentsTotal; }
public static Map<String, Object> finAccountReleaseAuth( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); GenericValue userLogin = (GenericValue) context.get("userLogin"); GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference"); Locale locale = (Locale) context.get("locale"); String err = UtilProperties.getMessage(resourceError, "AccountingFinAccountCannotBeExpired", locale); try { // expire the related financial authorization transaction GenericValue authTransaction = PaymentGatewayServices.getAuthTransaction(paymentPref); if (authTransaction == null) { return ServiceUtil.returnError( err + UtilProperties.getMessage( resourceError, "AccountingFinAccountCannotFindAuthorization", locale)); } Map<String, Object> input = UtilMisc.toMap( "userLogin", userLogin, "finAccountAuthId", authTransaction.get("referenceNum")); Map<String, Object> serviceResults = dispatcher.runSync("expireFinAccountAuth", input); Map<String, Object> result = ServiceUtil.returnSuccess(); result.put("releaseRefNum", authTransaction.getString("referenceNum")); result.put("releaseAmount", authTransaction.getBigDecimal("amount")); result.put("releaseResult", Boolean.TRUE); // if there's an error, don't release if (ServiceUtil.isError(serviceResults)) { return ServiceUtil.returnError(err + ServiceUtil.getErrorMessage(serviceResults)); } return result; } catch (GenericServiceException e) { Debug.logError(e, e.getMessage(), module); return ServiceUtil.returnError(err + e.getMessage()); } }
public static BigDecimal getPaymentNotApplied( Delegator delegator, String paymentId, Boolean actual) { if (delegator == null) { throw new IllegalArgumentException("Null delegator is not allowed in this method"); } GenericValue payment = null; try { payment = delegator.findByPrimaryKey("Payment", UtilMisc.toMap("paymentId", paymentId)); } catch (GenericEntityException e) { Debug.logError(e, "Problem getting Payment", module); } if (payment == null) { throw new IllegalArgumentException("The paymentId passed does not match an existing payment"); } return payment .getBigDecimal("amount") .subtract(getPaymentApplied(delegator, paymentId, actual)) .setScale(decimals, rounding); }
public static String addListToCart( Delegator delegator, LocalDispatcher dispatcher, ShoppingCart cart, String prodCatalogId, String shoppingListId, boolean includeChild, boolean setAsListItem, boolean append) throws java.lang.IllegalArgumentException { String errMsg = null; // no list; no add if (shoppingListId == null) { errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.choose_shopping_list", cart.getLocale()); throw new IllegalArgumentException(errMsg); } // get the shopping list GenericValue shoppingList = null; List<GenericValue> shoppingListItems = null; try { shoppingList = EntityQuery.use(delegator) .from("ShoppingList") .where("shoppingListId", shoppingListId) .queryOne(); if (shoppingList == null) { errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.error_getting_shopping_list_and_items", cart.getLocale()); throw new IllegalArgumentException(errMsg); } shoppingListItems = shoppingList.getRelated("ShoppingListItem", null, null, false); if (shoppingListItems == null) { shoppingListItems = FastList.newInstance(); } // include all items of child lists if flagged to do so if (includeChild) { List<GenericValue> childShoppingLists = shoppingList.getRelated("ChildShoppingList", null, null, false); for (GenericValue v : childShoppingLists) { List<GenericValue> items = v.getRelated("ShoppingListItem", null, null, false); shoppingListItems.addAll(items); } } } catch (GenericEntityException e) { Debug.logError(e, "Problems getting ShoppingList and ShoppingListItem records", module); errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.error_getting_shopping_list_and_items", cart.getLocale()); throw new IllegalArgumentException(errMsg); } // no items; not an error; just mention that nothing was added if (UtilValidate.isEmpty(shoppingListItems)) { errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.no_items_added", cart.getLocale()); return errMsg; } // check if we are to clear the cart first if (!append) { cart.clear(); // Prevent the system from creating a new shopping list every time the cart is restored for // anonymous user. cart.setAutoSaveListId(shoppingListId); } // get the survey info for all the items Map<String, List<String>> shoppingListSurveyInfo = getItemSurveyInfos(shoppingListItems); // add the items StringBuilder eventMessage = new StringBuilder(); for (GenericValue shoppingListItem : shoppingListItems) { String productId = shoppingListItem.getString("productId"); BigDecimal quantity = shoppingListItem.getBigDecimal("quantity"); Timestamp reservStart = shoppingListItem.getTimestamp("reservStart"); BigDecimal reservLength = shoppingListItem.getBigDecimal("reservLength"); BigDecimal reservPersons = shoppingListItem.getBigDecimal("reservPersons"); // String accommodationMapId = shoppingListItem.getString("accommodationMapId"); // String accommodationSpotId = shoppingListItem.getString("accommodationSpotId"); String configId = shoppingListItem.getString("configId"); try { String listId = shoppingListItem.getString("shoppingListId"); String itemId = shoppingListItem.getString("shoppingListItemSeqId"); Map<String, Object> attributes = FastMap.newInstance(); // list items are noted in the shopping cart if (setAsListItem) { attributes.put("shoppingListId", listId); attributes.put("shoppingListItemSeqId", itemId); } // check if we have existing survey responses to append if (shoppingListSurveyInfo.containsKey(listId + "." + itemId) && UtilValidate.isNotEmpty(shoppingListSurveyInfo.get(listId + "." + itemId))) { attributes.put("surveyResponses", shoppingListSurveyInfo.get(listId + "." + itemId)); } ProductConfigWrapper configWrapper = null; if (UtilValidate.isNotEmpty(configId)) { configWrapper = ProductConfigWorker.loadProductConfigWrapper( delegator, dispatcher, configId, productId, cart.getProductStoreId(), prodCatalogId, cart.getWebSiteId(), cart.getCurrency(), cart.getLocale(), cart.getAutoUserLogin()); } // TODO: add code to check for survey response requirement // i cannot get the addOrDecrease function to accept a null reservStart field: i get a null // pointer exception a null constant works.... if (reservStart == null) { cart.addOrIncreaseItem( productId, null, quantity, null, null, null, null, null, null, attributes, prodCatalogId, configWrapper, null, null, null, dispatcher); } else { cart.addOrIncreaseItem( productId, null, quantity, reservStart, reservLength, reservPersons, null, null, null, null, null, attributes, prodCatalogId, configWrapper, null, null, null, dispatcher); } Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.added_product_to_cart", messageMap, cart.getLocale()); eventMessage.append(errMsg).append("\n"); } catch (CartItemModifyException e) { Debug.logWarning( e, UtilProperties.getMessage( resource_error, "OrderProblemsAddingItemFromListToCart", cart.getLocale())); Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.problem_adding_product_to_cart", messageMap, cart.getLocale()); eventMessage.append(errMsg).append("\n"); } catch (ItemNotFoundException e) { Debug.logWarning( e, UtilProperties.getMessage(resource_error, "OrderProductNotFound", cart.getLocale())); Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("productId", productId); errMsg = UtilProperties.getMessage( resource_error, "shoppinglistevents.problem_adding_product_to_cart", messageMap, cart.getLocale()); eventMessage.append(errMsg).append("\n"); } } if (eventMessage.length() > 0) { return eventMessage.toString(); } // all done return ""; // no message to return; will simply reply as success }
// Note we're not doing a lot of error checking here as this method is really only used // to confirm the order with PayPal, the subsequent authorizations will handle any errors // that may occur. public static Map<String, Object> doExpressCheckout( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); GenericValue userLogin = (GenericValue) context.get("userLogin"); GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference"); OrderReadHelper orh = new OrderReadHelper(delegator, paymentPref.getString("orderId")); Locale locale = (Locale) context.get("locale"); GenericValue payPalPaymentSetting = getPaymentMethodGatewayPayPal(dctx, context, null); GenericValue payPalPaymentMethod = null; try { payPalPaymentMethod = paymentPref.getRelatedOne("PaymentMethod", false); payPalPaymentMethod = payPalPaymentMethod.getRelatedOne("PayPalPaymentMethod", false); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } BigDecimal processAmount = paymentPref.getBigDecimal("maxAmount"); NVPEncoder encoder = new NVPEncoder(); encoder.add("METHOD", "DoExpressCheckoutPayment"); encoder.add("TOKEN", payPalPaymentMethod.getString("expressCheckoutToken")); encoder.add("PAYMENTACTION", "Order"); encoder.add("PAYERID", payPalPaymentMethod.getString("payerId")); // set the amount encoder.add("AMT", processAmount.setScale(2).toPlainString()); encoder.add("CURRENCYCODE", orh.getCurrency()); BigDecimal grandTotal = orh.getOrderGrandTotal(); BigDecimal shippingTotal = orh.getShippingTotal().setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal taxTotal = orh.getTaxTotal().setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal subTotal = grandTotal.subtract(shippingTotal).subtract(taxTotal).setScale(2, BigDecimal.ROUND_HALF_UP); encoder.add("ITEMAMT", subTotal.toPlainString()); encoder.add("SHIPPINGAMT", shippingTotal.toPlainString()); encoder.add("TAXAMT", taxTotal.toPlainString()); NVPDecoder decoder = null; try { decoder = sendNVPRequest(payPalPaymentSetting, encoder); } catch (PayPalException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (decoder == null) { return ServiceUtil.returnError( UtilProperties.getMessage(resource, "AccountingPayPalUnknownError", locale)); } Map<String, String> errorMessages = getErrorMessageMap(decoder); if (UtilValidate.isNotEmpty(errorMessages)) { if (errorMessages.containsKey("10417")) { // "The transaction cannot complete successfully, Instruct the customer to use an // alternative payment method" // I've only encountered this once and there's no indication of the cause so the temporary // solution is to try again boolean retry = context.get("_RETRY_") == null || (Boolean) context.get("_RETRY_"); if (retry) { context.put("_RETRY_", false); return PayPalServices.doExpressCheckout(dctx, context); } } return ServiceUtil.returnError(UtilMisc.toList(errorMessages.values())); } Map<String, Object> inMap = FastMap.newInstance(); inMap.put("userLogin", userLogin); inMap.put("paymentMethodId", payPalPaymentMethod.get("paymentMethodId")); inMap.put("transactionId", decoder.get("TRANSACTIONID")); Map<String, Object> outMap = null; try { outMap = dispatcher.runSync("updatePayPalPaymentMethod", inMap); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (ServiceUtil.isError(outMap)) { Debug.logError(ServiceUtil.getErrorMessage(outMap), module); return outMap; } return ServiceUtil.returnSuccess(); }
// auto-replenish service (deposit) public static Map<String, Object> finAccountReplenish( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); Locale locale = (Locale) context.get("locale"); GenericValue userLogin = (GenericValue) context.get("userLogin"); String productStoreId = (String) context.get("productStoreId"); String finAccountId = (String) context.get("finAccountId"); // lookup the FinAccount GenericValue finAccount; try { finAccount = EntityQuery.use(delegator) .from("FinAccount") .where("finAccountId", finAccountId) .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (finAccount == null) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", finAccountId), locale)); } String currency = finAccount.getString("currencyUomId"); String statusId = finAccount.getString("statusId"); // look up the type -- determine auto-replenish is active GenericValue finAccountType; try { finAccountType = finAccount.getRelatedOne("FinAccountType", false); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } String replenishEnumId = finAccountType.getString("replenishEnumId"); if (!"FARP_AUTOMATIC".equals(replenishEnumId)) { // type does not support auto-replenish return ServiceUtil.returnSuccess(); } // attempt to lookup the product store from a previous deposit if (productStoreId == null) { productStoreId = getLastProductStoreId(delegator, finAccountId); if (productStoreId == null) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountCannotBeReplenish", locale)); } } // get the product store settings GenericValue finAccountSettings; Map<String, Object> psfasFindMap = UtilMisc.<String, Object>toMap( "productStoreId", productStoreId, "finAccountTypeId", finAccount.getString("finAccountTypeId")); try { finAccountSettings = EntityQuery.use(delegator) .from("ProductStoreFinActSetting") .where(psfasFindMap) .cache() .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (finAccountSettings == null) { Debug.logWarning( "finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because no ProductStoreFinActSetting record found for: " + psfasFindMap, module); // no settings; don't replenish return ServiceUtil.returnSuccess(); } BigDecimal replenishThreshold = finAccountSettings.getBigDecimal("replenishThreshold"); if (replenishThreshold == null) { Debug.logWarning( "finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because ProductStoreFinActSetting.replenishThreshold field was null for: " + psfasFindMap, module); return ServiceUtil.returnSuccess(); } BigDecimal replenishLevel = finAccount.getBigDecimal("replenishLevel"); if (replenishLevel == null || replenishLevel.compareTo(BigDecimal.ZERO) == 0) { Debug.logWarning( "finAccountReplenish Warning: not replenishing FinAccount [" + finAccountId + "] because FinAccount.replenishLevel field was null or 0", module); // no replenish level set; this account goes not support auto-replenish return ServiceUtil.returnSuccess(); } // get the current balance BigDecimal balance = finAccount.getBigDecimal("actualBalance"); // see if we are within the threshold for replenishment if (balance.compareTo(replenishThreshold) > -1) { Debug.logInfo( "finAccountReplenish Info: Not replenishing FinAccount [" + finAccountId + "] because balance [" + balance + "] is greater than the replenishThreshold [" + replenishThreshold + "]", module); // not ready return ServiceUtil.returnSuccess(); } // configure rollback service to set status to Negative Pending Replenishment if ("FNACT_NEGPENDREPL".equals(statusId)) { try { Map<String, Object> rollbackCtx = UtilMisc.toMap( "userLogin", userLogin, "finAccountId", finAccountId, "statusId", "FNACT_NEGPENDREPL"); dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } String replenishMethod = finAccountSettings.getString("replenishMethodEnumId"); BigDecimal depositAmount; if (replenishMethod == null || "FARP_TOP_OFF".equals(replenishMethod)) { // the deposit is level - balance (500 - (-10) = 510 || 500 - (10) = 490) depositAmount = replenishLevel.subtract(balance); } else if ("FARP_REPLENISH_LEVEL".equals(replenishMethod)) { // the deposit is replenish-level itself depositAmount = replenishLevel; } else { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountUnknownReplenishMethod", locale)); } // get the owner party String ownerPartyId = finAccount.getString("ownerPartyId"); if (ownerPartyId == null) { // no owner cannot replenish; (not fatal, just not supported by this account) Debug.logWarning( "finAccountReplenish Warning: No owner attached to financial account [" + finAccountId + "] cannot auto-replenish", module); return ServiceUtil.returnSuccess(); } // get the payment method to use to replenish String paymentMethodId = finAccount.getString("replenishPaymentId"); if (paymentMethodId == null) { Debug.logWarning( "finAccountReplenish Warning: No payment method (replenishPaymentId) attached to financial account [" + finAccountId + "] cannot auto-replenish", module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount", locale)); } GenericValue paymentMethod; try { paymentMethod = EntityQuery.use(delegator) .from("PaymentMethod") .where("paymentMethodId", paymentMethodId) .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (paymentMethod == null) { // no payment methods on file; cannot replenish Debug.logWarning( "finAccountReplenish Warning: No payment method found for ID [" + paymentMethodId + "] for party [" + ownerPartyId + "] cannot auto-replenish", module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNoPaymentMethodAssociatedWithReplenishAccount", locale)); } // hit the payment method for the amount to replenish Map<String, BigDecimal> orderItemMap = UtilMisc.toMap("Auto-Replenishment FA #" + finAccountId, depositAmount); Map<String, Object> replOrderCtx = new HashMap<String, Object>(); replOrderCtx.put("productStoreId", productStoreId); replOrderCtx.put("paymentMethodId", paymentMethod.getString("paymentMethodId")); replOrderCtx.put("currency", currency); replOrderCtx.put("partyId", ownerPartyId); replOrderCtx.put("itemMap", orderItemMap); replOrderCtx.put("userLogin", userLogin); Map<String, Object> replResp; try { replResp = dispatcher.runSync("createSimpleNonProductSalesOrder", replOrderCtx); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } if (ServiceUtil.isError(replResp)) { return replResp; } String orderId = (String) replResp.get("orderId"); // create the deposit Map<String, Object> depositCtx = new HashMap<String, Object>(); depositCtx.put("productStoreId", productStoreId); depositCtx.put("finAccountId", finAccountId); depositCtx.put("currency", currency); depositCtx.put("partyId", ownerPartyId); depositCtx.put("orderId", orderId); depositCtx.put("orderItemSeqId", "00001"); // always one item on a replish order depositCtx.put("amount", depositAmount); depositCtx.put("reasonEnumId", "FATR_REPLENISH"); depositCtx.put("userLogin", userLogin); try { Map<String, Object> depositResp = dispatcher.runSync("finAccountDeposit", depositCtx); if (ServiceUtil.isError(depositResp)) { return depositResp; } } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } // say we are in good standing again if ("FNACT_NEGPENDREPL".equals(statusId)) { try { Map<String, Object> ufaResp = dispatcher.runSync( "updateFinAccount", UtilMisc.<String, Object>toMap( "finAccountId", finAccountId, "statusId", "FNACT_ACTIVE", "userLogin", userLogin)); if (ServiceUtil.isError(ufaResp)) { return ufaResp; } } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } return ServiceUtil.returnSuccess(); }
// base deposit service public static Map<String, Object> finAccountDeposit( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); Locale locale = (Locale) context.get("locale"); GenericValue userLogin = (GenericValue) context.get("userLogin"); String productStoreId = (String) context.get("productStoreId"); String finAccountId = (String) context.get("finAccountId"); String orderItemSeqId = (String) context.get("orderItemSeqId"); String reasonEnumId = (String) context.get("reasonEnumId"); String orderId = (String) context.get("orderId"); Boolean isRefund = (Boolean) context.get("isRefund"); BigDecimal amount = (BigDecimal) context.get("amount"); final String DEPOSIT = isRefund == null || !isRefund ? "DEPOSIT" : "ADJUSTMENT"; String partyId = (String) context.get("partyId"); if (UtilValidate.isEmpty(partyId)) { partyId = "_NA_"; } String currencyUom = (String) context.get("currency"); if (UtilValidate.isEmpty(currencyUom)) { currencyUom = EntityUtilProperties.getPropertyValue( "general.properties", "currency.uom.id.default", "USD", delegator); } GenericValue finAccount; try { finAccount = EntityQuery.use(delegator) .from("FinAccount") .where("finAccountId", finAccountId) .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", finAccountId), locale)); } // verify we have a financial account if (finAccount == null) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale)); } // make sure the fin account itself has not expired if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountExpired", UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale)); } Debug.logInfo("Deposit into financial account #" + finAccountId + " [" + amount + "]", module); // get the previous balance BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance"); if (previousBalance == null) { previousBalance = FinAccountHelper.ZERO; } // create the transaction BigDecimal actualBalance; String refNum; try { refNum = FinAccountPaymentServices.createFinAcctPaymentTransaction( delegator, dispatcher, userLogin, amount, productStoreId, partyId, orderId, orderItemSeqId, currencyUom, DEPOSIT, finAccountId, reasonEnumId); finAccount.refresh(); actualBalance = finAccount.getBigDecimal("actualBalance"); } catch (GeneralException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } // make sure balance is not null if (actualBalance == null) { actualBalance = FinAccountHelper.ZERO; } else { if (actualBalance.compareTo(BigDecimal.ZERO) < 0) { // balance went below zero, set negative pending replenishment status so that no more auths // or captures will go through until it is replenished try { Map<String, Object> rollbackCtx = UtilMisc.toMap( "userLogin", userLogin, "finAccountId", finAccountId, "statusId", "FNACT_NEGPENDREPL"); dispatcher.addRollbackService("updateFinAccount", rollbackCtx, true); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } } Map<String, Object> result = ServiceUtil.returnSuccess(); result.put("previousBalance", previousBalance); result.put("balance", actualBalance); result.put("amount", amount); result.put("processResult", Boolean.TRUE); result.put("referenceNum", refNum); return result; }
// base account transaction services public static Map<String, Object> finAccountWithdraw( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); Locale locale = (Locale) context.get("locale"); GenericValue userLogin = (GenericValue) context.get("userLogin"); String productStoreId = (String) context.get("productStoreId"); String finAccountId = (String) context.get("finAccountId"); String orderItemSeqId = (String) context.get("orderItemSeqId"); String reasonEnumId = (String) context.get("reasonEnumId"); String orderId = (String) context.get("orderId"); Boolean requireBalance = (Boolean) context.get("requireBalance"); BigDecimal amount = (BigDecimal) context.get("amount"); if (requireBalance == null) requireBalance = Boolean.TRUE; final String WITHDRAWAL = "WITHDRAWAL"; String partyId = (String) context.get("partyId"); if (UtilValidate.isEmpty(partyId)) { partyId = "_NA_"; } String currencyUom = (String) context.get("currency"); if (UtilValidate.isEmpty(currencyUom)) { currencyUom = EntityUtilProperties.getPropertyValue( "general.properties", "currency.uom.id.default", "USD", delegator); } // validate the amount if (amount.compareTo(BigDecimal.ZERO) < 0) { return ServiceUtil.returnError( UtilProperties.getMessage(resourceError, "AccountingFinAccountMustBePositive", locale)); } GenericValue finAccount; try { finAccount = EntityQuery.use(delegator) .from("FinAccount") .where("finAccountId", finAccountId) .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } // verify we have a financial account if (finAccount == null) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale)); } // make sure the fin account itself has not expired if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountExpired", UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale)); } // check the actual balance (excluding authorized amounts) and create the transaction if it is // sufficient BigDecimal previousBalance = finAccount.getBigDecimal("actualBalance"); if (previousBalance == null) { previousBalance = FinAccountHelper.ZERO; } BigDecimal balance; String refNum; Boolean procResult; if (requireBalance && previousBalance.compareTo(amount) < 0) { procResult = Boolean.FALSE; balance = previousBalance; refNum = "N/A"; } else { try { refNum = FinAccountPaymentServices.createFinAcctPaymentTransaction( delegator, dispatcher, userLogin, amount, productStoreId, partyId, orderId, orderItemSeqId, currencyUom, WITHDRAWAL, finAccountId, reasonEnumId); finAccount.refresh(); balance = finAccount.getBigDecimal("actualBalance"); procResult = Boolean.TRUE; } catch (GeneralException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } // make sure balance is not null if (balance == null) { balance = FinAccountHelper.ZERO; } Map<String, Object> result = ServiceUtil.returnSuccess(); result.put("previousBalance", previousBalance); result.put("balance", balance); result.put("amount", amount); result.put("processResult", procResult); result.put("referenceNum", refNum); return result; }
// base payment integration services public static Map<String, Object> finAccountPreAuth( DispatchContext dctx, Map<String, Object> context) { LocalDispatcher dispatcher = dctx.getDispatcher(); Delegator delegator = dctx.getDelegator(); GenericValue userLogin = (GenericValue) context.get("userLogin"); Locale locale = (Locale) context.get("locale"); GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference"); String finAccountCode = (String) context.get("finAccountCode"); String finAccountPin = (String) context.get("finAccountPin"); String finAccountId = (String) context.get("finAccountId"); String orderId = (String) context.get("orderId"); BigDecimal amount = (BigDecimal) context.get("processAmount"); // check for an existing auth trans and cancel it GenericValue authTrans = PaymentGatewayServices.getAuthTransaction(paymentPref); if (authTrans != null) { Map<String, Object> input = UtilMisc.toMap("userLogin", userLogin, "finAccountAuthId", authTrans.get("referenceNum")); try { dispatcher.runSync("expireFinAccountAuth", input); } catch (GenericServiceException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } if (finAccountId == null && paymentPref != null) { finAccountId = paymentPref.getString("finAccountId"); } // obtain the order information OrderReadHelper orh = new OrderReadHelper(delegator, orderId); // NOTE DEJ20070808: this means that we want store related settings for where the item is being // purchased, // NOT where the account was setup; should this be changed to use settings from the store where // the account was setup? String productStoreId = orh.getProductStoreId(); // TODO, NOTE DEJ20070808: why is this setup this way anyway? for the allowAuthToNegative // wouldn't that be better setup // on the FinAccount and not on the ProductStoreFinActSetting? maybe an override on the // FinAccount would be good... // get the financial account GenericValue finAccount; if (finAccountId != null) { try { finAccount = EntityQuery.use(delegator) .from("FinAccount") .where("finAccountId", finAccountId) .queryOne(); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(e.getMessage()); } } else { if (finAccountCode != null) { try { finAccount = FinAccountHelper.getFinAccountFromCode(finAccountCode, delegator); } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountCannotLocateItFromAccountCode", locale)); } } else { return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountIdAndFinAccountCodeAreNull", locale)); } } if (finAccount == null) { return ServiceUtil.returnError( UtilProperties.getMessage(resourceError, "AccountingFinAccountIdInvalid", locale)); } String finAccountTypeId = finAccount.getString("finAccountTypeId"); finAccountId = finAccount.getString("finAccountId"); String statusId = finAccount.getString("statusId"); try { // fin the store requires a pin number; validate the PIN with the code Map<String, Object> findProductStoreFinActSettingMap = UtilMisc.<String, Object>toMap( "productStoreId", productStoreId, "finAccountTypeId", finAccountTypeId); GenericValue finAccountSettings = EntityQuery.use(delegator) .from("ProductStoreFinActSetting") .where(findProductStoreFinActSettingMap) .cache() .queryOne(); if (finAccountSettings == null) { Debug.logWarning( "In finAccountPreAuth could not find ProductStoreFinActSetting record, values searched by: " + findProductStoreFinActSettingMap, module); } if (Debug.verboseOn()) Debug.logVerbose("In finAccountPreAuth finAccountSettings=" + finAccountSettings, module); BigDecimal minBalance = FinAccountHelper.ZERO; String allowAuthToNegative = "N"; if (finAccountSettings != null) { allowAuthToNegative = finAccountSettings.getString("allowAuthToNegative"); minBalance = finAccountSettings.getBigDecimal("minBalance"); if (minBalance == null) { minBalance = FinAccountHelper.ZERO; } // validate the PIN if the store requires it if ("Y".equals(finAccountSettings.getString("requirePinCode"))) { if (!FinAccountHelper.validatePin(delegator, finAccountCode, finAccountPin)) { Map<String, Object> result = ServiceUtil.returnSuccess(); result.put( "authMessage", UtilProperties.getMessage( resourceError, "AccountingFinAccountPinCodeCombinatorNotFound", locale)); result.put("authResult", Boolean.FALSE); result.put("processAmount", amount); result.put("authFlag", "0"); result.put("authCode", "A"); result.put("authRefNum", "0"); Debug.logWarning("Unable to auth FinAccount: " + result, module); return result; } } } // check for expiration date if ((finAccount.getTimestamp("thruDate") != null) && (finAccount.getTimestamp("thruDate").before(UtilDateTime.nowTimestamp()))) { Map<String, Object> result = ServiceUtil.returnSuccess(); result.put( "authMessage", UtilProperties.getMessage( resourceError, "AccountingFinAccountExpired", UtilMisc.toMap("thruDate", finAccount.getTimestamp("thruDate")), locale)); result.put("authResult", Boolean.FALSE); result.put("processAmount", amount); result.put("authFlag", "0"); result.put("authCode", "A"); result.put("authRefNum", "0"); Debug.logWarning("Unable to auth FinAccount: " + result, module); return result; } // check for account being in bad standing somehow if ("FNACT_NEGPENDREPL".equals(statusId) || "FNACT_MANFROZEN".equals(statusId) || "FNACT_CANCELLED".equals(statusId)) { // refresh the finaccount finAccount.refresh(); statusId = finAccount.getString("statusId"); if ("FNACT_NEGPENDREPL".equals(statusId) || "FNACT_MANFROZEN".equals(statusId) || "FNACT_CANCELLED".equals(statusId)) { Map<String, Object> result = ServiceUtil.returnSuccess(); if ("FNACT_NEGPENDREPL".equals(statusId)) { result.put( "authMessage", UtilProperties.getMessage(resourceError, "AccountingFinAccountNegative", locale)); } else if ("FNACT_MANFROZEN".equals(statusId)) { result.put( "authMessage", UtilProperties.getMessage(resourceError, "AccountingFinAccountFrozen", locale)); } else if ("FNACT_CANCELLED".equals(statusId)) { result.put( "authMessage", UtilProperties.getMessage(resourceError, "AccountingFinAccountCancelled", locale)); } result.put("authResult", Boolean.FALSE); result.put("processAmount", amount); result.put("authFlag", "0"); result.put("authCode", "A"); result.put("authRefNum", "0"); Debug.logWarning("Unable to auth FinAccount: " + result, module); return result; } } // 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"); if (availableBalance == null) { availableBalance = FinAccountHelper.ZERO; } else { BigDecimal availableBalanceOriginal = availableBalance; availableBalance = availableBalance.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding); if (availableBalance.compareTo(availableBalanceOriginal) != 0) { Debug.logWarning( "In finAccountPreAuth for finAccountId [" + finAccountId + "] availableBalance [" + availableBalanceOriginal + "] was different after rounding [" + availableBalance + "]; it should never have made it into the database this way, so check whatever put it there.", module); } } Map<String, Object> result = ServiceUtil.returnSuccess(); String authMessage = null; Boolean processResult; String refNum; // make sure to round and scale it to the same as availableBalance amount = amount.setScale(FinAccountHelper.decimals, FinAccountHelper.rounding); Debug.logInfo( "Allow auth to negative: " + allowAuthToNegative + " :: available: " + availableBalance + " comp: " + minBalance + " = " + availableBalance.compareTo(minBalance) + " :: req: " + amount, module); // check the available balance to see if we can auth this tx if (("Y".equals(allowAuthToNegative) && availableBalance.compareTo(minBalance) > -1) || (availableBalance.compareTo(amount) > -1)) { Timestamp thruDate; if (finAccountSettings != null && finAccountSettings.getLong("authValidDays") != null) { thruDate = UtilDateTime.getDayEnd( UtilDateTime.nowTimestamp(), finAccountSettings.getLong("authValidDays")); } else { thruDate = UtilDateTime.getDayEnd( UtilDateTime.nowTimestamp(), Long.valueOf(30)); // default 30 days for an auth } Map<String, Object> tmpResult = dispatcher.runSync( "createFinAccountAuth", UtilMisc.<String, Object>toMap( "finAccountId", finAccountId, "amount", amount, "thruDate", thruDate, "userLogin", userLogin)); if (ServiceUtil.isError(tmpResult)) { return tmpResult; } refNum = (String) tmpResult.get("finAccountAuthId"); processResult = Boolean.TRUE; // refresh the account finAccount.refresh(); } else { Debug.logWarning( "Attempted to authorize [" + amount + "] against a balance of only [" + availableBalance + "] for finAccountId [" + finAccountId + "]", module); refNum = "0"; // a refNum is always required from authorization authMessage = "Insufficient funds"; processResult = Boolean.FALSE; } result.put("processAmount", amount); result.put("authMessage", authMessage); result.put("authResult", processResult); result.put("processAmount", amount); result.put("authFlag", "1"); result.put("authCode", "A"); result.put("authRefNum", refNum); Debug.logInfo("FinAccont Auth: " + result, module); return result; } catch (GenericEntityException ex) { Debug.logError(ex, "Cannot authorize financial account", module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountCannotBeAuthorized", UtilMisc.toMap("errorString", ex.getMessage()), locale)); } catch (GenericServiceException ex) { Debug.logError(ex, "Cannot authorize financial account", module); return ServiceUtil.returnError( UtilProperties.getMessage( resourceError, "AccountingFinAccountCannotBeAuthorized", UtilMisc.toMap("errorString", ex.getMessage()), locale)); } }
public static Map<String, Object> rateProductTaxCalcForDisplay( DispatchContext dctx, Map<String, ? extends Object> context) { Delegator delegator = dctx.getDelegator(); String productStoreId = (String) context.get("productStoreId"); String billToPartyId = (String) context.get("billToPartyId"); String productId = (String) context.get("productId"); BigDecimal quantity = (BigDecimal) context.get("quantity"); BigDecimal basePrice = (BigDecimal) context.get("basePrice"); BigDecimal shippingPrice = (BigDecimal) context.get("shippingPrice"); Locale locale = (Locale) context.get("locale"); if (quantity == null) quantity = ONE_BASE; BigDecimal amount = basePrice.multiply(quantity); BigDecimal taxTotal = ZERO_BASE; BigDecimal taxPercentage = ZERO_BASE; BigDecimal priceWithTax = basePrice; if (shippingPrice != null) priceWithTax = priceWithTax.add(shippingPrice); try { GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId)); GenericValue productStore = delegator.findByPrimaryKeyCache( "ProductStore", UtilMisc.toMap("productStoreId", productStoreId)); if (productStore == null) { throw new IllegalArgumentException( "Could not find ProductStore with ID [" + productStoreId + "] for tax calculation"); } if ("Y".equals(productStore.getString("showPricesWithVatTax"))) { Set<GenericValue> taxAuthoritySet = FastSet.newInstance(); if (productStore.get("vatTaxAuthPartyId") == null) { List<GenericValue> taxAuthorityRawList = delegator.findList( "TaxAuthority", EntityCondition.makeCondition( "taxAuthGeoId", EntityOperator.EQUALS, productStore.get("vatTaxAuthGeoId")), null, null, null, true); taxAuthoritySet.addAll(taxAuthorityRawList); } else { GenericValue taxAuthority = delegator.findByPrimaryKeyCache( "TaxAuthority", UtilMisc.toMap( "taxAuthGeoId", productStore.get("vatTaxAuthGeoId"), "taxAuthPartyId", productStore.get("vatTaxAuthPartyId"))); taxAuthoritySet.add(taxAuthority); } if (taxAuthoritySet.size() == 0) { throw new IllegalArgumentException( "Could not find any Tax Authories for store with ID [" + productStoreId + "] for tax calculation; the store settings may need to be corrected."); } List<GenericValue> taxAdustmentList = getTaxAdjustments( delegator, product, productStore, null, billToPartyId, taxAuthoritySet, basePrice, quantity, amount, shippingPrice, ZERO_BASE); if (taxAdustmentList.size() == 0) { // this is something that happens every so often for different products and such, so don't // blow up on it... Debug.logWarning( "Could not find any Tax Authories Rate Rules for store with ID [" + productStoreId + "], productId [" + productId + "], basePrice [" + basePrice + "], amount [" + amount + "], for tax calculation; the store settings may need to be corrected.", module); } // add up amounts from adjustments (amount OR exemptAmount, sourcePercentage) for (GenericValue taxAdjustment : taxAdustmentList) { if ("SALES_TAX".equals(taxAdjustment.getString("orderAdjustmentTypeId"))) { taxPercentage = taxPercentage.add(taxAdjustment.getBigDecimal("sourcePercentage")); BigDecimal adjAmount = taxAdjustment.getBigDecimal("amount"); taxTotal = taxTotal.add(adjAmount); priceWithTax = priceWithTax.add( adjAmount.divide(quantity, salestaxCalcDecimals, salestaxRounding)); Debug.logInfo( "For productId [" + productId + "] added [" + adjAmount.divide(quantity, salestaxCalcDecimals, salestaxRounding) + "] of tax to price for geoId [" + taxAdjustment.getString("taxAuthGeoId") + "], new price is [" + priceWithTax + "]", module); } } } } catch (GenericEntityException e) { Debug.logError(e, "Data error getting tax settings: " + e.toString(), module); return ServiceUtil.returnError( UtilProperties.getMessage( resource, "AccountingTaxSettingError", UtilMisc.toMap("errorString", e.toString()), locale)); } // round to 2 decimal places for display/etc taxTotal = taxTotal.setScale(salestaxFinalDecimals, salestaxRounding); priceWithTax = priceWithTax.setScale(salestaxFinalDecimals, salestaxRounding); Map<String, Object> result = ServiceUtil.returnSuccess(); result.put("taxTotal", taxTotal); result.put("taxPercentage", taxPercentage); result.put("priceWithTax", priceWithTax); return result; }
private static List<GenericValue> getTaxAdjustments( Delegator delegator, GenericValue product, GenericValue productStore, String payToPartyId, String billToPartyId, Set<GenericValue> taxAuthoritySet, BigDecimal itemPrice, BigDecimal itemQuantity, BigDecimal itemAmount, BigDecimal shippingAmount, BigDecimal orderPromotionsAmount) { Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); List<GenericValue> adjustments = FastList.newInstance(); if (payToPartyId == null) { if (productStore != null) { payToPartyId = productStore.getString("payToPartyId"); } } // store expr EntityCondition storeCond = null; if (productStore != null) { storeCond = EntityCondition.makeCondition( EntityCondition.makeCondition( "productStoreId", EntityOperator.EQUALS, productStore.get("productStoreId")), EntityOperator.OR, EntityCondition.makeCondition("productStoreId", EntityOperator.EQUALS, null)); } else { storeCond = EntityCondition.makeCondition("productStoreId", EntityOperator.EQUALS, null); } // build the TaxAuthority expressions (taxAuthGeoId, taxAuthPartyId) List<EntityCondition> taxAuthCondOrList = FastList.newInstance(); // start with the _NA_ TaxAuthority... taxAuthCondOrList.add( EntityCondition.makeCondition( EntityCondition.makeCondition("taxAuthPartyId", EntityOperator.EQUALS, "_NA_"), EntityOperator.AND, EntityCondition.makeCondition("taxAuthGeoId", EntityOperator.EQUALS, "_NA_"))); for (GenericValue taxAuthority : taxAuthoritySet) { EntityCondition taxAuthCond = EntityCondition.makeCondition( EntityCondition.makeCondition( "taxAuthPartyId", EntityOperator.EQUALS, taxAuthority.getString("taxAuthPartyId")), EntityOperator.AND, EntityCondition.makeCondition( "taxAuthGeoId", EntityOperator.EQUALS, taxAuthority.getString("taxAuthGeoId"))); taxAuthCondOrList.add(taxAuthCond); } EntityCondition taxAuthoritiesCond = EntityCondition.makeCondition(taxAuthCondOrList, EntityOperator.OR); try { EntityCondition productCategoryCond = null; if (product != null) { // find the tax categories associated with the product and filter by those, with an IN // clause or some such // if this product is variant, find the virtual product id and consider also the categories // of the virtual // question: get all categories, or just a special type? for now let's do all categories... String virtualProductId = null; if ("Y".equals(product.getString("isVariant"))) { virtualProductId = ProductWorker.getVariantVirtualId(product); } Set<String> productCategoryIdSet = FastSet.newInstance(); EntityCondition productIdCond = null; if (virtualProductId != null) { productIdCond = EntityCondition.makeCondition( EntityCondition.makeCondition( "productId", EntityOperator.EQUALS, product.getString("productId")), EntityOperator.OR, EntityCondition.makeCondition( "productId", EntityOperator.EQUALS, virtualProductId)); } else { productIdCond = EntityCondition.makeCondition( "productId", EntityOperator.EQUALS, product.getString("productId")); } List<GenericValue> pcmList = delegator.findList( "ProductCategoryMember", productIdCond, UtilMisc.toSet("productCategoryId", "fromDate", "thruDate"), null, null, true); pcmList = EntityUtil.filterByDate(pcmList, true); for (GenericValue pcm : pcmList) { productCategoryIdSet.add(pcm.getString("productCategoryId")); } if (productCategoryIdSet.size() == 0) { productCategoryCond = EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null); } else { productCategoryCond = EntityCondition.makeCondition( EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition( "productCategoryId", EntityOperator.IN, productCategoryIdSet)); } } else { productCategoryCond = EntityCondition.makeCondition("productCategoryId", EntityOperator.EQUALS, null); } // FIXME handles shipping and promo tax. Simple solution, see // https://issues.apache.org/jira/browse/OFBIZ-4160 for a better one if (product == null && shippingAmount != null) { EntityCondition taxShippingCond = EntityCondition.makeCondition( EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition("taxShipping", EntityOperator.EQUALS, "Y")); if (productCategoryCond != null) { productCategoryCond = EntityCondition.makeCondition( productCategoryCond, EntityOperator.OR, taxShippingCond); } } if (product == null && orderPromotionsAmount != null) { EntityCondition taxOrderPromotionsCond = EntityCondition.makeCondition( EntityCondition.makeCondition("taxPromotions", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition("taxPromotions", EntityOperator.EQUALS, "Y")); if (productCategoryCond != null) { productCategoryCond = EntityCondition.makeCondition( productCategoryCond, EntityOperator.OR, taxOrderPromotionsCond); } } // build the main condition clause List<EntityCondition> mainExprs = UtilMisc.toList(storeCond, taxAuthoritiesCond, productCategoryCond); mainExprs.add( EntityCondition.makeCondition( EntityCondition.makeCondition("minItemPrice", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition( "minItemPrice", EntityOperator.LESS_THAN_EQUAL_TO, itemPrice))); mainExprs.add( EntityCondition.makeCondition( EntityCondition.makeCondition("minPurchase", EntityOperator.EQUALS, null), EntityOperator.OR, EntityCondition.makeCondition( "minPurchase", EntityOperator.LESS_THAN_EQUAL_TO, itemAmount))); EntityCondition mainCondition = EntityCondition.makeCondition(mainExprs, EntityOperator.AND); // create the orderby clause List<String> orderList = UtilMisc.<String>toList("minItemPrice", "minPurchase", "fromDate"); // finally ready... do the rate query List<GenericValue> lookupList = delegator.findList( "TaxAuthorityRateProduct", mainCondition, null, orderList, null, false); List<GenericValue> filteredList = EntityUtil.filterByDate(lookupList, true); if (filteredList.size() == 0) { Debug.logWarning( "In TaxAuthority Product Rate no records were found for condition:" + mainCondition.toString(), module); return adjustments; } // find the right entry(s) based on purchase amount for (GenericValue taxAuthorityRateProduct : filteredList) { BigDecimal taxRate = taxAuthorityRateProduct.get("taxPercentage") != null ? taxAuthorityRateProduct.getBigDecimal("taxPercentage") : ZERO_BASE; BigDecimal taxable = ZERO_BASE; if (product != null && (product.get("taxable") == null || (product.get("taxable") != null && product.getBoolean("taxable").booleanValue()))) { taxable = taxable.add(itemAmount); } if (shippingAmount != null && taxAuthorityRateProduct != null && (taxAuthorityRateProduct.get("taxShipping") == null || (taxAuthorityRateProduct.get("taxShipping") != null && taxAuthorityRateProduct.getBoolean("taxShipping").booleanValue()))) { taxable = taxable.add(shippingAmount); } if (orderPromotionsAmount != null && taxAuthorityRateProduct != null && (taxAuthorityRateProduct.get("taxPromotions") == null || (taxAuthorityRateProduct.get("taxPromotions") != null && taxAuthorityRateProduct.getBoolean("taxPromotions").booleanValue()))) { taxable = taxable.add(orderPromotionsAmount); } if (taxable.compareTo(BigDecimal.ZERO) == 0) { // this should make it less confusing if the taxable flag on the product is not Y/true, // and there is no shipping and such continue; } // taxRate is in percentage, so needs to be divided by 100 BigDecimal taxAmount = (taxable.multiply(taxRate)) .divide(PERCENT_SCALE, salestaxCalcDecimals, salestaxRounding); String taxAuthGeoId = taxAuthorityRateProduct.getString("taxAuthGeoId"); String taxAuthPartyId = taxAuthorityRateProduct.getString("taxAuthPartyId"); // get glAccountId from TaxAuthorityGlAccount entity using the payToPartyId as the // organizationPartyId GenericValue taxAuthorityGlAccount = delegator.findByPrimaryKey( "TaxAuthorityGlAccount", UtilMisc.toMap( "taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "organizationPartyId", payToPartyId)); String taxAuthGlAccountId = null; if (taxAuthorityGlAccount != null) { taxAuthGlAccountId = taxAuthorityGlAccount.getString("glAccountId"); } else { // TODO: what to do if no TaxAuthorityGlAccount found? Use some default, or is that done // elsewhere later on? } GenericValue productPrice = null; if (product != null && taxAuthPartyId != null && taxAuthGeoId != null) { // find a ProductPrice for the productId and taxAuth* valxues, and see if it has a // priceWithTax value Map<String, String> priceFindMap = UtilMisc.toMap( "productId", product.getString("productId"), "taxAuthPartyId", taxAuthPartyId, "taxAuthGeoId", taxAuthGeoId, "productPricePurposeId", "PURCHASE"); List<GenericValue> productPriceList = delegator.findByAnd("ProductPrice", priceFindMap, UtilMisc.toList("-fromDate")); productPriceList = EntityUtil.filterByDate(productPriceList, true); productPrice = (productPriceList != null && productPriceList.size() > 0) ? productPriceList.get(0) : null; // Debug.logInfo("=================== productId=" + product.getString("productId"), // module); // Debug.logInfo("=================== productPrice=" + productPrice, module); } GenericValue taxAdjValue = delegator.makeValue("OrderAdjustment"); if (productPrice != null && "Y".equals(productPrice.getString("taxInPrice"))) { // tax is in the price already, so we want the adjustment to be a VAT_TAX adjustment to be // subtracted instead of a SALES_TAX adjustment to be added taxAdjValue.set("orderAdjustmentTypeId", "VAT_TAX"); // the amount will be different because we want to figure out how much of the price was // tax, and not how much tax needs to be added // the formula is: taxAmount = priceWithTax - (priceWithTax/(1+taxPercentage/100)) BigDecimal taxAmountIncluded = itemAmount.subtract( itemAmount.divide( BigDecimal.ONE.add( taxRate.divide(PERCENT_SCALE, 4, BigDecimal.ROUND_HALF_UP)), 3, BigDecimal.ROUND_HALF_UP)); taxAdjValue.set("amountAlreadyIncluded", taxAmountIncluded); taxAdjValue.set("amount", BigDecimal.ZERO); } else { taxAdjValue.set("orderAdjustmentTypeId", "SALES_TAX"); taxAdjValue.set("amount", taxAmount); } taxAdjValue.set("sourcePercentage", taxRate); taxAdjValue.set( "taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId")); // the primary Geo should be the main jurisdiction that the tax is for, and the secondary // would just be to define a parent or wrapping jurisdiction of the primary taxAdjValue.set("primaryGeoId", taxAuthGeoId); taxAdjValue.set("comments", taxAuthorityRateProduct.getString("description")); if (taxAuthPartyId != null) taxAdjValue.set("taxAuthPartyId", taxAuthPartyId); if (taxAuthGlAccountId != null) taxAdjValue.set("overrideGlAccountId", taxAuthGlAccountId); if (taxAuthGeoId != null) taxAdjValue.set("taxAuthGeoId", taxAuthGeoId); // check to see if this party has a tax ID for this, and if the party is tax exempt in the // primary (most-local) jurisdiction if (UtilValidate.isNotEmpty(billToPartyId) && UtilValidate.isNotEmpty(taxAuthGeoId)) { // see if partyId is a member of any groups, if so honor their tax exemptions // look for PartyRelationship with partyRelationshipTypeId=GROUP_ROLLUP, the partyIdTo is // the group member, so the partyIdFrom is the groupPartyId Set<String> billToPartyIdSet = FastSet.newInstance(); billToPartyIdSet.add(billToPartyId); List<GenericValue> partyRelationshipList = EntityUtil.filterByDate( delegator.findByAndCache( "PartyRelationship", UtilMisc.toMap( "partyIdTo", billToPartyId, "partyRelationshipTypeId", "GROUP_ROLLUP")), true); for (GenericValue partyRelationship : partyRelationshipList) { billToPartyIdSet.add(partyRelationship.getString("partyIdFrom")); } handlePartyTaxExempt( taxAdjValue, billToPartyIdSet, taxAuthGeoId, taxAuthPartyId, taxAmount, nowTimestamp, delegator); } else { Debug.logInfo( "NOTE: A tax calculation was done without a billToPartyId or taxAuthGeoId, so no tax exemptions or tax IDs considered; billToPartyId=[" + billToPartyId + "] taxAuthGeoId=[" + taxAuthGeoId + "]", module); } adjustments.add(taxAdjValue); if (productPrice != null && itemQuantity != null && productPrice.getBigDecimal("priceWithTax") != null && !"Y".equals(productPrice.getString("taxInPrice"))) { BigDecimal priceWithTax = productPrice.getBigDecimal("priceWithTax"); BigDecimal price = productPrice.getBigDecimal("price"); BigDecimal baseSubtotal = price.multiply(itemQuantity); BigDecimal baseTaxAmount = (baseSubtotal.multiply(taxRate)) .divide(PERCENT_SCALE, salestaxCalcDecimals, salestaxRounding); // Debug.logInfo("=================== priceWithTax=" + priceWithTax, module); // Debug.logInfo("=================== enteredTotalPriceWithTax=" + // enteredTotalPriceWithTax, module); // Debug.logInfo("=================== calcedTotalPriceWithTax=" + calcedTotalPriceWithTax, // module); // tax is not already in price so we want to add it in, but this is a VAT situation so // adjust to make it as accurate as possible // for VAT taxes if the calculated total item price plus calculated taxes is different // from what would be // expected based on the original entered price with taxes (if the price was entered this // way), then create // an adjustment that corrects for the difference, and this correction will be effectively // subtracted from the // price and not from the tax (the tax is meant to be calculated based on Tax Authority // rules and so should // not be shorted) // TODO (don't think this is needed, but just to keep it in mind): get this to work with // multiple VAT tax authorities instead of just one (right now will get incorrect totals // if there are multiple taxes included in the price) // TODO add constraint to ProductPrice lookup by any productStoreGroupId associated with // the current productStore BigDecimal enteredTotalPriceWithTax = priceWithTax.multiply(itemQuantity); BigDecimal calcedTotalPriceWithTax = (baseSubtotal).add(baseTaxAmount); if (!enteredTotalPriceWithTax.equals(calcedTotalPriceWithTax)) { // if the calced amount is higher than the entered amount we want the value to be // negative // to get it down to match the entered amount // so, subtract the calced amount from the entered amount (ie: correction = entered - // calced) BigDecimal correctionAmount = enteredTotalPriceWithTax.subtract(calcedTotalPriceWithTax); // Debug.logInfo("=================== correctionAmount=" + correctionAmount, module); GenericValue correctionAdjValue = delegator.makeValue("OrderAdjustment"); correctionAdjValue.set( "taxAuthorityRateSeqId", taxAuthorityRateProduct.getString("taxAuthorityRateSeqId")); correctionAdjValue.set("amount", correctionAmount); // don't set this, causes a doubling of the tax rate because calling code adds up all // tax rates: correctionAdjValue.set("sourcePercentage", taxRate); correctionAdjValue.set("orderAdjustmentTypeId", "VAT_PRICE_CORRECT"); // the primary Geo should be the main jurisdiction that the tax is for, and the // secondary would just be to define a parent or wrapping jurisdiction of the primary correctionAdjValue.set("primaryGeoId", taxAuthGeoId); correctionAdjValue.set("comments", taxAuthorityRateProduct.getString("description")); if (taxAuthPartyId != null) correctionAdjValue.set("taxAuthPartyId", taxAuthPartyId); if (taxAuthGlAccountId != null) correctionAdjValue.set("overrideGlAccountId", taxAuthGlAccountId); if (taxAuthGeoId != null) correctionAdjValue.set("taxAuthGeoId", taxAuthGeoId); adjustments.add(correctionAdjValue); } } } } catch (GenericEntityException e) { Debug.logError(e, "Problems looking up tax rates", module); return FastList.newInstance(); } return adjustments; }