📄 utilcogs.java
字号:
/* * Copyright (C) 2006 Open Source Strategies, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */package com.opensourcestrategies.financials.util;import java.util.*;import java.sql.Timestamp;import java.math.BigDecimal;import javolution.util.FastMap;import org.ofbiz.accounting.AccountingException;import org.ofbiz.base.util.*;import org.ofbiz.entity.*;import org.ofbiz.entity.condition.*;import org.ofbiz.entity.util.*;import org.ofbiz.service.*;import com.opensourcestrategies.financials.util.UtilFinancial;/** * UtilCOGS - Utilities for Cost of Goods Sold services and calculations * * @author <a href="mailto:leon@opensourcestrategies.com">Leon Torres</a> * @version $Rev: 81 $ * @since 2.2 */public class UtilCOGS { public static String module = UtilCOGS.class.getName(); public static int decimals = UtilNumber.getBigDecimalScale("fin_arithmetic.properties", "financial.statements.decimals"); public static int rounding = UtilNumber.getBigDecimalRoundingMode("fin_arithmetic.properties", "financial.statements.rounding"); public static final BigDecimal ZERO = new BigDecimal("0"); /** * Attempts to find the average cost of a given product. First it will look in ProductAverageCost. * Then it will call calculateProductCosts to find the average cost of the product in CostComponent. * If neither method works, then a null will be returned, signaling that no product average cost was * found and some other technique should be used, or an error should be raised. */ public static BigDecimal getProductAverageCost(String productId, String organizationPartyId, GenericValue userLogin, GenericDelegator delegator, LocalDispatcher dispatcher) { BigDecimal cost = null; try { // first try using the first unexpired ProductAverageCost.averageCost as the cost EntityConditionList conditions = new EntityConditionList(UtilMisc.toList( EntityUtil.getFilterByDateExpr(), new EntityExpr("productId", EntityOperator.EQUALS, productId), new EntityExpr("organizationPartyId", EntityOperator.EQUALS, organizationPartyId) ), EntityOperator.AND); GenericValue entry = EntityUtil.getFirst( delegator.findByCondition("ProductAverageCost", conditions, UtilMisc.toList("averageCost"), null) ); if (entry != null) { cost = entry.getBigDecimal("averageCost"); } // if we found a non-zero cost via ProductAverageCost, then we're done if ((cost != null) && (cost.signum() != 0)) return cost; // otherwise we have to look up the standard costs (the CostComponents with prefix EST_STD_) GenericValue acctgPref = delegator.findByPrimaryKeyCache("PartyAcctgPreference", UtilMisc.toMap("partyId", organizationPartyId)); Map results = dispatcher.runSync("calculateProductCosts", UtilMisc.toMap("productId", productId, "currencyUomId", acctgPref.get("baseCurrencyUomId"), "costComponentTypePrefix", "EST_STD", "userLogin", userLogin)); if (!ServiceUtil.isError(results)) { Double totalCost = (Double) results.get("totalCost"); if ((totalCost != null) && (totalCost.doubleValue() != 0.0)) { cost = new BigDecimal(totalCost.doubleValue()); } } // if we found a non-zero cost via calculateProductCosts, then we're done if ((cost != null) && (cost.signum() != 0)) return cost; } catch (GenericEntityException e) { Debug.logError("Failed to get product average cost for productId [" + productId + "] and organization [" + organizationPartyId + "] due to entity error.", module); } catch (GenericServiceException e) { Debug.logError("Failed to get product average cost for productId [" + productId + "] and organization [" + organizationPartyId + "] due to service error.", module); } // if we get here, it's because no non-zero cost was found, so return null return null; } /** * Gets the value of a product in the inventory of an organization. * The value is the sum of posted debits minus the sum of posted credits * in the INVENTORY_ACCOUNT or INV_ADJ_AVG_COST GL accounts for this product. * * @param productId The product to get the value of * @param organizationPartyId Organization of the transactions, which is required. * @param transactionDate An optional timestamp. If specified, the sum will be taken up to this date, inclusive. * @return BigDecimal value of the product */ public static BigDecimal getInventoryValueForProduct(String productId, String organizationPartyId, Timestamp transactionDate, GenericDelegator delegator) throws GenericEntityException { Map results = getNetInventoryValueHelper(productId, organizationPartyId, transactionDate, delegator); return (BigDecimal) results.get(productId); } /** * Gets the value of all products in the inventory of an organization. * The value is the sum of posted debits minus the sum of posted credits * in the INVENTORY_ACCOUNT or INV_ADJ_AVG_COST GL accounts for each product. * * @param productId Specify this to limit the query to one product * @param organizationPartyId Organization of the transactions, always specify this. * @param transactionDate Specify this to sum over all transactions before the transactionDate or set to null to sum over all dates * @return Map of productIds keyed to their net (debit - credit) amounts */ public static Map getInventoryValueForAllProducts(String organizationPartyId, Timestamp transactionDate, GenericDelegator delegator) throws GenericEntityException { return getNetInventoryValueHelper(null, organizationPartyId, transactionDate, delegator); } /** * Helper method to get product inventory values. Use one of the other methods that implements this, * getInventoryValueForProduct() or getInventoryValueForAllProducts(). * * @param productId Specify this to limit the query to one product * @param organizationPartyId Organization of the transactions, always specify this. * @param transactionDate Specify this to sum over all transactions before the transactionDate or set to null to sum over all dates * @return Map of productIds keyed to their net (debit - credit) amounts */ private static Map getNetInventoryValueHelper(String productId, String organizationPartyId, Timestamp transactionDate, GenericDelegator delegator) throws GenericEntityException { if (organizationPartyId == null) { throw new GenericEntityException("No organizationPartyId specified for getting product inventory value(s)."); } // fields to select: exclude the transaction date from select list, otherwise we can't use the special view List selectFields = UtilMisc.toList("productId", "amount", "glAccountTypeId"); // common AND conditions List commonConditions = UtilMisc.toList( new EntityExpr("isPosted", EntityOperator.EQUALS, "Y"), new EntityExpr("organizationPartyId", EntityOperator.EQUALS, organizationPartyId) ); // select all the inventory account types commonConditions.add(new EntityConditionList(UtilMisc.toList( new EntityExpr("glAccountTypeId", EntityOperator.EQUALS, "INVENTORY_ACCOUNT"), new EntityExpr("glAccountTypeId", EntityOperator.EQUALS, "INV_ADJ_AVG_COST"), new EntityExpr("glAccountTypeId", EntityOperator.EQUALS, "RAWMAT_INVENTORY"), new EntityExpr("glAccountTypeId", EntityOperator.EQUALS, "WIP_INVENTORY") ), EntityOperator.OR)); // add optional constraints to common conditions if (productId != null) commonConditions.add(new EntityExpr("productId", EntityOperator.EQUALS, productId)); if (transactionDate != null) commonConditions.add(new EntityExpr("transactionDate", EntityOperator.LESS_THAN_EQUAL_TO, transactionDate)); // build condition for debits List debitConditions = new ArrayList(commonConditions); debitConditions.add(new EntityExpr("debitCreditFlag", EntityOperator.EQUALS, "D")); EntityConditionList debitConditionList = new EntityConditionList(debitConditions, EntityOperator.AND); // perform the query EntityListIterator listIt = delegator.findListIteratorByCondition("AcctgTransEntryProdSums", debitConditionList, null, selectFields, UtilMisc.toList("productId"), // fields to order by // the first true here is for "specifyTypeAndConcur" the second true is for a distinct select new EntityFindOptions(true, EntityFindOptions.TYPE_SCROLL_INSENSITIVE, EntityFindOptions.CONCUR_READ_ONLY, true)); // get all values from the entity list iterator List debitSums = listIt.getCompleteList(); // build condition for credits List creditConditions = new ArrayList(commonConditions); creditConditions.add(new EntityExpr("debitCreditFlag", EntityOperator.EQUALS, "C")); EntityConditionList creditConditionList = new EntityConditionList(creditConditions, EntityOperator.AND); // perform the query listIt = delegator.findListIteratorByCondition("AcctgTransEntryProdSums", creditConditionList, null, selectFields, UtilMisc.toList("productId"), // fields to order by // the first true here is for "specifyTypeAndConcur" the second true is for a distinct select new EntityFindOptions(true, EntityFindOptions.TYPE_SCROLL_INSENSITIVE, EntityFindOptions.CONCUR_READ_ONLY, true)); // get all values from the entity list iterator List creditSums = listIt.getCompleteList(); // make a map of the product Id to the (debit - credit) amount Map inventoryValueByProduct = FastMap.newInstance(); // the strategy is to store the negative of the credit amount in the map first (because debits might not exist for a particular product) for (Iterator iter = creditSums.iterator(); iter.hasNext(); ) { GenericValue value = (GenericValue) iter.next(); inventoryValueByProduct.put(UtilFinancial.getProductIdOrDefault(value), ZERO.subtract(value.getBigDecimal("amount")).setScale(decimals, rounding)); } // then go through debits and add the debit amounts for (Iterator iter = debitSums.iterator(); iter.hasNext(); ) { GenericValue value = (GenericValue) iter.next(); BigDecimal creditAmount = (BigDecimal) inventoryValueByProduct.get(UtilFinancial.getProductIdOrDefault(value)); if (creditAmount == null) creditAmount = ZERO; // if credit didn't exist, set to zero BigDecimal difference = value.getBigDecimal("amount").add(creditAmount).setScale(decimals, rounding); inventoryValueByProduct.put(UtilFinancial.getProductIdOrDefault(value), difference); } return inventoryValueByProduct; } /** * Method to get total inventory quantity on hand in all facilities for a given product and organizataiton. */ public static BigDecimal getInventoryQuantityForProduct(String productId, String organizationPartyId, GenericDelegator delegator, LocalDispatcher dispatcher) throws GenericServiceException, GenericEntityException { BigDecimal quantity = ZERO; // the strategy is to loop through the organization facilities and get the product inventory for each facility List facilities = delegator.findByAnd("Facility", UtilMisc.toMap("ownerPartyId", organizationPartyId)); for (Iterator iter = facilities.iterator(); iter.hasNext(); ) { String facilityId = ((GenericValue) iter.next()).getString("facilityId"); Map serviceResults = dispatcher.runSync("getInventoryAvailableByFacility", UtilMisc.toMap("productId", productId, "facilityId", facilityId)); if (ServiceUtil.isError(serviceResults)) { throw new GenericServiceException(ServiceUtil.getErrorMessage(serviceResults)); } BigDecimal facilityQuantity = new BigDecimal(((Double) serviceResults.get("quantityOnHandTotal")).doubleValue()); quantity = quantity.add(facilityQuantity).setScale(decimals, rounding); // TODO: quantity sum needs its own decimals and rounding } return quantity; } /** * Returns the total inventory value based on data from InventoryItem rather than accounting ledger data * * @param productId * @param organizationPartyId * @param delegator * @param dispatcher * @return * @throws GenericEntityException * @throws GenericServiceException */ public static BigDecimal getInventoryValueFromItems(String productId, String organizationPartyId, GenericDelegator delegator, LocalDispatcher dispatcher) throws GenericEntityException, GenericServiceException { List items = delegator.findByAnd("InventoryItem", UtilMisc.toMap("productId", productId, "ownerPartyId", organizationPartyId)); BigDecimal total = ZERO; for (Iterator it = items.iterator(); it.hasNext(); ) { GenericValue item = (GenericValue) it.next(); if ((item.get("unitCost") != null) && (item.get("quantityOnHandTotal") != null)) { BigDecimal conversionFactor = new BigDecimal(UtilFinancial.determineUomConversionFactor(delegator, dispatcher, organizationPartyId, item.getString("currencyUomId"))); // this precision is probably OK since we're multiplying total = total.add(item.getBigDecimal("unitCost").multiply(item.getBigDecimal("quantityOnHandTotal")).multiply(conversionFactor)).setScale(decimals, rounding); } } return total; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -