/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. *******************************************************************************/ package org.apache.ofbiz.accounting.payment; import java.math.BigDecimal; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.GeneralException; import org.apache.ofbiz.base.util.UtilDateTime; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilNumber; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.order.order.OrderReadHelper; import org.apache.ofbiz.service.DispatchContext; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ServiceUtil; /** * Worker methods for BillingAccounts */ public class BillingAccountWorker { public static final String module = BillingAccountWorker.class.getName(); public static final String resourceError = "AccountingUiLabels"; private static BigDecimal ZERO = BigDecimal.ZERO; private static int decimals = -1; private static int rounding = -1; static { decimals = UtilNumber.getBigDecimalScale("order.decimals"); rounding = UtilNumber.getBigDecimalRoundingMode("order.rounding"); // set zero to the proper scale if (decimals != -1) ZERO = ZERO.setScale(decimals); } public static List<Map<String, Object>> makePartyBillingAccountList(GenericValue userLogin, String currencyUomId, String partyId, Delegator delegator, LocalDispatcher dispatcher) throws GeneralException { List<Map<String, Object>> billingAccountList = new LinkedList<Map<String,Object>>(); Map<String, Object> agentResult = dispatcher.runSync("getRelatedParties", UtilMisc.<String, Object>toMap("userLogin", userLogin, "partyIdFrom", partyId, "roleTypeIdFrom", "AGENT", "roleTypeIdTo", "CUSTOMER", "partyRelationshipTypeId", "AGENT", "includeFromToSwitched", "Y")); if (ServiceUtil.isError(agentResult)) { throw new GeneralException("Error while finding party BillingAccounts when getting Customers that this party is an agent of: " + ServiceUtil.getErrorMessage(agentResult)); } List<String> relatedPartyIdList = UtilGenerics.checkList(agentResult.get("relatedPartyIdList")); List<GenericValue> billingAccountRoleList = EntityQuery.use(delegator).from("BillingAccountRole") .where(EntityCondition.makeCondition("partyId", EntityOperator.IN, relatedPartyIdList), EntityCondition.makeCondition("roleTypeId", EntityOperator.EQUALS, "BILL_TO_CUSTOMER") ).filterByDate().queryList(); if (billingAccountRoleList.size() > 0) { BigDecimal totalAvailable = BigDecimal.ZERO; for (GenericValue billingAccountRole : billingAccountRoleList) { GenericValue billingAccountVO = billingAccountRole.getRelatedOne("BillingAccount", false); // skip accounts that have thruDate < nowTimestamp java.sql.Timestamp thruDate = billingAccountVO.getTimestamp("thruDate"); if ((thruDate != null) && UtilDateTime.nowTimestamp().after(thruDate)) continue; if (currencyUomId.equals(billingAccountVO.getString("accountCurrencyUomId"))) { BigDecimal accountBalance = OrderReadHelper.getBillingAccountBalance(billingAccountVO); Map<String, Object> billingAccount = new HashMap<String, Object>(billingAccountVO); BigDecimal accountLimit = OrderReadHelper.getAccountLimit(billingAccountVO); billingAccount.put("accountBalance", accountBalance); BigDecimal accountAvailable = accountLimit.subtract(accountBalance); totalAvailable = totalAvailable.add(accountAvailable); billingAccountList.add(billingAccount); } } Collections.sort(billingAccountList, new BillingAccountComparator()); } return billingAccountList; } /** * Returns list of orders which are currently open against a billing account */ public static List<GenericValue> getBillingAccountOpenOrders(Delegator delegator, String billingAccountId) throws GenericEntityException { return EntityQuery.use(delegator).from("OrderHeader") .where(EntityCondition.makeCondition("billingAccountId", EntityOperator.EQUALS, billingAccountId), EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "ORDER_REJECTED"), EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "ORDER_CANCELLED"), EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "ORDER_COMPLETED") ).queryList(); } /** * Returns the amount which could be charged to a billing account, which is defined as the accountLimit minus account balance and minus the balance of outstanding orders * When trying to figure out how much of a billing account can be used to pay for an outstanding order, use this method * @param billingAccount GenericValue object of the billing account * @return returns the amount which could be charged to a billing account * @throws GenericEntityException */ public static BigDecimal getBillingAccountAvailableBalance(GenericValue billingAccount) throws GenericEntityException { if ((billingAccount != null) && (billingAccount.get("accountLimit") != null)) { BigDecimal accountLimit = billingAccount.getBigDecimal("accountLimit"); BigDecimal availableBalance = accountLimit.subtract(OrderReadHelper.getBillingAccountBalance(billingAccount)).setScale(decimals, rounding); return availableBalance; } else { Debug.logWarning("Available balance requested for null billing account, returning zero", module); return ZERO; } } public static BigDecimal getBillingAccountAvailableBalance(Delegator delegator, String billingAccountId) throws GenericEntityException { GenericValue billingAccount = EntityQuery.use(delegator).from("BillingAccount").where("billingAccountId", billingAccountId).queryOne(); return getBillingAccountAvailableBalance(billingAccount); } /** * Calculates the net balance of a billing account, which is sum of all amounts applied to invoices minus sum of all amounts applied from payments. * When charging or capturing an invoice to a billing account, use this method * @param delegator the delegator * @param billingAccountId the billing account id * @return the amount of the billing account which could be captured * @throws GenericEntityException */ public static BigDecimal getBillingAccountNetBalance(Delegator delegator, String billingAccountId) throws GenericEntityException { BigDecimal balance = ZERO; // search through all PaymentApplications and add the amount that was applied to invoice and subtract the amount applied from payments List<GenericValue> paymentAppls = EntityQuery.use(delegator).from("PaymentApplication").where("billingAccountId", billingAccountId).queryList(); for (Iterator<GenericValue> pAi = paymentAppls.iterator(); pAi.hasNext();) { GenericValue paymentAppl = pAi.next(); BigDecimal amountApplied = paymentAppl.getBigDecimal("amountApplied"); GenericValue invoice = paymentAppl.getRelatedOne("Invoice", false); if (invoice != null) { // make sure the invoice has not been canceled and it is not a "Customer return invoice" if (!"CUST_RTN_INVOICE".equals(invoice.getString("invoiceTypeId")) && !"INVOICE_CANCELLED".equals(invoice.getString("statusId"))) { balance = balance.add(amountApplied); } } else { balance = balance.subtract(amountApplied); } } balance = balance.setScale(decimals, rounding); return balance; } /** * Returns the amount of the billing account which could be captured, which is BillingAccount.accountLimit - net balance * @param billingAccount GenericValue object of the billing account * @return the amount of the billing account which could be captured * @throws GenericEntityException */ public static BigDecimal availableToCapture(GenericValue billingAccount) throws GenericEntityException { BigDecimal netBalance = getBillingAccountNetBalance(billingAccount.getDelegator(), billingAccount.getString("billingAccountId")); BigDecimal accountLimit = billingAccount.getBigDecimal("accountLimit"); return accountLimit.subtract(netBalance).setScale(decimals, rounding); } public static Map<String, Object> calcBillingAccountBalance(DispatchContext dctx, Map<String, ? extends Object> context) { Delegator delegator = dctx.getDelegator(); String billingAccountId = (String) context.get("billingAccountId"); Locale locale = (Locale) context.get("locale"); Map<String, Object> result = ServiceUtil.returnSuccess(); try { GenericValue billingAccount = EntityQuery.use(delegator).from("BillingAccount").where("billingAccountId", billingAccountId).queryOne(); if (billingAccount == null) { return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingBillingAccountNotFound", UtilMisc.toMap("billingAccountId", billingAccountId), locale)); } result.put("billingAccount", billingAccount); result.put("accountBalance", OrderReadHelper.getBillingAccountBalance(billingAccount)); result.put("netAccountBalance", getBillingAccountNetBalance(delegator, billingAccountId)); result.put("availableBalance", getBillingAccountAvailableBalance(billingAccount)); result.put("availableToCapture", availableToCapture(billingAccount)); return result; } catch (GenericEntityException e) { Debug.logError(e, module); return ServiceUtil.returnError(UtilProperties.getMessage(resourceError, "AccountingBillingAccountNotFound", UtilMisc.toMap("billingAccountId", billingAccountId), locale)); } } protected static class BillingAccountComparator implements Comparator<Map<String, Object>> { public int compare(Map<String, Object> billingAccount1, Map<String, Object> billingAccount2) { return ((BigDecimal)billingAccount1.get("accountBalance")).compareTo((BigDecimal)billingAccount2.get("accountBalance")); } } }