MoneyWiseValidateTransDefaults.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.data.validate;

import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase.MoneyWiseAssetBaseList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetDirection;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseCash.MoneyWiseCashList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit.MoneyWiseDepositList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoan.MoneyWiseLoanList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding.MoneyWiseSecurityHoldingMap;
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.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransaction.MoneyWiseTransactionList;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;

import java.util.Currency;
import java.util.Iterator;

/**
 * Transaction builder.
 *
 * @author Tony Washer
 */
public class MoneyWiseValidateTransDefaults {
    /**
     * Logger.
     */
    private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(MoneyWiseValidateTransDefaults.class);

    /**
     * The transaction validator.
     */
    private final MoneyWiseValidateTransaction theValidator;

    /**
     * The EditSet.
     */
    private PrometheusEditSet theEditSet;

    /**
     * The Date Range.
     */
    private OceanusDateRange theRange;

    /**
     * Constructor.
     *
     * @param pValidator the validator
     */
    public MoneyWiseValidateTransDefaults(final MoneyWiseValidateTransaction pValidator) {
        theValidator = pValidator;
    }

    /**
     * Obtain range.
     *
     * @return the date range
     */
    public OceanusDateRange getRange() {
        return theRange;
    }

    /**
     * Set range.
     *
     * @param pRange the date range
     */
    public void setRange(final OceanusDateRange pRange) {
        theRange = pRange;
    }

    /**
     * autoCorrect transaction after change.
     *
     * @param pTrans the transaction
     * @throws OceanusException on error
     */
    public void autoCorrect(final MoneyWiseTransaction pTrans) throws OceanusException {
        /* Access details */
        final MoneyWiseTransAsset myAccount = pTrans.getAccount();
        MoneyWiseTransAsset myPartner = pTrans.getPartner();
        MoneyWiseTransCategory myCategory = pTrans.getCategory();
        final MoneyWiseAssetDirection myDir = pTrans.getDirection();
        final OceanusMoney myAmount = pTrans.getAmount();
        final Currency myCurrency = myAccount.getCurrency();
        theEditSet = theValidator.getEditSet();

        /* Check that category is valid */
        if (!theValidator.isValidCategory(myAccount, myCategory)) {
            /* Determine valid category */
            myCategory = getDefaultCategoryForAccount(myAccount);
            pTrans.setCategory(myCategory);
        }

        /* Check that direction is valid */
        if (!theValidator.isValidDirection(myAccount, myCategory, myDir)) {
            /* Reverse direction */
            pTrans.switchDirection();
        }

        /* Check that partner is valid */
        if (!theValidator.isValidPartner(myAccount, myCategory, myPartner)) {
            /* Determine valid partner */
            myPartner = getDefaultPartnerForAccountAndCategory(myAccount, myCategory);
            pTrans.setPartner(myPartner);
        }

        /* If we need to null money */
        if (myCategory.getCategoryTypeClass().needsNullAmount()) {
            if (myAmount != null) {
                /* Create a zero amount */
                pTrans.setAmount(null);
            }

            /* Else money is required */
        } else {
            if (myAmount == null) {
                /* Create a zero amount */
                pTrans.setAmount(new OceanusMoney(myCurrency));

                /* If we need to change currency */
            } else if (!myCurrency.equals(myAmount.getCurrency())) {
                /* Convert the currency */
                pTrans.setAmount(myAmount.changeCurrency(myCurrency));
            }
        }

        /* AutoCorrect the InfoSet */
        final MoneyWiseValidateTransInfoSet myInfoSet = theValidator.getInfoSetValidator();
        myInfoSet.autoCorrect(pTrans.getInfoSet());
    }

    /**
     * Build empty transaction.
     *
     * @return the new transaction
     */
    private MoneyWiseTransaction newTransaction() {
        /* Obtain a new transaction */
        return new MoneyWiseTransaction(theEditSet.getDataList(MoneyWiseBasicDataType.TRANSACTION, MoneyWiseTransactionList.class));
    }

