/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#include <pecunia/Codes.h>
using pecunia::currency::Iso4217Codes;
#include <pecunia/Money.h>
using pecunia::currency::Money;
#include <QDate>
using ::QDate;
#include <QTest>
using ::QObject;
// using QTEST_MAIN
#include <qt-mocks/Matchers.h>
using qt_mocks::equals;
using qt_mocks::isFalse;
using qt_mocks::isTrue;
// using QVERIFY_FALSE
// using QVERIFY_THAT
// using QVERIFY_TRUE
// using QVERIFY_EXCEPTION_THROWN

#include <chrono>
using std::chrono::system_clock;
#include <map>
using std::map;
#include <set>
using std::set;
#include <string>
using namespace std::string_literals;
using std::string;

#include <accounting/Account.h>
using drn::accounting::Account;
using drn::accounting::AccountTypes;
#include <accounting/AccountCode.h>
using drn::accounting::AccountCode;
using drn::accounting::AccountNumber;
#include <accounting/AccountNumberGenerator.h>
using drn::accounting::SequentialAccountNumberGenerator;
#include <accounting/AccountingErrors.h>
using drn::accounting::AccountError;
#include <accounting/LedgerBooks.h>
using drn::accounting::GeneralLedger;
using drn::accounting::LedgerAccount;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
#include <banking/Bank.h>
using drn::banking::Bank;
#include <banking/BankAccountType.h>
using drn::banking::BankAccountType;
using drn::banking::SupportedAccountTypes;
#include <banking/BankingErrors.h>
using drn::banking::BankError;
#include <banking/BankMap.h>
using drn::banking::BankMap;
#include <banking/BankName.h>
using drn::banking::BankName;
#include "internal/BankLedgers.h"
using drn::navigation::internal::BankLedgers;
#include <foundation/CalendarTime.hpp>
using drn::foundation::Days;


namespace drn
{
namespace navigation
{
namespace internal
{
namespace tests
{

struct BankLedgersUT : QObject
{
	Q_OBJECT

private slots:
	void init()
	{
		this->sang_ = AccountNumber{};
		this->banks_.clear();
		this->ledgers_ = {};
		this->ba_.clearBankAccountType();
	}

	//{ Constructor Tests

