blob: 9920662eca0a60da3123282ad322b04ae8762b12 [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.order.finaccount;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.List;
import java.util.Random;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilNumber;
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.EntityOperator;
import org.ofbiz.entity.util.EntityQuery;
/**
* A package of methods for improving efficiency of financial accounts services
*
*/
public class FinAccountHelper {
public static final String module = FinAccountHelper.class.getName();
/**
* A word on precision: since we're just adding and subtracting, the interim figures should have one more decimal place of precision than the final numbers.
*/
public static int decimals = UtilNumber.getBigDecimalScale("finaccount.decimals");
public static int rounding = UtilNumber.getBigDecimalRoundingMode("finaccount.rounding");
public static final BigDecimal ZERO = BigDecimal.ZERO.setScale(decimals, rounding);
public static final String giftCertFinAccountTypeId = "GIFTCERT_ACCOUNT";
public static final boolean defaultPinRequired = false;
// pool of available characters for account codes, here numbers plus uppercase characters
static char[] char_pool = new char[10+26];
static {
int j = 0;
for (int i = "0".charAt(0); i <= "9".charAt(0); i++) {
char_pool[j++] = (char) i;
}
for (int i = "A".charAt(0); i <= "Z".charAt(0); i++) {
char_pool[j++] = (char) i;
}
}
/**
* A convenience method which adds transactions.get(0).get(fieldName) to initialValue, all done in BigDecimal to decimals and rounding
* @param initialValue the initial value
* @param transactions a List of GenericValue objects of transactions
* @param fieldName the field name to get the value from the transaction
* @param decimals number of decimals
* @param rounding how to rounding
* @return the new value in a BigDecimal field
* @throws GenericEntityException
*/
public static BigDecimal addFirstEntryAmount(BigDecimal initialValue, List<GenericValue> transactions, String fieldName, int decimals, int rounding) throws GenericEntityException {
if ((transactions != null) && (transactions.size() == 1)) {
GenericValue firstEntry = transactions.get(0);
if (firstEntry.get(fieldName) != null) {
BigDecimal valueToAdd = firstEntry.getBigDecimal(fieldName);
return initialValue.add(valueToAdd).setScale(decimals, rounding);
} else {
return initialValue;
}
} else {
return initialValue;
}
}
/**
* Returns a unique randomly generated account code for FinAccount.finAccountCode composed of uppercase letters and numbers
* @param codeLength length of code in number of characters
* @param delegator the delegator
* @return returns a unique randomly generated account code for FinAccount.finAccountCode composed of uppercase letters and numbers
* @throws GenericEntityException
*/
public static String getNewFinAccountCode(int codeLength, Delegator delegator) throws GenericEntityException {
// keep generating new account codes until a unique one is found
Random r = new Random();
boolean foundUniqueNewCode = false;
StringBuilder newAccountCode = null;
long count = 0;
while (!foundUniqueNewCode) {
newAccountCode = new StringBuilder(codeLength);
for (int i = 0; i < codeLength; i++) {
newAccountCode.append(char_pool[r.nextInt(char_pool.length)]);
}
GenericValue existingAccountWithCode = EntityQuery.use(delegator).from("FinAccount")
.where("finAccountCode", newAccountCode.toString()).queryFirst();
if (existingAccountWithCode == null) {
foundUniqueNewCode = true;
}
count++;
if (count > 999999) {
throw new GenericEntityException("Unable to locate unique FinAccountCode! Length [" + codeLength + "]");
}
}
return newAccountCode.toString();
}
/**
* Gets the first (and should be only) FinAccount based on finAccountCode, which will be cleaned up to be only uppercase and alphanumeric
* @param finAccountCode the financial account code
* @param delegator the delegator
* @return gets the first financial account by code
* @throws GenericEntityException
*/
public static GenericValue getFinAccountFromCode(String finAccountCode, Delegator delegator) throws GenericEntityException {
if (finAccountCode == null) {
return null;
}
finAccountCode = finAccountCode.toUpperCase().replaceAll("[^0-9A-Z]", "");
// now look for the account
List<GenericValue> accounts = EntityQuery.use(delegator).from("FinAccount")
.where("finAccountCode", finAccountCode)
.filterByDate().queryList();
if (UtilValidate.isEmpty(accounts)) {
// OK to display - not a code anyway
Debug.logWarning("No fin account found for account code [" + finAccountCode + "]", module);
return null;
} else if (accounts.size() > 1) {
// This should never happen, but don't display the code if it does -- it is supposed to be encrypted!
Debug.logError("Multiple fin accounts found", module);
return null;
} else {
return accounts.get(0);
}
}
/**
* Sum of all DEPOSIT and ADJUSTMENT transactions minus all WITHDRAWAL transactions whose transactionDate is before asOfDateTime
* @param finAccountId the financial account id
* @param asOfDateTime the validity date
* @param delegator the delegator
* @return returns the sum of all DEPOSIT and ADJUSTMENT transactions minus all WITHDRAWAL transactions
* @throws GenericEntityException
*/
public static BigDecimal getBalance(String finAccountId, Timestamp asOfDateTime, Delegator delegator) throws GenericEntityException {
if (asOfDateTime == null) asOfDateTime = UtilDateTime.nowTimestamp();
BigDecimal incrementTotal = ZERO; // total amount of transactions which increase balance
BigDecimal decrementTotal = ZERO; // decrease balance
// find the sum of all transactions which increase the value
List<GenericValue> transSums = EntityQuery.use(delegator)
.select("amount")
.from("FinAccountTransSum")
.where(EntityCondition.makeCondition("finAccountId", EntityOperator.EQUALS, finAccountId),
EntityCondition.makeCondition("transactionDate", EntityOperator.LESS_THAN_EQUAL_TO, asOfDateTime),
EntityCondition.makeCondition(UtilMisc.toList(
EntityCondition.makeCondition("finAccountTransTypeId", EntityOperator.EQUALS, "DEPOSIT"),
EntityCondition.makeCondition("finAccountTransTypeId", EntityOperator.EQUALS, "ADJUSTMENT")),
EntityOperator.OR))
.queryList();
incrementTotal = addFirstEntryAmount(incrementTotal, transSums, "amount", (decimals+1), rounding);
// now find sum of all transactions with decrease the value
transSums = EntityQuery.use(delegator)
.select("amount")
.from("FinAccountTransSum")
.where(EntityCondition.makeCondition("finAccountId", EntityOperator.EQUALS, finAccountId),
EntityCondition.makeCondition("transactionDate", EntityOperator.LESS_THAN_EQUAL_TO, asOfDateTime),
EntityCondition.makeCondition("finAccountTransTypeId", EntityOperator.EQUALS, "WITHDRAWAL"))
.queryList();
decrementTotal = addFirstEntryAmount(decrementTotal, transSums, "amount", (decimals+1), rounding);
// the net balance is just the incrementTotal minus the decrementTotal
return incrementTotal.subtract(decrementTotal).setScale(decimals, rounding);
}
/**
* Returns the net balance (see above) minus the sum of all authorization amounts which are not expired and were authorized by the as of date
* @param finAccountId the financial account id
* @param asOfDateTime the validity date
* @param delegator the delegator
* @return returns the net balance (see above) minus the sum of all authorization amounts which are not expired
* @throws GenericEntityException
*/
public static BigDecimal getAvailableBalance(String finAccountId, Timestamp asOfDateTime, Delegator delegator) throws GenericEntityException {
if (asOfDateTime == null) asOfDateTime = UtilDateTime.nowTimestamp();
BigDecimal netBalance = getBalance(finAccountId, asOfDateTime, delegator);
// find sum of all authorizations which are not expired and which were authorized before as of time
List<GenericValue> authSums = EntityQuery.use(delegator)
.select("amount")
.from("FinAccountAuthSum")
.where(EntityCondition.makeCondition("finAccountId", EntityOperator.EQUALS, finAccountId),
EntityCondition.makeCondition("authorizationDate", EntityOperator.LESS_THAN_EQUAL_TO, asOfDateTime))
.queryList();
BigDecimal authorizationsTotal = addFirstEntryAmount(ZERO, authSums, "amount", (decimals+1), rounding);
// the total available balance is transactions total minus authorizations total
return netBalance.subtract(authorizationsTotal).setScale(decimals, rounding);
}
public static boolean validateFinAccount(GenericValue finAccount) {
return false;
}
/**
* Validates a FinAccount's PIN number
* @param delegator the delegator
* @param finAccountId the financial account id
* @param pinNumber a pin number
* @return true if the bin is valid
*/
public static boolean validatePin(Delegator delegator, String finAccountId, String pinNumber) {
GenericValue finAccount = null;
try {
finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", finAccountId).queryOne();
} catch (GenericEntityException e) {
Debug.logError(e, module);
}
if (finAccount != null) {
String dbPin = finAccount.getString("finAccountCode");
Debug.logInfo("FinAccount Pin Validation: [Sent: " + pinNumber + "] [Actual: " + dbPin + "]", module);
if (dbPin != null && dbPin.equals(pinNumber)) {
return true;
}
} else {
Debug.logInfo("FinAccount record not found (" + finAccountId + ")", module);
}
return false;
}
/**
* Generate a random financial number
* @param delegator the delegator
* @param length length of the number to generate (up to 19 digits)
* @param isId to be used as an ID (will check the DB to make sure it doesn't already exist)
* @return Generated number
* @throws GenericEntityException
*/
public static String generateRandomFinNumber(Delegator delegator, int length, boolean isId) throws GenericEntityException {
if (length > 19) {
length = 19;
}
Random rand = new Random();
boolean isValid = false;
String number = null;
while (!isValid) {
number = "";
for (int i = 0; i < length; i++) {
int randInt = rand.nextInt(9);
number = number + randInt;
}
if (isId) {
int check = UtilValidate.getLuhnCheckDigit(number);
number = number + check;
// validate the number
if (checkFinAccountNumber(number)) {
// make sure this number doens't already exist
isValid = checkIsNumberInDatabase(delegator, number);
}
} else {
isValid = true;
}
}
return number;
}
private static boolean checkIsNumberInDatabase(Delegator delegator, String number) throws GenericEntityException {
GenericValue finAccount = EntityQuery.use(delegator).from("FinAccount").where("finAccountId", number).queryOne();
return finAccount == null;
}
public static boolean checkFinAccountNumber(String number) {
number = number.replaceAll("\\D", "");
return UtilValidate.sumIsMod10(UtilValidate.getLuhnSum(number));
}
}