MoneyWiseXAnalysisTransAnalyser.java
/*
* MoneyWise: Finance Application
* Copyright 2012-2026. Tony Washer
*
* Licensed 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 io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.analyse;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEvent;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysis;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisAccountBucket;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisCursor;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisPayeeBucket;
import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisTransCategoryBucket;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetType;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransAsset;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransTag;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
import java.util.List;
/**
* Transaction analyser.
*/
public class MoneyWiseXAnalysisTransAnalyser {
/**
* The analysis.
*/
private final MoneyWiseXAnalysis theAnalysis;
/**
* The analysis state.
*/
private final MoneyWiseXAnalysisState theState;
/**
* The market analysis.
*/
private final MoneyWiseXAnalysisMarket theMarket;
/**
* The tax analysis.
*/
private final MoneyWiseXAnalysisTax theTax;
/**
* The security analysis.
*/
private final MoneyWiseXAnalysisSecurity theSecurity;
/**
* The portfolioXfer analyser.
*/
private final MoneyWiseXAnalysisPortfolioXfer thePortfolioXfer;
/**
* The reporting currency.
*/
private final MoneyWiseCurrency theCurrency;
/**
* The current transaction.
*/
private MoneyWiseXAnalysisTransaction theTransaction;
/**
* Constructor.
*
* @param pAnalyser the analyser
* @throws OceanusException on error
*/
MoneyWiseXAnalysisTransAnalyser(final MoneyWiseXAnalysisEventAnalyser pAnalyser) throws OceanusException {
/* Store details */
theAnalysis = pAnalyser.getAnalysis();
theState = pAnalyser.getState();
theMarket = pAnalyser.getMarket();
theTax = pAnalyser.getTax();
theCurrency = theAnalysis.getCurrency();
/* Create the security analyser */
theSecurity = new MoneyWiseXAnalysisSecurity(pAnalyser, this);
thePortfolioXfer = new MoneyWiseXAnalysisPortfolioXfer(pAnalyser, theSecurity);
theTax.declareSecurityAnalyser(theSecurity);
}
/**
* Obtain the currency.
*
* @return the currency
*/
MoneyWiseCurrency getCurrency() {
return theCurrency;
}
/**
* Process transaction event.
*
* @param pEvent the event
* @throws OceanusException on error
*/
public void processTransaction(final MoneyWiseXAnalysisEvent pEvent) throws OceanusException {
/* Parse the event */
theTransaction = new MoneyWiseXAnalysisTransaction(pEvent);
final MoneyWiseTransaction myTrans = theTransaction.getTransaction();
/* Ignore header transactions */
if (!myTrans.isHeader()) {
/* Touch underlying items */
myTrans.touchUnderlyingItems();
/* Process tags */
final List<MoneyWiseTransTag> myTags = myTrans.getTransactionTags();
if (myTags != null) {
/* Process the transaction tags */
theAnalysis.getTransactionTags().processEvent(pEvent, myTags.iterator());
}
/* Process the transaction */
processTransaction();
}
}
/**
* Process transaction.
*
* @throws OceanusException on error
*/
private void processTransaction() throws OceanusException {
/* Access account and partner */
final MoneyWiseTransaction myTrans = theTransaction.getTransaction();
final MoneyWiseTransAsset myDebit = theTransaction.getDebitAccount();
final MoneyWiseTransAsset myCredit = theTransaction.getCreditAccount();
/* If the event relates to a security holding account, split out the workings */
if (isSecurityHolding(myDebit)) {
/* Process as a Security transaction */
theSecurity.processDebitSecurity(theTransaction);
/* If the event relates to a security holding partner, split out the workings */
} else if (isSecurityHolding(myCredit)) {
/* Process as a Security transaction */
theSecurity.processCreditSecurity(theTransaction);
/* If the event is portfolioXfer, split out the workings */
} else if (isPortfolioXfer(theTransaction.getCategoryClass())) {
/* Process the portfolioXfer */
thePortfolioXfer.processPortfolioXfer(theTransaction);
/* Else handle the event normally */
} else if (myDebit instanceof MoneyWiseAssetBase
&& myCredit instanceof MoneyWiseAssetBase) {
processNonSecurity();
/* else reject */
} else {
throw new MoneyWiseLogicException("Invalid Asset Pair: "
+ myTrans.getAccount().getAssetType()
+ " "
+ myTrans.getDirection()
+ " "
+ myTrans.getPartner().getAssetType());
}
/* adjust category bucket */
adjustCategoryBucket();
/* Process taxBasis */
theTax.adjustTaxBasis(theTransaction);
/* Register the eventBuckets */
final MoneyWiseXAnalysisEvent myEvent = theTransaction.getEvent();
theMarket.adjustMarketTotals(myEvent);
theState.registerInterestedBucketsForEvent(myEvent);
}
/**
* process non-security transaction.
*/
private void processNonSecurity() {
/* Adjust parent details */
theTransaction.adjustParent();
/* Process Asset accounts */
processAssets();
/* Process Auto-expense accounts */
processAutoExpense();
/* Process Payee accounts */
processPayees();
}
/**
* process assets.
*/
private void processAssets() {
/* Access debit and credit accounts and amounts */
final MoneyWiseAssetBase myDebit = (MoneyWiseAssetBase) theTransaction.getDebitAccount();
final MoneyWiseAssetBase myCredit = (MoneyWiseAssetBase) theTransaction.getCreditAccount();
/* If the debit account is an asset */
OceanusMoney myDebitAmount = theTransaction.getDebitAmount();
if (isAsset(myDebit)) {
/* Process the debit asset bucket */
myDebitAmount = processDebitAsset(myDebit);
}
/* If the credit account is an asset */
OceanusMoney myCreditAmount = theTransaction.getCreditAmount();
if (isAsset(myCredit)) {
/* Process the credit asset bucket */
myCreditAmount = processCreditAsset(myCredit);
/* Reload the debitAmount which changes if debit is payee and credit is foreign */
myDebitAmount = theTransaction.getDebitAmount();
}
/* Adjust for currencyFluctuation */
final OceanusMoney myFluctuation = new OceanusMoney(myDebitAmount);
myFluctuation.addAmount(myCreditAmount);
if (myFluctuation.isNonZero()) {
theMarket.adjustTotalsForCurrencyFluctuation(theTransaction.getEvent(), myFluctuation);
}
}
/**
* process debit Asset.
*
* @param pDebit the debit asset
* @return the debitAmount in reporting currency
*/
OceanusMoney processDebitAsset(final MoneyWiseAssetBase pDebit) {
/* Adjust the debit asset bucket */
final MoneyWiseXAnalysisAccountBucket<?> myBucket = getAccountBucket(pDebit);
OceanusMoney myDebitAmount = theTransaction.getDebitAmount();
myBucket.addToBalance(myDebitAmount);
myBucket.adjustValuation();
theState.registerBucketInterest(myBucket);
/* If the asset is foreign, convert debit amount to reporting currency */
if (pDebit.isForeign()) {
/* convert debit amount to reporting currency */
myDebitAmount = myBucket.getDeltaValuation();
theTransaction.setDebitAmount(myDebitAmount);
}
/* Return the debit amount */
return myDebitAmount;
}
/**
* process credit Asset.
*
* @param pCredit the credit asset
* @return the creditAmount in reporting currency
*/
OceanusMoney processCreditAsset(final MoneyWiseAssetBase pCredit) {
/* Adjust the credit asset bucket */
final MoneyWiseXAnalysisAccountBucket<?> myBucket = getAccountBucket(pCredit);
OceanusMoney myCreditAmount = theTransaction.getCreditAmount();
myBucket.addToBalance(myCreditAmount);
myBucket.adjustValuation();
theState.registerBucketInterest(myBucket);
/* If the asset is foreign */
if (pCredit.isForeign()) {
/* convert credit amount to reporting currency */
myCreditAmount = myBucket.getDeltaValuation();
theTransaction.setCreditAmount(myCreditAmount);
}
/* Return the credit amount */
return myCreditAmount;
}
/**
* process autoExpense assets.
*/
private void processAutoExpense() {
/* Access debit and credit accounts and amounts */
final MoneyWiseAssetBase myDebit = (MoneyWiseAssetBase) theTransaction.getDebitAccount();
final MoneyWiseAssetBase myCredit = (MoneyWiseAssetBase) theTransaction.getCreditAccount();
/* If the debit account is auto-Expense */
if (isAutoExpense(myDebit)) {
/* Access debit as cashPayee */
processDebitAutoExpense((MoneyWiseCash) myDebit);
}
/* If the credit account is auto-Expense */
if (isAutoExpense(myCredit)) {
/* Access credit as cashPayee */
processCreditAutoExpense((MoneyWiseCash) myCredit);
}
}
/**
* process debit autoExpense asset.
*
* @param pDebit the debit asset
*/
private void processDebitAutoExpense(final MoneyWiseCash pDebit) {
/* Access debit Payee/Category auto-expense */
final MoneyWiseTransCategory myAuto = pDebit.getAutoExpense();
final MoneyWisePayee myDebit = pDebit.getAutoPayee();
/* If the debit is foreign and the credit is a payee */
if (pDebit.isForeign()) {
final MoneyWiseTransAsset myCredit = theTransaction.getCreditAccount();
if (myCredit instanceof MoneyWisePayee) {
/* Convert the debit amount to reporting currency */
final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
final OceanusRatio myRate = myCursor.getCurrentXchgRate(pDebit.getAssetCurrency());
final OceanusMoney myAmount = theTransaction.getDebitAmount();
theTransaction.setDebitAmount(myAmount.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate));
}
}
/* Adjust expense for autoPayee bucket */
final OceanusMoney myAmount = theTransaction.getDebitAmount();
final MoneyWiseXAnalysisPayeeBucket myPayee = theAnalysis.getPayees().getBucket(myDebit);
myPayee.addExpense(myAmount);
theState.registerBucketInterest(myPayee);
/* Adjust expense for Category bucket */
final MoneyWiseXAnalysisTransCategoryBucket myCategory = theAnalysis.getTransCategories().getBucket(myAuto);
myCategory.addExpense(myAmount);
theState.registerBucketInterest(myCategory);
/* Adjust expense taxBasis */
theTax.processAutoExpense(myAmount);
}
/**
* process credit autoExpense asset.
*
* @param pCredit the credit asset
*/
private void processCreditAutoExpense(final MoneyWiseCash pCredit) {
/* Access credit Payee/Category auto-expense */
final MoneyWiseTransCategory myAuto = pCredit.getAutoExpense();
final MoneyWisePayee myCredit = pCredit.getAutoPayee();
/* If the credit is foreign and the debit is a payee */
if (pCredit.isForeign()) {
final MoneyWiseTransAsset myDebit = theTransaction.getDebitAccount();
if (myDebit instanceof MoneyWisePayee) {
/* Convert the debit amount to reporting currency */
final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
final OceanusRatio myRate = myCursor.getCurrentXchgRate(pCredit.getAssetCurrency());
final OceanusMoney myAmount = theTransaction.getCreditAmount();
theTransaction.setCreditAmount(myAmount.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate));
}
}
/* Adjust expense for autoPayee bucket */
final OceanusMoney myAmount = theTransaction.getCreditAmount();
final MoneyWiseXAnalysisPayeeBucket myPayee = theAnalysis.getPayees().getBucket(myCredit);
myPayee.addExpense(myAmount);
theState.registerBucketInterest(myPayee);
/* Adjust expense for Category bucket */
final MoneyWiseXAnalysisTransCategoryBucket myCategory = theAnalysis.getTransCategories().getBucket(myAuto);
myCategory.addExpense(myAmount);
theState.registerBucketInterest(myCategory);
/* Adjust expense taxBasis */
theTax.processAutoExpense(myAmount);
}
/**
* process payee assets.
*/
private void processPayees() {
/* Access debit and credit accounts and amounts */
final MoneyWiseAssetBase myDebit = (MoneyWiseAssetBase) theTransaction.getDebitAccount();
final MoneyWiseAssetBase myCredit = (MoneyWiseAssetBase) theTransaction.getCreditAccount();
/* If the debit account is payee */
if (isPayee(myDebit)) {
/* process debit as Payee */
processDebitPayee((MoneyWisePayee) myDebit);
}
/* If the credit account is payee */
if (isPayee(myCredit)) {
/* process credit as Payee */
processCreditPayee((MoneyWisePayee) myCredit);
}
}
/**
* process debit payee asset.
*
* @param pDebit the debit asset
*/
void processDebitPayee(final MoneyWisePayee pDebit) {
/* Determine income/expense */
final boolean isExpense = theTransaction.isExpenseCategory();
/* Adjust expense for Payee bucket */
final OceanusMoney myAmount = theTransaction.getDebitAmount();
final MoneyWiseXAnalysisPayeeBucket myPayeeBucket = theAnalysis.getPayees().getBucket(pDebit);
if (isExpense) {
myPayeeBucket.addExpense(myAmount);
} else {
myPayeeBucket.subtractIncome(myAmount);
}
theState.registerBucketInterest(myPayeeBucket);
/* Record payee bucket */
theTax.recordPayeeBucket(myPayeeBucket);
}
/**
* process credit payee asset.
*
* @param pCredit the credit asset
*/
void processCreditPayee(final MoneyWisePayee pCredit) {
/* Determine income/expense */
final boolean isExpense = theTransaction.isExpenseCategory();
/* Adjust expense for Payee bucket */
final OceanusMoney myAmount = theTransaction.getCreditAmount();
final MoneyWiseXAnalysisPayeeBucket myPayeeBucket = theAnalysis.getPayees().getBucket(pCredit);
if (isExpense) {
myPayeeBucket.addExpense(myAmount);
} else {
myPayeeBucket.subtractIncome(myAmount);
}
theState.registerBucketInterest(myPayeeBucket);
/* Record payee bucket */
theTax.recordPayeeBucket(myPayeeBucket);
}
/**
* Adjust category buckets.
*/
void adjustCategoryBucket() {
/* Access the credit amount and category */
final OceanusMoney myAmount = theTransaction.isTo()
? theTransaction.getCreditAmount()
: theTransaction.getDebitAmount();
final MoneyWiseTransCategory myCategory = theTransaction.getCategory();
/* Ignore transfers */
if (myCategory.isTransfer()) {
return;
}
/* Adjust income/expense for Category bucket */
final MoneyWiseXAnalysisTransCategoryBucket myCatBucket = theAnalysis.getTransCategories().getBucket(myCategory);
if (theTransaction.isExpenseCategory()) {
myCatBucket.addExpense(myAmount);
} else if (theTransaction.isIncomeCategory()) {
myCatBucket.subtractIncome(myAmount);
}
theState.registerBucketInterest(myCatBucket);
}
/**
* adjustForeignDebit.
*
* @param pExchangeRate the exchangeRate
* @return the adjusted debitAmount
*/
OceanusMoney adjustForeignAssetDebit(final OceanusRatio pExchangeRate) {
/* Calculate the value in the local currency */
OceanusMoney myAmount = theTransaction.getDebitAmount();
myAmount = myAmount.convertCurrency(theCurrency.getCurrency(), pExchangeRate);
theTransaction.setDebitAmount(myAmount);
/* Adjust for currencyFluctuation */
final OceanusMoney myCreditAmount = theTransaction.getCreditAmount();
final OceanusMoney myFluctuation = new OceanusMoney(myCreditAmount);
myFluctuation.addAmount(myAmount);
if (myFluctuation.isNonZero()) {
theMarket.adjustTotalsForCurrencyFluctuation(theTransaction.getEvent(), myFluctuation);
}
/* return the new debit amount */
return myAmount;
}
/**
* adjustForeignCredit.
*
* @param pExchangeRate the exchangeRate
* @return the adjusted creditAmount
*/
OceanusMoney adjustForeignAssetCredit(final OceanusRatio pExchangeRate) {
/* Calculate the value in the local currency */
OceanusMoney myAmount = theTransaction.getCreditAmount();
myAmount = myAmount.convertCurrency(theCurrency.getCurrency(), pExchangeRate);
theTransaction.setCreditAmount(myAmount);
/* Adjust for currencyFluctuation */
final OceanusMoney myDebitAmount = theTransaction.getDebitAmount();
final OceanusMoney myFluctuation = new OceanusMoney(myDebitAmount);
myFluctuation.addAmount(myAmount);
if (myFluctuation.isNonZero()) {
theMarket.adjustTotalsForCurrencyFluctuation(theTransaction.getEvent(), myFluctuation);
}
/* return the new credit amount */
return myAmount;
}
/**
* Obtain Account bucket for asset.
*
* @param pAsset the asset
* @return the bucket
*/
MoneyWiseXAnalysisAccountBucket<?> getAccountBucket(final MoneyWiseAssetBase pAsset) {
switch (pAsset.getAssetType()) {
case DEPOSIT:
return theAnalysis.getDeposits().getBucket((MoneyWiseDeposit) pAsset);
case CASH:
return theAnalysis.getCash().getBucket((MoneyWiseCash) pAsset);
case LOAN:
return theAnalysis.getLoans().getBucket((MoneyWiseLoan) pAsset);
case PORTFOLIO:
return theAnalysis.getPortfolios().getCashBucket((MoneyWisePortfolio) pAsset);
default:
throw new IllegalArgumentException();
}
}
/**
* is the account an asset?
*
* @param pAccount the account
* @return true/false
*/
boolean isAsset(final MoneyWiseAssetBase pAccount) {
switch (pAccount.getAssetType()) {
case DEPOSIT:
case LOAN:
case CASH:
case PORTFOLIO:
return true;
case SECURITY:
case PAYEE:
case SECURITYHOLDING:
case AUTOEXPENSE:
default:
return false;
}
}
/**
* is the account a payee?
*
* @param pAccount the account
* @return true/false
*/
boolean isPayee(final MoneyWiseAssetBase pAccount) {
return pAccount.getAssetType() == MoneyWiseAssetType.PAYEE;
}
/**
* is the account an autoExpense?
*
* @param pAccount the account
* @return true/false
*/
private boolean isAutoExpense(final MoneyWiseAssetBase pAccount) {
return pAccount.getAssetType() == MoneyWiseAssetType.AUTOEXPENSE;
}
/**
* is the account a securityHolding?
*
* @param pAccount the account
* @return true/false
*/
static boolean isSecurityHolding(final MoneyWiseTransAsset pAccount) {
return pAccount instanceof MoneyWiseSecurityHolding;
}
/**
* is the account a securityHolding?
*
* @param pAccount the account
* @return true/false
*/
static boolean isPortfolio(final MoneyWiseTransAsset pAccount) {
return pAccount instanceof MoneyWisePortfolio;
}
/**
* is the category PortfolioXfer?
*
* @param pCategoryClass the categoryClass
* @return true/false
*/
private static boolean isPortfolioXfer(final MoneyWiseTransCategoryClass pCategoryClass) {
return pCategoryClass == MoneyWiseTransCategoryClass.PORTFOLIOXFER;
}
}