    /**
     * Build default transaction.
     *
     * @param pKey the key to base the new transaction around (or null)
     * @return the new transaction (or null if no possible transaction)
     */
    public MoneyWiseTransaction buildTransaction(final Object pKey) {
        /* Protect against exceptions */
        try {
            theEditSet = theValidator.getEditSet();
            if (pKey == null) {
                /* Build default transaction */
                return buildDefaultTransaction();
            }
            if (pKey instanceof MoneyWisePayee myPayee) {
                /* Build default payee transaction */
                return buildDefaultTransactionForPayee(myPayee);
            }
            if (pKey instanceof MoneyWiseSecurityHolding myHolding) {
                /* Build default holding transaction */
                return buildDefaultTransactionForHolding(myHolding);
            }
            if (pKey instanceof MoneyWiseTransAsset myAsset) {
                /* Build default account transaction */
                return buildDefaultTransactionForAccount(myAsset);
            }
            if (pKey instanceof MoneyWiseTransCategory myCategory) {
                /* Build default category transaction */
                return buildDefaultTransactionForCategory(myCategory);
            }
        } catch (OceanusException e) {
            LOGGER.error("Unable to build transaction", e);
        }

        /* Unrecognised key */
        return null;
    }

    /**
     * Build standard details.
     *
     * @param pTrans the transaction to build
     * @throws OceanusException on error
     */
    private void buildStandardDetails(final MoneyWiseTransaction pTrans) throws OceanusException {
        /* Access standard range */
        final OceanusDateRange myRange = theRange;

        /* Set default direction */
        pTrans.setDirection(MoneyWiseAssetDirection.TO);

        /* Determine date */
        OceanusDate myDate = new OceanusDate();
        final int iResult = myRange.compareToDate(myDate);
        if (iResult < 0) {
            myDate = myRange.getEnd();
        } else if (iResult > 0) {
            myDate = myRange.getStart();
        }
        pTrans.setDate(myDate);

        /* Create a zero amount */
        final MoneyWiseTransAsset myAccount = pTrans.getAccount();
        final Currency myCurrency = myAccount.getCurrency();
        pTrans.setAmount(new OceanusMoney(myCurrency));
    }

    /**
     * Build default transaction.
     *
     * @return the valid transaction (or null)
     * @throws OceanusException on error
     */
    private MoneyWiseTransaction buildDefaultTransaction() throws OceanusException {
        final MoneyWiseTransCategory myCategory = getDefaultCategory();
        if (myCategory == null) {
            return null;
        }

        /* Look for transaction for this category */
        return buildDefaultTransactionForCategory(myCategory);
    }

    /**
     * Build default transaction for payee.
     *
     * @param pPayee the payee to build for
     * @return the valid transaction (or null)
     * @throws OceanusException on error
     */
    private MoneyWiseTransaction buildDefaultTransactionForPayee(final MoneyWisePayee pPayee) throws OceanusException {
        /* Check for closed/hidden payee */
        if (pPayee.isClosed() || pPayee.isHidden()) {
            return null;
        }

        /* Build an empty transaction */
        final MoneyWiseTransaction myTrans = newTransaction();

        /* Record the payee */
        myTrans.setPartner(pPayee);

        /* Build default category */
        final MoneyWiseTransCategory myCategory = getDefaultCategory();
        if (myCategory == null) {
            return null;
        }
        myTrans.setCategory(myCategory);

        /* Build default account */
        final MoneyWiseTransAsset myAccount = getDefaultAccountForCategory(myCategory);
        if (myAccount == null) {
            return null;
        }
        myTrans.setAccount(myAccount);

        /* Check that we are valid after all this */
        if (!theValidator.isValidPartner(myAccount, myCategory, pPayee)) {
            return null;
        }

        /* build standard details */
        buildStandardDetails(myTrans);

        /* AutoCorrect the transaction */
        autoCorrect(myTrans);

        /* Return the new transaction */
        return myTrans;
    }

    /**
     * Build default transaction for category.
     *
     * @param pCategory the category to build for
     * @return the valid transaction (or null)
     * @throws OceanusException on error
     */
    private MoneyWiseTransaction buildDefaultTransactionForCategory(final MoneyWiseTransCategory pCategory) throws OceanusException {
        /* Check for hidden category */
        if (pCategory.isHidden()) {
            return null;
        }

        /* Build an empty transaction */
        final MoneyWiseTransaction myTrans = newTransaction();

        /* Record the category */
        myTrans.setCategory(pCategory);

        /* Build default account category */
        final MoneyWiseTransAsset myAccount = getDefaultAccountForCategory(pCategory);
        if (myAccount == null) {
            return null;
        }
        myTrans.setAccount(myAccount);

        /* Build default partner */
        final MoneyWiseTransAsset myPartner = getDefaultPartnerForAccountAndCategory(myAccount, pCategory);
        if (myPartner == null) {
            return null;
        }
        myTrans.setPartner(myPartner);

        /* build standard details */
        buildStandardDetails(myTrans);

        /* AutoCorrect the transaction */
        autoCorrect(myTrans);

        /* Return the new transaction */
        return myTrans;
    }