	void constructor_fullInit_ShouldBeValues()
	{
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		const Account wendys{
			AccountTypes::Revenue,
			wendysAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(wendys, this->sang_);
		const BankName hfName{"Home Federal"};
		const AccountCode hfPrimaryAccountCode{AccountNumber{3}, "Primary"};
		const AccountCode hfSavingsAccountCode{AccountNumber{4}, "Savings"};
		const Account hfPrimaryAccount{
			AccountTypes::Asset,
			hfPrimaryAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Account hfSavingsAccount{
			AccountTypes::Asset,
			hfSavingsAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(hfPrimaryAccount, this->sang_);
		this->ledgers_.add(hfSavingsAccount, this->sang_);
		const Bank hf{hfName, {hfPrimaryAccountCode.number(), hfSavingsAccountCode.number()}};
		this->banks_.add(hf);
		const BankName c1stName{"Community 1st Credit Union"};
		const AccountCode c1stPrimaryAccountCode{AccountNumber{5}, "Primary"};
		const AccountCode c1stSavingsAccountCode{AccountNumber{6}, "Savings"};
		const Account c1stPrimaryAccount{
			AccountTypes::Asset,
			c1stPrimaryAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Account c1stSavingsAccount{
			AccountTypes::Asset,
			c1stSavingsAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(c1stPrimaryAccount, this->sang_);
		this->ledgers_.add(c1stSavingsAccount, this->sang_);
		const Bank c1st{
			c1stName,
			{c1stPrimaryAccountCode.number(), c1stSavingsAccountCode.number()}
		};
		this->banks_.add(c1st);
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				{
					hfName,
					{
						{
							hfPrimaryAccountCode.number(),
							SupportedAccountTypes::Chequeing
						},
						{
							hfSavingsAccountCode.number(),
							SupportedAccountTypes::Savings
						}
					}

				},
				{
					c1stName,
					{
						{
							c1stPrimaryAccountCode.number(),
							SupportedAccountTypes::Chequeing
						},
						{
							c1stSavingsAccountCode.number(),
							SupportedAccountTypes::Savings
						}
					}
				}
			}
		};
		const BankLedgers bm{&this->banks_, &this->ledgers_, types};
		QVERIFY_THAT(bm.banks(), equals(this->banks_));
		QVERIFY_THAT(bm.ledgers(), equals(this->ledgers_));
		QVERIFY_THAT(bm.bankAccountTypes(), equals(types));
	}

	void constructor_duplicateAccounts_ShouldThrow()
	{
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				BankName{"Home Federal"},
				{
					{
						AccountNumber{1u},
						SupportedAccountTypes::Savings
					}
				}
			},
			{
				BankName{"Community 1st Credit Union"},
				{
					{
						AccountNumber{1u},
						SupportedAccountTypes::Savings
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}

	void constructor_bankTypeUnknown_ShouldThrow()
	{
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				BankName{"Home Federal"},
				{
					{
						AccountNumber{1u},
						SupportedAccountTypes::Unknown
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}

	void constructor_noAssociations_ShouldThrow()
	{
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{BankName{"Home Federal"}, {}}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}

	void constructor_defaultAccount_ShouldThrow()
	{
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				BankName{"Home Federal"},
				{
					{
						AccountNumber{},
						SupportedAccountTypes::Savings
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}

	void constructor_bankNameInTypeDoesNotExist_ShouldThrow()
	{
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		const Account wendys{
			AccountTypes::Revenue,
			wendysAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(wendys, this->sang_);
		const BankName hfName{"Home Federal"};
		const AccountCode hfAccountCode{AccountNumber{2}, "Primary"};
		const Account hfAccount{AccountTypes::Asset, hfAccountCode, Money{12, 2200u, Iso4217Codes::USD}};
		const Bank hf{hfName, {hfAccountCode.number()}};
		this->banks_.add(hf);
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				BankName{"Community 1st"},
				{
					{
						AccountNumber{1u},
						SupportedAccountTypes::Savings
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}
	void constructor_accountNumberInTypeDoesNotExist_ShouldThrow()
	{
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		const Account wendys{
			AccountTypes::Revenue,
			wendysAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(wendys, this->sang_);
		const BankName hfName{"Home Federal"};
		const AccountCode hfAccountCode{AccountNumber{2}, "Primary"};
		const Account hfAccount{AccountTypes::Asset, hfAccountCode, Money{12, 2200u, Iso4217Codes::USD}};
		const Bank hf{hfName, {hfAccountCode.number()}};
		this->banks_.add(hf);
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				BankName{"Home Federal"},
				{
					{
						AccountNumber{1234u},
						SupportedAccountTypes::Savings
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}
	void constructor_accountNumberReUsedInBanktype_ShouldThrow()
	{
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		const Account wendys{
			AccountTypes::Revenue,
			wendysAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(wendys, this->sang_);
		const BankName hfName{"Home Federal"};
		const AccountCode hfPrimaryAccountCode{AccountNumber{3}, "Primary"};
		const AccountCode hfSavingsAccountCode{AccountNumber{4}, "Savings"};
		const Account hfPrimaryAccount{
			AccountTypes::Asset,
			hfPrimaryAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Account hfSavingsAccount{
			AccountTypes::Asset,
			hfSavingsAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(hfPrimaryAccount, this->sang_);
		this->ledgers_.add(hfSavingsAccount, this->sang_);
		const Bank hf{hfName, {hfPrimaryAccountCode.number(), hfSavingsAccountCode.number()}};
		this->banks_.add(hf);
		const BankName c1stName{"Community 1st Credit Union"};
		const AccountCode c1stPrimaryAccountCode{AccountNumber{5}, "Primary"};
		const AccountCode c1stSavingsAccountCode{AccountNumber{6}, "Savings"};
		const Account c1stPrimaryAccount{
			AccountTypes::Asset,
			c1stPrimaryAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Account c1stSavingsAccount{
			AccountTypes::Asset,
			c1stSavingsAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		this->ledgers_.add(c1stPrimaryAccount, this->sang_);
		this->ledgers_.add(c1stSavingsAccount, this->sang_);
		const Bank c1st{
			c1stName,
			{c1stPrimaryAccountCode.number(), c1stSavingsAccountCode.number()}
		};
		this->banks_.add(c1st);
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				{
					hfName,
					{
						{
							hfPrimaryAccountCode.number(),
							SupportedAccountTypes::Chequeing
						},
						{
							hfSavingsAccountCode.number(),
							SupportedAccountTypes::Savings
						}
					}

				},
				{
					c1stName,
					{
						{
							c1stPrimaryAccountCode.number(),
							SupportedAccountTypes::Chequeing
						},
						{
							hfSavingsAccountCode.number(),
							SupportedAccountTypes::Savings
						}
					}
				}
			}
		};
		QVERIFY_EXCEPTION_THROWN(BankLedgers(&this->banks_, &this->ledgers_, types), BankError);
	}

	//}

	//{ add Tests

	void add_validParamsNoAccounts_ShouldBeAdded()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
	}

	void add_validParamsHasAccounts_ShouldBeAdded()
	{
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		const Account wendys{AccountTypes::Asset, wendysAccountCode, Money{12, 2200u, Iso4217Codes::USD}};
		this->ledgers_.add(wendys, this->sang_);
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName, {wendysAccountCode.number()}};
		QVERIFY_NO_THROW(
			this->ba_.add(homeFederal, {{wendysAccountCode.number(), SupportedAccountTypes::Savings}})
		);
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.bankAccountTypes().count(hfName), equals(static_cast<size_t>(1)));
		const auto accountCodesBankTypes{this->ba_.bankAccountTypes().at(hfName)};
		QVERIFY_THAT(
			accountCodesBankTypes.count(wendysAccountCode.number()),
			equals(static_cast<size_t>(1))
		);
		QVERIFY_THAT(
			accountCodesBankTypes.at(wendysAccountCode.number()),
			equals(SupportedAccountTypes::Savings)
		);
	}

	void add_bankAlreadyExists_ShouldThorw()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		QVERIFY_EXCEPTION_THROWN(this->ba_.add(homeFederal), BankError);
	}

	void add_accountAssoicatedWithAnotherBank_ShouldThrow()
	{
		const BankName c1stName{"Community First Credit Union"};
		const Bank c1st{c1stName};
		this->ba_.add(c1st);
		const AccountCode wendysAccountCode{AccountNumber{1}, "Wendy's"};
		this->ba_.addAccount(
			c1stName,
			wendysAccountCode.name(),
			SupportedAccountTypes::Savings,
			Money{0, 0u, Iso4217Codes::USD},
			this->sang_
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1)));
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName, {wendysAccountCode.number()}};
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.add(homeFederal),
			BankError
		);
	}

	void add_accountsNotInGeneralLedger_ShouldBeThrown()
	{
		const BankName hfName{"Home Federal"};
		const AccountCode savingsAccountCode{AccountNumber{1010u}, "Savings"};
		const AccountCode chequeingAccountCode{AccountNumber{1020u}, "Current Account"};
		const Bank homeFederal{hfName, {savingsAccountCode.number(), chequeingAccountCode.number()}};
		QVERIFY_EXCEPTION_THROWN(this->ba_.add(homeFederal), BankError);
	}

	//}

	//{ addAccount Tests

	void addAccount_validParams_ShouldBeAdded()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		const Money openingBalance{1500, 2500u, Iso4217Codes::USD};
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		const map<TransactionNumber, Transaction> transactions{
			{
				TransactionNumber{1u},
				Transaction{
					QDate::currentDate(),
					savingsAccountCode,
					AccountCode{AccountNumber{2u}, QStringLiteral("Opening Balance")},
					openingBalance,
					QObject::tr("Starting Balance."),
					TransactionNumber{1u}
				}
			}
		};
		homeFederal.add(savingsAccountCode.number());
		const LedgerAccount savingsLedger{
			Account{AccountTypes::Asset, savingsAccountCode, openingBalance}, transactions
		};
		QVERIFY_NO_THROW(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				savingsLedger.account_.balance(),
				this->sang_
			)
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(this->ba_.ledgers().ledger(savingsAccountCode), equals(savingsLedger));
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> expectedSupportedAccountTypes{
			{
				hfName,
				{
					{
						savingsAccountCode.number(),
						SupportedAccountTypes::Savings
					}
				}
			}
		};
		QVERIFY_THAT(this->ba_.bankAccountTypes(), equals(expectedSupportedAccountTypes));
	}

	void addAccount_bankDoesNotExist_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				Money{Iso4217Codes::USD},
				this->sang_
			),
			BankError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_THAT(this->ba_.banks().hasBank(hfName), isFalse());
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isFalse());
	}

	void addAccount_accountNameIsEmpty_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				hfName,
				"   ",
				SupportedAccountTypes::Savings,
				Money{Iso4217Codes::USD},
				this->sang_
			),
			AccountError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(AccountCode{AccountNumber{1u}}), isFalse());
	}

	void addAccount_accountNameAlreadyExistsInSameBank_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		homeFederal.add(savingsAccountCode.number());
		const LedgerAccount savingsLedger{
			Account{AccountTypes::Asset, savingsAccountCode, Iso4217Codes::USD}, {}
		};
		this->sang_ = this->sang_.current({});
		QVERIFY_NO_THROW(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				savingsLedger.account_.balance(),
				this->sang_
			)
		);
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				Money{Iso4217Codes::PLN},
				this->sang_
			),
			BankError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(this->ba_.ledgers().ledger(savingsAccountCode), equals(savingsLedger));
	}

