blob: 3d73b6ce238f5c992c5aa6021dccd84a671272e2 [file] [log] [blame]
/*******************************************************************************
* 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.ofbiz.accounting.finaccount;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javolution.util.FastMap;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.GeneralException;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityExpr;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.util.EntityListIterator;
import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.order.finaccount.FinAccountHelper;
import org.ofbiz.product.store.ProductStoreWorker;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.service.ModelService;
import org.ofbiz.service.ServiceUtil;
public class FinAccountServices {
public static final String module = FinAccountServices.class.getName();
public static final String resourceError = "AccountingErrorUiLabels";
public static Map<String, Object> createAccountAndCredit(DispatchContext dctx, Map<String, Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
String finAccountTypeId = (String) context.get("finAccountTypeId");
String accountName = (String) context.get("accountName");
String finAccountId = (String) context.get("finAccountId");
Locale locale = (Locale) context.get("locale");
// check the type
if (finAccountTypeId == null) {
finAccountTypeId = "SVCCRED_ACCOUNT";
}
if (accountName == null) {
if ("SVCCRED_ACCOUNT".equals(finAccountTypeId)) {
accountName = "Customer Service Credit Account";
} else {
accountName = "Financial Account";
}
}
GenericValue userLogin = (GenericValue) context.get("userLogin");
try {
// find the most recent (active) service credit account for the specified party
String partyId = (String) context.get("partyId");
Map<String, String> lookupMap = UtilMisc.toMap("finAccountTypeId", finAccountTypeId, "ownerPartyId", partyId);
// if a productStoreId is present, restrict the accounts returned using the store's payToPartyId
String productStoreId = (String) context.get("productStoreId");
if (UtilValidate.isNotEmpty(productStoreId)) {
String payToPartyId = ProductStoreWorker.getProductStorePayToPartyId(productStoreId, delegator);
if (UtilValidate.isNotEmpty(payToPartyId)) {
lookupMap.put("organizationPartyId", payToPartyId);
}
}
// if a currencyUomId is present, use it to restrict the accounts returned
String currencyUomId = (String) context.get("currencyUomId");
if (UtilValidate.isNotEmpty(currencyUomId)) {
lookupMap.put("currencyUomId", currencyUomId);
}
// check for an existing account
GenericValue creditAccount;
if (finAccountId != null) {
creditAccount = delegator.findByPrimaryKey("FinAccount", UtilMisc.toMap("finAccountId", finAccountId));
} else {
List<GenericValue> creditAccounts = delegator.findByAnd("FinAccount", lookupMap, UtilMisc.toList("-fromDate"));
creditAccount = EntityUtil.getFirst(EntityUtil.filterByDate(creditAccounts));
}
if (creditAccount == null) {
// create a new service credit account
String createAccountServiceName = "createFinAccount";
if (UtilValidate.isNotEmpty(productStoreId)) {
createAccountServiceName = "createFinAccountForStore";
}
// automatically set the parameters
ModelService createAccountService = dctx.getModelService(createAccountServiceName);
Map<String, Object> createAccountContext = createAccountService.makeValid(context, ModelService.IN_PARAM);
createAccountContext.put("finAccountTypeId", finAccountTypeId);
createAccountContext.put("finAccountName", accountName);
createAccountContext.put("ownerPartyId", partyId);
createAccountContext.put("userLogin", userLogin);
Map<String, Object> createAccountResult = dispatcher.runSync(createAccountServiceName, createAccountContext);
if (ServiceUtil.isError(createAccountResult) || ServiceUtil.isFailure(createAccountResult)) {
return createAccountResult;
}
if (createAccountResult != null) {
String creditAccountId = (String) createAccountResult.get("finAccountId");
if (UtilValidate.isNotEmpty(creditAccountId)) {
creditAccount = delegator.findByPrimaryKey("FinAccount", UtilMisc.toMap("finAccountId", creditAccountId));
// create the owner role
Map<String, Object> roleCtx = FastMap.newInstance();
roleCtx.put("partyId", partyId);
roleCtx.put("roleTypeId", "OWNER");
roleCtx.put("finAccountId", creditAccountId);
roleCtx.put("userLogin", userLogin);
roleCtx.put("fromDate", UtilDateTime.nowTimestamp());
Map<String, Object> roleResp;
try {
roleResp = dispatcher.runSync("createFinAccountRole", roleCtx);
} catch (GenericServiceException e) {
return ServiceUtil.returnError(e.getMessage());
}
if (ServiceUtil.isError(roleResp)) {
return roleResp;
}
finAccountId = creditAccountId; // update the finAccountId for return parameter
}
}
if (creditAccount == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotCreditAccount", locale));
}
}
// create the credit transaction
Map<String, Object> transactionMap = FastMap.newInstance();
transactionMap.put("finAccountTransTypeId", "ADJUSTMENT");
transactionMap.put("finAccountId", creditAccount.getString("finAccountId"));
transactionMap.put("partyId", partyId);
transactionMap.put("amount", context.get("amount"));
transactionMap.put("reasonEnumId", context.get("reasonEnumId"));
transactionMap.put("comments", context.get("comments"));
transactionMap.put("userLogin", userLogin);
Map<String, Object> creditTransResult = dispatcher.runSync("createFinAccountTrans", transactionMap);
if (ServiceUtil.isError(creditTransResult) || ServiceUtil.isFailure(creditTransResult)) {
return creditTransResult;
}
} catch (GenericEntityException gee) {
return ServiceUtil.returnError(gee.getMessage());
} catch (GenericServiceException gse) {
return ServiceUtil.returnError(gse.getMessage());
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("finAccountId", finAccountId);
return result;
}
public static Map<String, Object> createFinAccountForStore(DispatchContext dctx, Map<String, Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
String productStoreId = (String) context.get("productStoreId");
String finAccountTypeId = (String) context.get("finAccountTypeId");
Locale locale = (Locale) context.get("locale");
GenericValue productStore = ProductStoreWorker.getProductStore(productStoreId, delegator);
try {
// get the product store id and use it to generate a unique fin account code
GenericValue productStoreFinAccountSetting = delegator.findByPrimaryKeyCache("ProductStoreFinActSetting", UtilMisc.toMap("productStoreId", productStoreId, "finAccountTypeId", finAccountTypeId));
if (productStoreFinAccountSetting == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountSetting",
UtilMisc.toMap("productStoreId", productStoreId, "finAccountTypeId", finAccountTypeId), locale));
}
Long accountCodeLength = productStoreFinAccountSetting.getLong("accountCodeLength");
Long accountValidDays = productStoreFinAccountSetting.getLong("accountValidDays");
Long pinCodeLength = productStoreFinAccountSetting.getLong("pinCodeLength");
String requirePinCode = productStoreFinAccountSetting.getString("requirePinCode");
// automatically set the parameters for the create fin account service
ModelService createService = dctx.getModelService("createFinAccount");
Map<String, Object> inContext = createService.makeValid(context, ModelService.IN_PARAM);
Timestamp now = UtilDateTime.nowTimestamp();
// now use our values
String finAccountCode = FinAccountHelper.getNewFinAccountCode(accountCodeLength.intValue(), delegator);
inContext.put("finAccountCode", finAccountCode);
// with pin codes, the account code becomes the ID and the pin becomes the code
if ("Y".equalsIgnoreCase(requirePinCode)) {
String pinCode = FinAccountHelper.getNewFinAccountCode(pinCodeLength.intValue(), delegator);
inContext.put("finAccountPin", pinCode);
}
// set the dates/userlogin
if (UtilValidate.isNotEmpty(accountValidDays)){
inContext.put("thruDate", UtilDateTime.getDayEnd(now, accountValidDays));
}
inContext.put("fromDate", now);
inContext.put("userLogin", userLogin);
// product store payToPartyId
String payToPartyId = ProductStoreWorker.getProductStorePayToPartyId(productStoreId, delegator);
inContext.put("organizationPartyId", payToPartyId);
inContext.put("currencyUomId", productStore.get("defaultCurrencyUomId"));
Map<String, Object> createResult = dispatcher.runSync("createFinAccount", inContext);
if (ServiceUtil.isError(createResult)) {
return createResult;
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("finAccountId", createResult.get("finAccountId"));
result.put("finAccountCode", finAccountCode);
return result;
} catch (GenericEntityException ex) {
return ServiceUtil.returnError(ex.getMessage());
} catch (GenericServiceException ex) {
return ServiceUtil.returnError(ex.getMessage());
}
}
public static Map<String, Object> checkFinAccountBalance(DispatchContext dctx, Map<String, Object> context) {
Delegator delegator = dctx.getDelegator();
String finAccountId = (String) context.get("finAccountId");
String finAccountCode = (String) context.get("finAccountCode");
Locale locale = (Locale) context.get("locale");
GenericValue finAccount;
if (finAccountId == null) {
try {
finAccount = FinAccountHelper.getFinAccountFromCode(finAccountCode, delegator);
} catch (GenericEntityException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
}
} else {
try {
finAccount = delegator.findByPrimaryKey("FinAccount", UtilMisc.toMap("finAccountId", finAccountId));
} 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));
}
// get the balance
BigDecimal availableBalance = finAccount.getBigDecimal("availableBalance");
BigDecimal balance = finAccount.getBigDecimal("actualBalance");
if (availableBalance == null) {
availableBalance = FinAccountHelper.ZERO;
}
if (balance == null) {
balance = FinAccountHelper.ZERO;
}
String statusId = finAccount.getString("statusId");
Debug.log("FinAccount Balance [" + balance + "] Available [" + availableBalance + "] - Status: " + statusId, module);
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("availableBalance", availableBalance);
result.put("balance", balance);
result.put("statusId", statusId);
return result;
}
public static Map<String, Object> checkFinAccountStatus(DispatchContext dctx, Map<String, Object> context) {
Delegator delegator = dctx.getDelegator();
String finAccountId = (String) context.get("finAccountId");
Locale locale = (Locale) context.get("locale");
if (finAccountId == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountNotFound", UtilMisc.toMap("finAccountId", ""), locale));
}
GenericValue finAccount;
try {
finAccount = delegator.findByPrimaryKey("FinAccount", UtilMisc.toMap("finAccountId", finAccountId));
} catch (GenericEntityException ex) {
return ServiceUtil.returnError(ex.getMessage());
}
if (finAccount != null) {
String statusId = finAccount.getString("statusId");
if (statusId == null) statusId = "FNACT_ACTIVE";
BigDecimal balance = finAccount.getBigDecimal("actualBalance");
if (balance == null) {
balance = FinAccountHelper.ZERO;
}
Debug.logInfo("Account #" + finAccountId + " Balance: " + balance + " Status: " + statusId, module);
if ("FNACT_ACTIVE".equals(statusId) && balance.compareTo(FinAccountHelper.ZERO) < 1) {
finAccount.set("statusId", "FNACT_MANFROZEN");
Debug.logInfo("Financial account [" + finAccountId + "] has passed its threshold [" + balance + "] (Frozen)", module);
} else if ("FNACT_MANFROZEN".equals(statusId) && balance.compareTo(FinAccountHelper.ZERO) > 0) {
finAccount.set("statusId", "FNACT_ACTIVE");
Debug.logInfo("Financial account [" + finAccountId + "] has been made current [" + balance + "] (Un-Frozen)", module);
}
try {
finAccount.store();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(e.getMessage());
}
}
return ServiceUtil.returnSuccess();
}
public static Map<String, Object> refundFinAccount(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 finAccountId = (String) context.get("finAccountId");
Map<String, Object> result = null;
GenericValue finAccount;
try {
finAccount = delegator.findByPrimaryKey("FinAccount", UtilMisc.toMap("finAccountId", finAccountId));
} catch (GenericEntityException e) {
return ServiceUtil.returnError(e.getMessage());
}
if (finAccount != null) {
// check to make sure the account is refundable
if (!"Y".equals(finAccount.getString("isRefundable"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountIsNotRefundable", locale));
}
// get the actual and available balance
BigDecimal availableBalance = finAccount.getBigDecimal("availableBalance");
BigDecimal actualBalance = finAccount.getBigDecimal("actualBalance");
// if they do not match, then there are outstanding authorizations which need to be settled first
if (actualBalance.compareTo(availableBalance) != 0) {
return ServiceUtil.returnError(UtilProperties.getMessage(resourceError,
"AccountingFinAccountCannotBeRefunded", locale));
}
// now we make sure there is something to refund
if (actualBalance.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal remainingBalance = new BigDecimal(actualBalance.toString());
BigDecimal refundAmount = BigDecimal.ZERO;
List<EntityExpr> exprs = UtilMisc.toList(EntityCondition.makeCondition("finAccountTransTypeId", EntityOperator.EQUALS, "DEPOSIT"),
EntityCondition.makeCondition("finAccountId", EntityOperator.EQUALS, finAccountId));
EntityCondition condition = EntityCondition.makeCondition(exprs, EntityOperator.AND);
EntityListIterator eli = null;
try {
eli = delegator.find("FinAccountTrans", condition, null, null, UtilMisc.toList("-transactionDate"), null);
GenericValue trans;
while (remainingBalance.compareTo(FinAccountHelper.ZERO) < 0 && (trans = eli.next()) != null) {
String orderId = trans.getString("orderId");
String orderItemSeqId = trans.getString("orderItemSeqId");
// make sure there is an order available to refund
if (orderId != null && orderItemSeqId != null) {
GenericValue orderHeader = delegator.findByPrimaryKey("OrderHeader", UtilMisc.toMap("orderId",orderId));
GenericValue productStore = delegator.getRelatedOne("ProductStore", orderHeader);
GenericValue orderItem = delegator.findByPrimaryKey("OrderItem", UtilMisc.toMap("orderId", orderId, "orderItemSeqId", orderItemSeqId));
if (!"ITEM_CANCELLED".equals(orderItem.getString("statusId"))) {
// make sure the item hasn't already been returned
List<GenericValue> returnItems = orderItem.getRelated("ReturnItem");
if (UtilValidate.isEmpty(returnItems)) {
BigDecimal txAmt = trans.getBigDecimal("amount");
BigDecimal refAmt = txAmt;
if (remainingBalance.compareTo(txAmt) == -1) {
refAmt = remainingBalance;
}
remainingBalance = remainingBalance.subtract(refAmt);
refundAmount = refundAmount.add(refAmt);
// create the return header
Map<String, Object> rhCtx = UtilMisc.toMap("returnHeaderTypeId", "CUSTOMER_RETURN", "fromPartyId", finAccount.getString("ownerPartyId"), "toPartyId", productStore.getString("payToPartyId"), "userLogin", userLogin);
Map<String, Object> rhResp = dispatcher.runSync("createReturnHeader", rhCtx);
if (ServiceUtil.isError(rhResp)) {
throw new GeneralException(ServiceUtil.getErrorMessage(rhResp));
}
String returnId = (String) rhResp.get("returnId");
// create the return item
Map<String, Object> returnItemCtx = FastMap.newInstance();
returnItemCtx.put("returnId", returnId);
returnItemCtx.put("orderId", orderId);
returnItemCtx.put("description", orderItem.getString("itemDescription"));
returnItemCtx.put("orderItemSeqId", orderItemSeqId);
returnItemCtx.put("returnQuantity", BigDecimal.ONE);
returnItemCtx.put("receivedQuantity", BigDecimal.ONE);
returnItemCtx.put("returnPrice", refAmt);
returnItemCtx.put("returnReasonId", "RTN_NOT_WANT");
returnItemCtx.put("returnTypeId", "RTN_REFUND"); // refund return
returnItemCtx.put("returnItemTypeId", "RET_NPROD_ITEM");
returnItemCtx.put("userLogin", userLogin);
Map<String, Object> retItResp = dispatcher.runSync("createReturnItem", returnItemCtx);
if (ServiceUtil.isError(retItResp)) {
throw new GeneralException(ServiceUtil.getErrorMessage(retItResp));
}
String returnItemSeqId = (String) retItResp.get("returnItemSeqId");
// approve the return
Map<String, Object> appRet = UtilMisc.toMap("statusId", "RETURN_ACCEPTED", "returnId", returnId, "userLogin", userLogin);
Map<String, Object> appResp = dispatcher.runSync("updateReturnHeader", appRet);
if (ServiceUtil.isError(appResp)) {
throw new GeneralException(ServiceUtil.getErrorMessage(appResp));
}
// "receive" the return - should trigger the refund
Map<String, Object> recRet = UtilMisc.toMap("statusId", "RETURN_RECEIVED", "returnId", returnId, "userLogin", userLogin);
Map<String, Object> recResp = dispatcher.runSync("updateReturnHeader", recRet);
if (ServiceUtil.isError(recResp)) {
throw new GeneralException(ServiceUtil.getErrorMessage(recResp));
}
// get the return item
GenericValue returnItem = delegator.findByPrimaryKey("ReturnItem",
UtilMisc.toMap("returnId", returnId, "returnItemSeqId", returnItemSeqId));
GenericValue response = returnItem.getRelatedOne("ReturnItemResponse");
if (response == null) {
throw new GeneralException("No return response found for: " + returnItem.getPrimaryKey());
}
String paymentId = response.getString("paymentId");
// create the adjustment transaction
Map<String, Object> txCtx = FastMap.newInstance();
txCtx.put("finAccountTransTypeId", "ADJUSTMENT");
txCtx.put("finAccountId", finAccountId);
txCtx.put("orderId", orderId);
txCtx.put("orderItemSeqId", orderItemSeqId);
txCtx.put("paymentId", paymentId);
txCtx.put("amount", refAmt.negate());
txCtx.put("partyId", finAccount.getString("ownerPartyId"));
txCtx.put("userLogin", userLogin);
Map<String, Object> txResp = dispatcher.runSync("createFinAccountTrans", txCtx);
if (ServiceUtil.isError(txResp)) {
throw new GeneralException(ServiceUtil.getErrorMessage(txResp));
}
}
}
}
}
} catch (GeneralException e) {
Debug.logError(e, module);
return ServiceUtil.returnError(e.getMessage());
} finally {
if (eli != null) {
try {
eli.close();
} catch (GenericEntityException e) {
Debug.logWarning(e, module);
}
}
}
// check to make sure we balanced out
if (remainingBalance.compareTo(FinAccountHelper.ZERO) == 1) {
result = ServiceUtil.returnSuccess(UtilProperties.getMessage(resourceError,
"AccountingFinAccountPartiallyRefunded", locale));
}
}
}
if (result == null) {
result = ServiceUtil.returnSuccess();
}
return result;
}
}