    /**
     * Build default transaction for Deposit/Loan/Cash.
     *
     * @param pAccount the Deposit/Loan/Cash to build for
     * @return the valid transaction (or null)
     * @throws OceanusException on error
     */
    private MoneyWiseTransaction buildDefaultTransactionForAccount(final MoneyWiseTransAsset pAccount) throws OceanusException {
        /* Check for closed account */
        if (pAccount.isClosed()) {
            return null;
        }

        /* Build an empty transaction */
        final MoneyWiseTransaction myTrans = newTransaction();

        /* Record the account */
        myTrans.setAccount(pAccount);

        /* Build default expense category */
        final MoneyWiseTransCategory myCategory = getDefaultCategory();
        if (myCategory == null) {
            return null;
        }
        myTrans.setCategory(myCategory);

        /* Build default partner */
        final MoneyWiseTransAsset myPartner = getDefaultPartnerForAccountAndCategory(pAccount, myCategory);
        if (myPartner == null) {
            return null;
        }
        myTrans.setPartner(myPartner);

        /* build standard details */
        buildStandardDetails(myTrans);

        /* AutoCorrect the transaction */
        autoCorrect(myTrans);

        /* Return the new transaction */
        return myTrans;
    }

    /**
     * Build default transaction for securityHolding.
     *
     * @param pHolding the SecurityHolding to build for
     * @return the valid transaction (or null)
     * @throws OceanusException on error
     */
    private MoneyWiseTransaction buildDefaultTransactionForHolding(final MoneyWiseSecurityHolding pHolding) throws OceanusException {
        /* Check for closed holding */
        if (pHolding.isClosed()) {
            return null;
        }

        /* Build an empty transaction */
        final MoneyWiseTransaction myTrans = newTransaction();

        /* Record the account */
        myTrans.setAccount(pHolding);

        /* Build default category */
        final MoneyWiseTransCategory myCategory = getDefaultCategoryForAccount(pHolding);
        myTrans.setCategory(myCategory);

        /* Build default partner */
        final MoneyWiseTransAsset myPartner = getDefaultPartnerForAccountAndCategory(pHolding, myCategory);
        if (myPartner == null) {
            return null;
        }
        myTrans.setPartner(myPartner);

        /* build standard details */
        buildStandardDetails(myTrans);

        /* AutoCorrect the transaction */
        autoCorrect(myTrans);

        /* Return the new transaction */
        return myTrans;
    }

    /**
     * Obtain default account for category.
     *
     * @param pCategory the category
     * @return the default account
     */
    private MoneyWiseTransAsset getDefaultAccountForCategory(final MoneyWiseTransCategory pCategory) {
        /* Try deposits/cash/loans */
        MoneyWiseTransAsset myAccount = getDefaultAssetForCategory(theEditSet.getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class), pCategory);
        if (myAccount == null) {
            myAccount = getDefaultAssetForCategory(theEditSet.getDataList(MoneyWiseBasicDataType.CASH, MoneyWiseCashList.class), pCategory);
        }
        if (myAccount == null) {
            myAccount = getDefaultAssetForCategory(theEditSet.getDataList(MoneyWiseBasicDataType.LOAN, MoneyWiseLoanList.class), pCategory);
        }

        /* Try holdings */
        if (myAccount == null) {
            myAccount = getDefaultHolding(pCategory);
        }