	void addAccount_accountNameAlreadyExistsInDifferentBank_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		const BankName c1stName{"Community 1st Credit Union"};
		Bank c1st{c1stName};
		QVERIFY_NO_THROW(this->ba_.add(c1st));
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		homeFederal.add(savingsAccountCode.number());
		const LedgerAccount savingsLedger{
			Account{AccountTypes::Asset, savingsAccountCode, Iso4217Codes::USD}, {}
		};
		this->sang_ = this->sang_.current({});
		QVERIFY_NO_THROW(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				savingsLedger.account_.balance(),
				this->sang_
			)
		);
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				c1stName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				Money{Iso4217Codes::PLN},
				this->sang_
			),
			BankError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(this->ba_.ledgers().ledger(savingsAccountCode), equals(savingsLedger));
		QVERIFY_THAT(this->ba_.banks().bank(c1stName), equals(c1st));
	}

	void addAccount_bankAccountTypeIsUnknown_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Unknown,
				Money{Iso4217Codes::USD},
				this->sang_
			),
			AccountError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isFalse());
	}

	void addAccount_startingBalanceCurrencyIsUnknown_ShouldThrow()
	{
		const BankName hfName{"Home Federal"};
		const Bank homeFederal{hfName};
		QVERIFY_NO_THROW(this->ba_.add(homeFederal));
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		QVERIFY_EXCEPTION_THROWN(
			this->ba_.addAccount(
				hfName,
				savingsAccountCode.name(),
				SupportedAccountTypes::Savings,
				Money{Iso4217Codes::XXX},
				this->sang_
			),
			AccountError
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_THAT(this->ba_.banks().bank(hfName), equals(homeFederal));
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isFalse());
	}

	//}

	//{ close Tests

	void close_hasAccount_ShouldClose()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		this->ba_.add(homeFederal);
		const Money openingBalance{1500, 2500u, Iso4217Codes::USD};
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		const map<TransactionNumber, Transaction> transactions{
			{
				TransactionNumber{1u},
				Transaction{
					QDate::currentDate(),
					savingsAccountCode,
					AccountCode{AccountNumber{2u}, QStringLiteral("Opening Balance")},
					openingBalance,
					QObject::tr("Starting Balance."),
					TransactionNumber{1u}
				}
			}
		};
		homeFederal.add(savingsAccountCode.number());
		const LedgerAccount savingsLedger{
			Account{AccountTypes::Asset, savingsAccountCode, openingBalance}, transactions
		};
		this->ba_.addAccount(
			hfName,
			savingsAccountCode.name(),
			SupportedAccountTypes::Savings,
			savingsLedger.account_.balance(),
			this->sang_
		);
		this->ba_.close(hfName);
		QVERIFY_THAT(this->ba_.banks().bank(hfName).isClosed(), isTrue());
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(
			this->ba_.ledgers().ledger(savingsAccountCode).account_.isClosed(),
			isTrue()
		);
	}

	void close_hasAccountNotCloseable_ShouldNotClose()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		this->ba_.add(homeFederal);
		const Money openingBalance{1500, 2500u, Iso4217Codes::USD};
		const AccountCode savings{AccountNumber{1u}, "Savings"};
		this->ba_.addAccount(
			hfName,
				savings.name(),
			SupportedAccountTypes::Savings,
			openingBalance,
			this->sang_
		);
		SequentialAccountNumberGenerator gen{};
		const AccountCode computer{AccountNumber{3}, "New Computer", savings.number()};
		this->ledgers_.add(
			Account{AccountTypes::Asset, computer, Iso4217Codes::USD},
			gen
		);
		QVERIFY_EXCEPTION_THROWN(this->ba_.close(hfName), AccountError);
		QVERIFY_FALSE(this->ba_.banks().bank(hfName).isClosed());
		QVERIFY_TRUE(this->ba_.ledgers().hasLedger(savings));
		QVERIFY_FALSE(this->ba_.ledgers().ledger(savings).account_.isClosed());
	}

	//}

	//{ open Tests

	void open_hasAccount_ShouldOpen()
	{
		const BankName hfName{"Home Federal"};
		Bank homeFederal{hfName};
		this->ba_.add(homeFederal);
		const Money openingBalance{1500, 2500u, Iso4217Codes::USD};
		const AccountCode savingsAccountCode{AccountNumber{1u}, "Savings"};
		const map<TransactionNumber, Transaction> transactions{
			{
				TransactionNumber{1u},
				Transaction{
					QDate::currentDate(),
					savingsAccountCode,
					AccountCode{AccountNumber{2u}, QStringLiteral("Opening Balance")},
					openingBalance,
					QObject::tr("Starting Balance."),
					TransactionNumber{1u}
				}
			}
		};
		homeFederal.add(savingsAccountCode.number());
		const LedgerAccount savingsLedger{
			Account{AccountTypes::Asset, savingsAccountCode, openingBalance}, transactions
		};
		this->ba_.addAccount(
			hfName,
			savingsAccountCode.name(),
			SupportedAccountTypes::Savings,
			savingsLedger.account_.balance(),
			this->sang_
		);
		this->ba_.close(hfName);
		QVERIFY_THAT(this->ba_.banks().bank(hfName).isClosed(), isTrue());
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(
			this->ba_.ledgers().ledger(savingsAccountCode).account_.isClosed(),
			isTrue()
		);
		this->ba_.open(hfName);
		QVERIFY_THAT(this->ba_.banks().bank(hfName).isClosed(), isFalse());
		QVERIFY_THAT(this->ba_.ledgers().hasLedger(savingsAccountCode), isTrue());
		QVERIFY_THAT(
			this->ba_.ledgers().ledger(savingsAccountCode).account_.isClosed(),
			isFalse()
		);
	}

	//}

	//{ bankAccountCodes

	void bankAccountCodes_hasBanks_ShouldGive()
	{
		const BankName hfName{"Home Federal"};
		const AccountCode hfAccountCode{AccountNumber{1}, "Primary"};
		const Account hfAccount{
			AccountTypes::Asset,
			hfAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Bank hf{hfName, {hfAccountCode.number()}};
		this->banks_.add(hf);
		this->ledgers_.add(hfAccount, this->sang_);
		const map<BankName, map<AccountNumber, SupportedAccountTypes>> types{
			{
				hfName,
				{
					{hfAccountCode.number(), SupportedAccountTypes::Chequeing}
				}
			}
		};
		BankLedgers bm{&this->banks_, &this->ledgers_, types};
		set<BankAccountType> actual;
		QVERIFY_NO_THROW(actual = bm.bankAccountCodes(hfName));
		const set<BankAccountType> expected{{hfAccountCode, SupportedAccountTypes::Chequeing}};
		QVERIFY_THAT(actual.size(), equals(expected.size()));
		QVERIFY_THAT(*actual.cbegin(), equals(*expected.cbegin()));
	}

	void bankAccountCodes_hasBanksWithNoCodes_ShouldGive()
	{
		const BankName hfName{"Home Federal"};
		const AccountCode hfAccountCode{AccountNumber{1}, "Primary"};
		const Account hfAccount{
			AccountTypes::Asset,
			hfAccountCode,
			Money{12, 2200u, Iso4217Codes::USD}
		};
		const Bank hf{hfName, {hfAccountCode.number()}};
		this->banks_.add(hf);
		this->ledgers_.add(hfAccount, this->sang_);
		BankLedgers bm{&this->banks_, &this->ledgers_, {}};
		set<BankAccountType> actual;
		QVERIFY_NO_THROW(actual = bm.bankAccountCodes(hfName));
		QVERIFY_TRUE(actual.empty());
	}

	void bankAccountCodes_doesNotHaveBank_ShouldGive()
	{
		const BankName hfName{"Home Federal"};
		const BankLedgers bm{&this->banks_, &this->ledgers_, {}};
		set<BankAccountType> actual;
		QVERIFY_NO_THROW(actual = bm.bankAccountCodes(hfName));
		QVERIFY_TRUE(actual.empty());
	}

	//}

public:
	BankLedgersUT() :
		ba_{&this->banks_, &this->ledgers_, {}}
	{}

	SequentialAccountNumberGenerator sang_{};
	BankMap banks_;
	GeneralLedger ledgers_;
	BankLedgers ba_;
};

}}}}

QTEST_MAIN(drn::navigation::internal::tests::BankLedgersUT)
#include "BankLedgers-unit-tests.moc"