        /* Try portfolios */
        if (myAccount == null) {
            myAccount = getDefaultAssetForCategory(theEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class), pCategory);
        }

        /* Return the account */
        return myAccount;
    }

    /**
     * Obtain default category.
     *
     * @return the default category
     */
    private MoneyWiseTransCategory getDefaultCategory() {
        /* Look for category in order of expense, income, transfer */
        MoneyWiseTransCategory myCategory = getDefaultCategory(CategoryType.EXPENSE);
        if (myCategory == null) {
            myCategory = getDefaultCategory(CategoryType.INCOME);
        }
        if (myCategory == null) {
            myCategory = getDefaultCategory(CategoryType.TRANSFER);
        }

        /* Return category */
        return myCategory;
    }

    /**
     * Obtain default category.
     *
     * @param pType the category type
     * @return the default category
     */
    private MoneyWiseTransCategory getDefaultCategory(final CategoryType pType) {
        /* Access Categories */
        final MoneyWiseTransCategoryList myCategories = theEditSet.getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class);

        /* Loop through the available category values */
        final Iterator<MoneyWiseTransCategory> myIterator = myCategories.iterator();
        while (myIterator.hasNext()) {
            final MoneyWiseTransCategory myCategory = myIterator.next();

            /* Only process non-deleted low-level items */
            final MoneyWiseTransCategoryClass myClass = myCategory.getCategoryTypeClass();
            if (myCategory.isDeleted() || myClass.canParentCategory()) {
                continue;
            }

            /* Switch on type */
            switch (pType) {
                case EXPENSE:
                    if (myClass.isExpense()) {
                        return myCategory;
                    }
                    break;
                case INCOME:
                    if (myClass.isIncome()) {
                        return myCategory;
                    }
                    break;
                case TRANSFER:
                default:
                    if (myClass.isTransfer()) {
                        return myCategory;
                    }
                    break;
            }
        }

        /* No category available */
        return null;
    }

    /**
     * Obtain default category for account.
     *
     * @param pAccount the account
     * @return the default category
     */
    private MoneyWiseTransCategory getDefaultCategoryForAccount(final MoneyWiseTransAsset pAccount) {
        /* Access Categories */
        final MoneyWiseTransCategoryList myCategories = theEditSet.getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class);

        /* Loop through the available category values */
        final Iterator<MoneyWiseTransCategory> myIterator = myCategories.iterator();
        while (myIterator.hasNext()) {
            final MoneyWiseTransCategory myCategory = myIterator.next();

            /* Only process non-deleted low-level items */
            final MoneyWiseTransCategoryClass myClass = myCategory.getCategoryTypeClass();
            if (myCategory.isDeleted() || myClass.canParentCategory()) {
                continue;
            }

            /* Check whether the category is allowable for the owner */
            if (theValidator.isValidCategory(pAccount, myCategory)) {
                return myCategory;
            }
        }

        /* No category available */
        throw new IllegalArgumentException();
    }

    /**
     * Obtain default partner for account and category.
     *
     * @param pAccount  the account
     * @param pCategory the category
     * @return the default partner
     */
    private MoneyWiseTransAsset getDefaultPartnerForAccountAndCategory(final MoneyWiseTransAsset pAccount,
                                                                       final MoneyWiseTransCategory pCategory) {
        /* Try Payees */
        MoneyWiseTransAsset myPartner = getDefaultPartnerAsset(theEditSet.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class), pAccount, pCategory);

        /* Try deposits/cash/loans */
        if (myPartner == null) {
            myPartner = getDefaultPartnerAsset(theEditSet.getDataList(MoneyWiseBasicDataType.DEPOSIT, MoneyWiseDepositList.class), pAccount, pCategory);
        }
        if (myPartner == null) {
            myPartner = getDefaultPartnerAsset(theEditSet.getDataList(MoneyWiseBasicDataType.CASH, MoneyWiseCashList.class), pAccount, pCategory);
        }
        if (myPartner == null) {
            myPartner = getDefaultPartnerAsset(theEditSet.getDataList(MoneyWiseBasicDataType.LOAN, MoneyWiseLoanList.class), pAccount, pCategory);
        }

        /* Try portfolios */
        if (myPartner == null) {
            myPartner = getDefaultPartnerAsset(theEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class), pAccount, pCategory);
        }

        /* Try holdings */
        if (myPartner == null) {
            myPartner = getDefaultPartnerHolding(pAccount, pCategory);
        }

        /* Return the partner */
        return myPartner;
    }

    /**
     * Obtain the default account from an asset list.
     *
     * @param <X>       the Asset type
     * @param pList     the list to select from
     * @param pCategory the category
     * @return the default partner or null
     */
    private <X extends MoneyWiseAssetBase> MoneyWiseTransAsset getDefaultAssetForCategory(final MoneyWiseAssetBaseList<X> pList,
                                                                                          final MoneyWiseTransCategory pCategory) {
        /* Loop through the available values */
        final Iterator<X> myIterator = pList.iterator();
        while (myIterator.hasNext()) {
            final X myAsset = myIterator.next();

            /* Only process non-deleted, non-closed items */
            if (myAsset.isDeleted() || myAsset.isClosed()) {
                continue;
            }

            /* Check whether the asset is allowable for the owner */
            if (theValidator.isValidCategory(myAsset, pCategory)) {
                return myAsset;
            }
        }

        /* No asset available */
        return null;
    }

    /**
     * Obtain the default partner from an asset list.
     *
     * @param <X>       the Asset type
     * @param pList     the list to select from
     * @param pAccount  the account
     * @param pCategory the category
     * @return the default partner or null
     */
    private <X extends MoneyWiseAssetBase> MoneyWiseTransAsset getDefaultPartnerAsset(final MoneyWiseAssetBaseList<X> pList,
                                                                                      final MoneyWiseTransAsset pAccount,
                                                                                      final MoneyWiseTransCategory pCategory) {
        /* Loop through the available values */
        final Iterator<X> myIterator = pList.iterator();
        while (myIterator.hasNext()) {
            final X myAsset = myIterator.next();

            /* Only process non-deleted, non-closed items */
            if (myAsset.isDeleted() || myAsset.isClosed()) {
                continue;
            }

            /* Check whether the asset is allowable for the owner */
            if (theValidator.isValidPartner(pAccount, pCategory, myAsset)) {
                return myAsset;
            }
        }

        /* No asset available */
        return null;
    }

    /**
     * Obtain the default security holding from the security map.
     *
     * @param pCategory the category
     * @return the default partner
     */
    private MoneyWiseSecurityHolding getDefaultHolding(final MoneyWiseTransCategory pCategory) {
        /* Access Portfolios and Holdings Map */
        final MoneyWisePortfolioList myPortfolios = theEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
        final MoneyWiseSecurityHoldingMap myMap = myPortfolios.getSecurityHoldingsMap();

        /* Loop through the Portfolios */
        final Iterator<MoneyWisePortfolio> myPortIterator = myPortfolios.iterator();
        while (myPortIterator.hasNext()) {
            final MoneyWisePortfolio myPortfolio = myPortIterator.next();

            /* Ignore deleted or closed */
            if (myPortfolio.isDeleted() || myPortfolio.isClosed()) {
                continue;
            }

            /* Look for existing holdings */
            final Iterator<MoneyWiseSecurityHolding> myExistIterator = myMap.existingIterator(myPortfolio);
            if (myExistIterator != null) {
                /* Loop through them */
                while (myExistIterator.hasNext()) {
                    final MoneyWiseSecurityHolding myHolding = myExistIterator.next();

                    /* Check whether the asset is allowable for the combination */
                    if (theValidator.isValidCategory(myHolding, pCategory)) {
                        return myHolding;
                    }
                }
            }
        }

        /* No holding available */
        return null;
    }

    /**
     * Obtain the default partner security holding from the security map.
     *
     * @param pAccount  the account
     * @param pCategory the category
     * @return the default partner
     */
    private MoneyWiseSecurityHolding getDefaultPartnerHolding(final MoneyWiseTransAsset pAccount,
                                                              final MoneyWiseTransCategory pCategory) {
        /* Access Portfolios and Holdings Map */
        final MoneyWisePortfolioList myPortfolios = theEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
        final MoneyWiseSecurityHoldingMap myMap = myPortfolios.getSecurityHoldingsMap();

        /* Loop through the Portfolios */
        final Iterator<MoneyWisePortfolio> myPortIterator = myPortfolios.iterator();
        while (myPortIterator.hasNext()) {
            final MoneyWisePortfolio myPortfolio = myPortIterator.next();

            /* Ignore deleted or closed */
            if (myPortfolio.isDeleted() || myPortfolio.isClosed()) {
                continue;
            }

            /* Look for existing holdings */
            final Iterator<MoneyWiseSecurityHolding> myExistIterator = myMap.existingIterator(myPortfolio);
            if (myExistIterator != null) {
                /* Loop through them */
                while (myExistIterator.hasNext()) {
                    final MoneyWiseSecurityHolding myHolding = myExistIterator.next();

                    /* Check whether the asset is allowable for the combination */
                    if (theValidator.isValidPartner(pAccount, pCategory, myHolding)) {
                        return myHolding;
                    }
                }
            }
        }

        /* No holding available */
        return null;
    }

    /**
     * Category types.
     */
    private enum CategoryType {
        /**
         * Expense.
         */
        EXPENSE,

        /**
         * Income.
         */
        INCOME,

        /**
         * Transfer.
         */
        TRANSFER;
    }
}