/*******************************************************************************
***  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 <pecunia/MoneyTypes.h>
using pecunia::converter;
using pecunia::FloatingPoint;
#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 qt_mocks::notEquals;
// using QVERIFY_EXCEPTION_THROWN;
// using QVERIFY_THAT;

#include <stdexcept>
using std::logic_error;
#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;
using drn::accounting::TransactionError;
#include <accounting/LedgerBooks.h>
using drn::accounting::GeneralLedger;
using drn::accounting::LedgerAccount;
#include <accounting/Transaction.h>
using drn::accounting::Transaction;
using drn::accounting::TransactionNumber;
using drn::accounting::TransactionStatuses;
#include <banking/BankingErrors.h>
using drn::banking::BankError;
#include <budgeting/Bill.h>
using drn::budgeting::Bill;
#include <budgeting/Budget.h>
using drn::budgeting::Budget;
#include <budgeting/BudgetItemIdentifier.h>
using drn::budgeting::BudgetItemIdentifier;
#include <budgeting/BudgetItemTypes.h>
using drn::budgeting::BudgetItemTypes;
#include <budgeting/BudgetSource.h>
using drn::budgeting::BudgetSource;
#include <budgeting/BudgetingErrors.h>
using drn::budgeting::BudgetItemError;
#include <budgeting/EventFrequency.h>
using drn::budgeting::EventFrequency;
#include <budgeting/Debt.h>
using drn::budgeting::Debt;
using drn::budgeting::Percentage;
#include <budgeting/Goal.h>
using drn::budgeting::Goal;
#include <budgeting/Nontrack.h>
using drn::budgeting::Nontrack;
#include <budgeting/Wage.h>
using drn::budgeting::Wage;
#include <foundation/Error.h>
using drn::foundation::Error;
#include <foundation/TypeIndexMap.hpp>
using drn::foundation::TypeIndexMap;
#include "internal/BudgetLedgers.h"
using drn::navigation::internal::BudgetLedgers;


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

struct BudgetLedgersUT : QObject
{
	Q_OBJECT

	SequentialAccountNumberGenerator sang_{};
	Budget budget_;
	GeneralLedger ledgers_;
	BudgetLedgers bia_;

private slots:
	void initTestCase()
	{
		converter =
				[] (const Iso4217Codes& from, const Iso4217Codes& to) -> FloatingPoint
				{
					if (from == to)
						return static_cast<FloatingPoint>(1.0);
					switch (from)
					{
					case Iso4217Codes::XXX:
						return static_cast<FloatingPoint>(1.0);
					case Iso4217Codes::PLN:
						switch (to)
						{
						case Iso4217Codes::USD:
							return static_cast<FloatingPoint>(0.27);
						case Iso4217Codes::XXX:
							return static_cast<FloatingPoint>(1.0);
						default:
							throw logic_error{"Unhandled to code."};
						};
					case Iso4217Codes::USD:
						switch (to)
						{
						case Iso4217Codes::PLN:
							return static_cast<FloatingPoint>(3.76);
						case Iso4217Codes::XXX:
							return static_cast<FloatingPoint>(1.0);
						default:
							throw logic_error{"Unhandled to code."};
						};
					default:
						throw logic_error{"Unhandled from code."};
					}
				};
	}
	void init()
	{
		this->sang_ = AccountNumber{};
		this->budget_ = {};
		this->ledgers_ = {};
		this->bia_.clearCodes();
	}

	//{ Constructor Tests

	void constructor_fullInit_shouldBeValues()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		this->budget_.wages_.add(wage);
		const Account savings{
			AccountTypes::Asset,
			AccountCode{AccountNumber{1012}},
			Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(savings, this->sang_);
		TypeIndexMap<BudgetSource, AccountCode> budgetAccountCodes{};
		budgetAccountCodes.add<Wage>(source, savings.code());
		const BudgetLedgers bia{&this->budget_, &this->ledgers_, budgetAccountCodes};
		QVERIFY_THAT(bia.budget(), equals(this->budget_));
		QVERIFY_THAT(bia.ledgers(), equals(this->ledgers_));
		QVERIFY_THAT(bia.budgetAccountCodes(), equals(budgetAccountCodes));
	}

	void constructor_budgetAccountCodesMissingSource_shouldThrow()
	{
		const BudgetSource source{"Wendy's"};
		const Account savings{
			AccountTypes::Asset,
			AccountCode{AccountNumber{1012}},
			Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(savings, this->sang_);
		TypeIndexMap<BudgetSource, AccountCode> budgetAccountCodes{};
		budgetAccountCodes.add<Wage>(source, savings.code());
		QVERIFY_EXCEPTION_THROWN(
			BudgetLedgers(&this->budget_, &this->ledgers_, budgetAccountCodes),
			Error
		);
	}

	void constructor_budgetAccountCodesMissingAccount_shouldThrow()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		this->budget_.wages_.add(wage);
		const Account savings{
			AccountTypes::Asset,
			AccountCode{AccountNumber{1012}},
			Money{0, 0, Iso4217Codes::USD}
		};
		TypeIndexMap<BudgetSource, AccountCode> budgetAccountCodes{};
		budgetAccountCodes.add<Wage>(source, savings.code());
		QVERIFY_EXCEPTION_THROWN(
			BudgetLedgers(&this->budget_, &this->ledgers_, budgetAccountCodes),
			Error
		);
	}

	//}

	//{ add Tests

	void add_wageValidParam_shouldAdd()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		AccountCode generatedCode{};
		QVERIFY_NO_THROW(generatedCode = this->bia_.add(wage, this->sang_));
		QVERIFY_THAT(this->bia_.budget().wages_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().wages_.at(source), equals(wage));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{
			*this->sang_,
			source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
		};
		QVERIFY_THAT(generatedCode, equals(code));
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Revenue, code, Money{0, 0, Iso4217Codes::USD}, false};
		const auto account{*this->bia_.ledgers().accounts().cbegin()};
		QVERIFY_THAT(account, equals(expected));
		QVERIFY_THAT(
			account.code().name(),
			equals(expected.code().name())
		);
		QVERIFY_THAT(this->bia_.budgetAccountCode<Wage>(source), equals(code));
	}

	void add_wageSourceAlreadyExists_shouldThrow()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Wage duplicate{
			source,
			Money{892, 6600u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.add(duplicate, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().wages_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().wages_.at(source), equals(wage));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Revenue, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
	}

	void add_billValidParam_shouldAdd()
	{
		const BudgetSource source{"Xcel"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		AccountCode generatedCode{};
		QVERIFY_NO_THROW(generatedCode = this->bia_.add(bill, this->sang_));
		QVERIFY_THAT(this->bia_.budget().bills_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().bills_.at(source), equals(bill));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{
			*this->sang_,
			source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
		};
		QVERIFY_THAT(generatedCode, equals(code));
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		const auto account{*this->bia_.ledgers().accounts().cbegin()};
		QVERIFY_THAT(account, equals(expected));
		QVERIFY_THAT(
			account.code().name(),
			equals(expected.code().name())
		);
		QVERIFY_THAT(this->bia_.budgetAccountCode<Bill>(source), equals(code));
	}

	void add_billSourceAlreadyExists_shouldThrow()
	{
		const BudgetSource source{"Xcel"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Bill duplicate{
			source,
			Money{450, 7500u, Iso4217Codes::USD},
			EventFrequency::Quarterly,
			{2019, 7, 5}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.add(duplicate, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().bills_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().bills_.at(source), equals(bill));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
	}

	void add_debtValidParam_shouldAdd()
	{
		const BudgetSource source{"Visa"};
		const Debt debt{
			source,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		AccountCode generatedCode{};
		QVERIFY_NO_THROW(generatedCode = this->bia_.add(debt, this->sang_));
		QVERIFY_THAT(this->bia_.budget().debts_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().debts_.at(source), equals(debt));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{
			AccountNumber{1u},
			source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
		};
		QVERIFY_THAT(generatedCode, equals(code));
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Liability, code, debt.borrowed(), false};
		const auto account{*this->bia_.ledgers().accounts().cbegin()};
		QVERIFY_THAT(account, equals(expected));
		QVERIFY_THAT(
			account.code().name(),
			equals(expected.code().name())
		);
		QVERIFY_THAT(this->bia_.budgetAccountCode<Debt>(source), equals(code));
	}

	void add_debtSourceAlreadyExists_shouldThrow()
	{
		const BudgetSource source{"Visa"};
		const Debt debt{
			source,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Debt duplicate{
			source,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{25000, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.add(duplicate, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().debts_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().debts_.at(source), equals(debt));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Liability, code, debt.borrowed(), false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
	}

	void add_goalValidParam_shouldAdd()
	{
		const BudgetSource source{"Vacation"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		AccountCode generatedCode{};
		QVERIFY_NO_THROW(generatedCode = this->bia_.add(goal, this->sang_));
		QVERIFY_THAT(this->bia_.budget().goals_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().goals_.at(source), equals(goal));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{
			*this->sang_,
			source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
		};
		QVERIFY_THAT(generatedCode, equals(code));
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Asset, code, Money{0, 0, Iso4217Codes::USD}, false};
		const auto account{*this->bia_.ledgers().accounts().cbegin()};
		QVERIFY_THAT(account, equals(expected));
		QVERIFY_THAT(
			account.code().name(),
			equals(expected.code().name())
		);
		QVERIFY_THAT(this->bia_.budgetAccountCode<Goal>(source), equals(code));
	}

	void add_goalSourceAlreadyExists_shouldThrow()
	{
		const BudgetSource source{"Vacation"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Goal duplicate{
			source,
			Money{1500, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{15000, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.add(duplicate, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().goals_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().goals_.at(source), equals(goal));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Asset, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
	}

	void add_nontrackValidParam_shouldAdd()
	{
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		AccountCode generatedCode{};
		QVERIFY_NO_THROW(generatedCode = this->bia_.add(nontrack, this->sang_));
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()),
			equals(nontrack)
		);
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{
			*this->sang_,
			source.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_
		};
		QVERIFY_THAT(generatedCode, equals(code));
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		const auto account{*this->bia_.ledgers().accounts().cbegin()};
		QVERIFY_THAT(account, equals(expected));
		QVERIFY_THAT(
			account.code().name(),
			equals(expected.code().name())
		);
		QVERIFY_THAT(this->bia_.budgetAccountCode<Nontrack>(nontrack.source()), equals(code));
	}

	void add_nontrackSourceAlreadyExists_shouldThrow()
	{
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_EXCEPTION_THROWN(this->bia_.add(nontrack, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().nontracks_.at(nontrack.source()), equals(nontrack));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, nontrack.source().name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Nontrack>(nontrack.source()), equals(code));
	}

	//}

	//{ update Tests

	void update_wageChangeAllButCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Wage original{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Wage wage{
			source,
			Money{1500, 7500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 10}
		};
		QVERIFY_NO_THROW(this->bia_.update(wage, this->sang_));
		QVERIFY_THAT(this->bia_.budget().wages_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().wages_.at(source), equals(wage));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Revenue, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Wage>(source), equals(code));
	}

	void update_wageChangeCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Wage original{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::PLN},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.update(wage, this->sang_));
		QVERIFY_THAT(this->bia_.budget().wages_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().wages_.at(source), equals(wage));
		// The first account should be the budget item and the second the foreign currency
		// conversion account.
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Revenue, code, Money{0, 0, Iso4217Codes::PLN}, false};
		QVERIFY_THAT(this->bia_.ledgers().ledger(code).account_, equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Wage>(source), equals(code));
	}

	void update_wageMissingSource_shouldThrow()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::PLN},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(wage, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().wages_.has(source), isFalse());
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_FALSE(this->bia_.budgetAccountCodes().has<Wage>(source));
	}

	void update_billChangeAllButCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Bill original{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Bill bill{
			source,
			Money{1500, 7500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 10}
		};
		QVERIFY_NO_THROW(this->bia_.update(bill, this->sang_));
		QVERIFY_THAT(this->bia_.budget().bills_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().bills_.at(source), equals(bill));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Bill>(source), equals(code));
	}

	void update_billChangeCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Bill original{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Bill bill{
			source,
			Money{5, 1500u, Iso4217Codes::PLN},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.update(bill, this->sang_));
		QVERIFY_THAT(this->bia_.budget().bills_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().bills_.at(source), equals(bill));
		// The first account should be the budget item and the second the foreign currency
		// conversion account.
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::PLN}, false};
		QVERIFY_THAT(this->bia_.ledgers().ledger(code).account_, equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Bill>(source), equals(code));
	}

	void update_billMissingSource_shouldThrow()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Bill bill{
			source,
			Money{5, 1500u, Iso4217Codes::PLN},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(bill, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().bills_.has(source), isFalse());
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_FALSE(this->bia_.budgetAccountCodes().has<Bill>(source));
	}

	void update_debtChangeAllButCurrencyAndAmount_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Debt original{
			source,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Debt debt{
			source,
			Money{50, 7500u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 10},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{50.0}
		};
		QVERIFY_NO_THROW(this->bia_.update(debt, this->sang_));
		QVERIFY_THAT(this->bia_.budget().debts_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().debts_.at(source), equals(debt));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Liability, code, debt.borrowed(), false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Debt>(source), equals(code));
	}

	void update_debtChangeCurrency_shouldThrow()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Debt original{
			source,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		++this->sang_;
		Money minimum{Iso4217Codes::PLN};
		minimum = original.amount();
		QVERIFY_THAT(minimum, equals(original.amount()));
		Money borrowed{Iso4217Codes::PLN};
		borrowed = original.borrowed();
		QVERIFY_THAT(borrowed, equals(original.borrowed()));
		const Debt debt{
			source,
			minimum,
			EventFrequency::Monthly,
			{2019, 7, 5},
			borrowed,
			Percentage{20.0}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(debt, this->sang_), BudgetItemError);
	}

	void update_debtMissingSource_shouldThrow()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Debt debt{
			source,
			Money{50, 7500u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 10},
			Money{5000, 7500u, Iso4217Codes::USD},
			Percentage{50.0}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(debt, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().debts_.has(source), isFalse());
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_FALSE(this->bia_.budgetAccountCodes().has<Debt>(source));
	}

	void update_goalChangeAllButCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Goal original{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Goal goal{
			source,
			Money{1500, 5000u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 10},
			Money{15000, 7500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.update(goal, this->sang_));
		QVERIFY_THAT(this->bia_.budget().goals_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().goals_.at(source), equals(goal));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Asset, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Goal>(source), equals(code));
	}

	void update_goalChangeCurrency_shouldUpdate()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Goal original{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(original, this->sang_));
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::PLN},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::PLN}
		};
		QVERIFY_NO_THROW(this->bia_.update(goal, this->sang_));
		QVERIFY_THAT(this->bia_.budget().goals_.has(source), isTrue());
		QVERIFY_THAT(this->bia_.budget().goals_.at(source), equals(goal));
		// The first account should be the budget item and the second the foreign currency
		// conversion account.
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Asset, code, Money{0, 0, Iso4217Codes::PLN}, false};
		QVERIFY_THAT(this->bia_.ledgers().ledger(code).account_, equals(expected));
		QVERIFY_THAT(this->bia_.budgetAccountCode<Goal>(source), equals(code));
	}

	void update_goalMissingSource_shouldThrow()
	{
		const BudgetSource source{"My Budget Item Source"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(goal, this->sang_), BudgetItemError);
		QVERIFY_THAT(this->bia_.budget().goals_.has(source), isFalse());
		QVERIFY_THAT(*this->sang_, equals(AccountNumber()));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_FALSE(this->bia_.budgetAccountCodes().has<Goal>(source));
	}

	void update_nontrackChangePeriod_shouldUpdate()
	{
		const Nontrack original{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		this->bia_.add(original, this->sang_);
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{34,6700u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.update(nontrack, this->sang_));
		QVERIFY_THAT(this->bia_.budget().nontracks_.at(nontrack.source()), equals(nontrack));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, nontrack.source().name()};
		QVERIFY_THAT(
			this->bia_.ledgers().accountCodes().count(code),
			equals(static_cast<size_t>(1))
		);
		const Account expected{AccountTypes::Expense, code, Money{0, 0, Iso4217Codes::USD}, false};
		QVERIFY_THAT(*this->bia_.ledgers().accounts().cbegin(), equals(expected));
	}

	void update_nontrackChangeCurrency_shouldUpdate()
	{
		const Nontrack original{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 5}
		};
		this->bia_.add(original, this->sang_);
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{34,6700u, Iso4217Codes::PLN},
			EventFrequency::Weekly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.update(nontrack, this->sang_));
		QVERIFY_THAT(this->bia_.budget().nontracks_.at(nontrack.source()), equals(nontrack));
		// The first account should be the nontrack and the second the foreign currency
		// conversion account.
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const Account expectedNontrack{
			AccountTypes::Expense,
			AccountCode{AccountNumber{1u}, nontrack.source().name()},
			Money{0, 0, Iso4217Codes::PLN},
			false
		};
		const auto accounts{this->bia_.ledgers().accounts()};
		auto account{accounts.cbegin()};
		Q_ASSERT(account != accounts.cend());
		QVERIFY_THAT(*account, equals(expectedNontrack));
		++account;
		Q_ASSERT(account != accounts.cend());
		const Account expectedConversion{
			AccountTypes::Capital,
			AccountCode{AccountNumber{2u}},
			Money{0, 0, Iso4217Codes::PLN},
			false
		};
		QVERIFY_THAT(*account, equals(expectedConversion));
	}

	void update_nontrackDoesNotExist_shouldThrow()
	{
		const Nontrack original{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_EXCEPTION_THROWN(this->bia_.update(original, this->sang_), BudgetItemError);
	}

	//}

	//{ remove Tests

	void remove_wageValidParamNoTransactions_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_NO_THROW(this->bia_.removeWage(source));
		QVERIFY_FALSE(this->bia_.budget().wages_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_wageValidParamHasOnlyOpeningBalanceTransaction_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account opening{AccountTypes::Capital, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(opening, this->sang_);
		this->ledgers_.setOpeningAccountCode(opening.code());
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				*this->ledgers_.openingAccountCode(),
				Money{154, 25, Iso4217Codes::USD},
				QObject::tr("Opening balance")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeWage(source));
		QVERIFY_FALSE(this->bia_.budget().wages_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1012u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(opening.code()));
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_wageValidParamHasTransactions_shouldRemoveBudgetItemAndNotLedgerBook()
	{
		const BudgetSource source{"Wendy's"};
		const Wage wage{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account other{AccountTypes::Expense, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(other, this->sang_);
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				other.code(),
				Money{154, 25, Iso4217Codes::USD},
				QStringLiteral("Test trx")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeWage(source));
		QVERIFY_FALSE(this->bia_.budget().wages_.has(source));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().ledger(code).account_.isClosed());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_wageSourceDoesNotExist_shouldThrow()
	{
		const BudgetSource source{"Wendy's"};
		QVERIFY_EXCEPTION_THROWN(this->bia_.removeWage(source), BudgetItemError);
	}

	void remove_billValidParamNoTransactions_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Bill bill{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_NO_THROW(this->bia_.removeBill(source));
		QVERIFY_FALSE(this->bia_.budget().bills_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_billValidParamHasOnlyOpeningBalanceTransaction_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Bill bill{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account opening{AccountTypes::Capital, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(opening, this->sang_);
		this->ledgers_.setOpeningAccountCode(opening.code());
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				*this->ledgers_.openingAccountCode(),
				Money{154, 25, Iso4217Codes::USD},
				QObject::tr("Opening balance")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeBill(source));
		QVERIFY_FALSE(this->bia_.budget().bills_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1012u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(opening.code()));
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_billValidParamHasTransactions_shouldRemoveBudgetItemAndNotLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Bill bill{
			source,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account other{AccountTypes::Expense, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(other, this->sang_);
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				other.code(),
				Money{154, 25, Iso4217Codes::USD},
				QStringLiteral("Test trx")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeBill(source));
		QVERIFY_FALSE(this->bia_.budget().bills_.has(source));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().ledger(code).account_.isClosed());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_billSourceDoesNotExist_shouldThrow()
	{
		const BudgetSource source{"My Source"};
		QVERIFY_EXCEPTION_THROWN(this->bia_.removeBill(source), BudgetItemError);
	}

	void remove_debtValidParamHasOnlyOpeningBalanceTransaction_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Debt debt{
			source,
			Money{50, 7500u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 10},
			Money{5000, 7500u, Iso4217Codes::USD},
			Percentage{50.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, source.name()};
		const AccountCode openingAccountCode{AccountNumber{2}};
		this->ledgers_.setOpeningAccountCode(openingAccountCode);
		QVERIFY_NO_THROW(this->bia_.removeDebt(source));
		QVERIFY_FALSE(this->bia_.budget().debts_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(openingAccountCode));
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_debtValidParamHasTransactions_shouldRemoveBudgetItemAndNotLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Debt debt{
			source,
			Money{50, 7500u, Iso4217Codes::USD},
			EventFrequency::Weekly,
			{2019, 7, 10},
			Money{5000, 7500u, Iso4217Codes::USD},
			Percentage{50.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const AccountCode code{AccountNumber(1u), source.name()};
		const Account other{
			AccountTypes::Expense,
			AccountCode{AccountNumber{1012}},
			Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(other, this->sang_);
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				other.code(),
				Money{154, 25, Iso4217Codes::USD},
				QStringLiteral("Test trx")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeDebt(source));
		QVERIFY_FALSE(this->bia_.budget().debts_.has(source));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().ledger(code).account_.isClosed());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_debtSourceDoesNotExist_shouldThrow()
	{
		const BudgetSource source{"My Source"};
		QVERIFY_EXCEPTION_THROWN(this->bia_.removeDebt(source), BudgetItemError);
	}

	void remove_goalValidParamNoTransactions_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_NO_THROW(this->bia_.removeGoal(source));
		QVERIFY_FALSE(this->bia_.budget().goals_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_goalValidParamHasOnlyOpeningBalanceTransaction_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account opening{AccountTypes::Capital, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(opening, this->sang_);
		this->ledgers_.setOpeningAccountCode(opening.code());
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				*this->ledgers_.openingAccountCode(),
				Money{154, 25, Iso4217Codes::USD},
				QObject::tr("Opening balance")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeGoal(source));
		QVERIFY_FALSE(this->bia_.budget().goals_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1012u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(opening.code()));
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_goalValidParamHasTransactions_shouldRemoveBudgetItemAndNotLedgerBook()
	{
		const BudgetSource source{"My Source"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account other{AccountTypes::Expense, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(other, this->sang_);
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				other.code(),
				Money{154, 25, Iso4217Codes::USD},
				QStringLiteral("Test trx")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeGoal(source));
		QVERIFY_FALSE(this->bia_.budget().goals_.has(source));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().ledger(code).account_.isClosed());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_goalSourceDoesNotExist_shouldThrow()
	{
		const BudgetSource source{"My Source"};
		QVERIFY_EXCEPTION_THROWN(this->bia_.removeGoal(source), BudgetItemError);
	}

	void remove_nontrackValidParamNoTransactions_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		QVERIFY_NO_THROW(this->bia_.removeNontrack(source));
		QVERIFY_FALSE(this->bia_.budget().nontracks_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().isEmpty());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_nontrackValidParamHasOnlyOpeningBalanceTransaction_shouldRemoveBudgetItemAndLedgerBook()
	{
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account opening{
			AccountTypes::Capital,
			AccountCode{AccountNumber{1012}},
			Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(opening, this->sang_);
		this->ledgers_.setOpeningAccountCode(opening.code());
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				*this->ledgers_.openingAccountCode(),
				Money{154, 25, Iso4217Codes::USD},
				QObject::tr("Opening balance")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeNontrack(source));
		QVERIFY_FALSE(this->bia_.budget().nontracks_.has(source));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1012u)));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_FALSE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(opening.code()));
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_nontrackValidParamHasTransactions_shouldRemoveBudgetItemAndNotLedgerBook()
	{
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, source.name()};
		const Account other{AccountTypes::Expense, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}};
		this->ledgers_.add(other, this->sang_);
		this->ledgers_.post(
			Transaction{
				QDate{2019, 7, 5},
				code,
				other.code(),
				Money{154, 25, Iso4217Codes::USD},
				QStringLiteral("Test trx")
			}
		);
		QVERIFY_NO_THROW(this->bia_.removeNontrack(source));
		QVERIFY_FALSE(this->bia_.budget().nontracks_.has(source));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_TRUE(this->bia_.ledgers().ledger(code).account_.isClosed());
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().empty());
	}

	void remove_nontrackSourceDoesNotExist_shouldThrow()
	{
		const BudgetSource source{"My Source"};
		QVERIFY_EXCEPTION_THROWN(this->bia_.removeNontrack(source), BudgetItemError);
	}

	//}

	//{ rename Tests

	void rename_wageValidParam_shouldRename()
	{
		const BudgetSource original{"Original Name"};
		const Wage wage{
			original,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, original.name()};
		const BudgetSource changed{"Changed Name"};
		QVERIFY_NO_THROW(this->bia_.renameWage(original, changed, {}));
		QVERIFY_FALSE(this->bia_.budget().wages_.has(original));
		QVERIFY_TRUE(this->bia_.budget().wages_.has(changed));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_THAT(
			this->bia_.ledgers().ledger(code).account_.code().name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().has<Wage>(changed));
		QVERIFY_THAT(
			this->bia_.budgetAccountCodes().value<Wage>(changed).name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
	}

	void rename_billValidParam_shouldRename()
	{
		const BudgetSource original{"Original Name"};
		const Bill bill{
			original,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, original.name()};
		const BudgetSource changed{"Changed Name"};
		QVERIFY_NO_THROW(this->bia_.renameBill(original, changed, {}));
		QVERIFY_FALSE(this->bia_.budget().bills_.has(original));
		QVERIFY_TRUE(this->bia_.budget().bills_.has(changed));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_THAT(
			this->bia_.ledgers().ledger(code).account_.code().name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().has<Bill>(changed));
		QVERIFY_THAT(
			this->bia_.budgetAccountCodes().value<Bill>(changed).name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
	}

	void rename_debtValidParam_shouldRename()
	{
		const BudgetSource original{"Original Name"};
		const Debt debt{
			original,
			Money{5, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{2500, 2500u, Iso4217Codes::USD},
			Percentage{20.0}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		const AccountCode code{AccountNumber{1u}, original.name()};
		const BudgetSource changed{"Changed Name"};
		QVERIFY_NO_THROW(this->bia_.renameDebt(original, changed, {}));
		QVERIFY_FALSE(this->bia_.budget().debts_.has(original));
		QVERIFY_TRUE(this->bia_.budget().debts_.has(changed));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(2u)));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_THAT(
			this->bia_.ledgers().ledger(code).account_.code().name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().has<Debt>(changed));
		QVERIFY_THAT(
			this->bia_.budgetAccountCodes().value<Debt>(changed).name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
	}

	void rename_goalValidParam_shouldRename()
	{
		const BudgetSource original{"Original Name"};
		const Goal goal{
			original,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{2019, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, original.name()};
		const BudgetSource changed{"Changed Name"};
		QVERIFY_NO_THROW(this->bia_.renameGoal(original, changed, {}));
		QVERIFY_FALSE(this->bia_.budget().goals_.has(original));
		QVERIFY_TRUE(this->bia_.budget().goals_.has(changed));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_THAT(
			this->bia_.ledgers().ledger(code).account_.code().name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().has<Goal>(changed));
		QVERIFY_THAT(
			this->bia_.budgetAccountCodes().value<Goal>(changed).name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
	}

	void rename_nontrackValidParam_shouldRename()
	{
		const BudgetSource original{"Discretionary Spending"};
		const Nontrack nontrack{
			original,
			Money{5, 1500u, Iso4217Codes::USD},
			EventFrequency::Hourly,
			{2019, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		const AccountCode code{*this->sang_, original.name()};
		const BudgetSource changed{"My Spending"};
		QVERIFY_NO_THROW(this->bia_.renameNontrack(original, changed, {}));
		QVERIFY_FALSE(this->bia_.budget().nontracks_.has(original));
		QVERIFY_TRUE(this->bia_.budget().nontracks_.has(changed));
		QVERIFY_THAT(*this->sang_, equals(AccountNumber(1u)));
		QVERIFY_TRUE(this->bia_.ledgers().hasLedger(code));
		QVERIFY_THAT(
			this->bia_.ledgers().ledger(code).account_.code().name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
		QVERIFY_TRUE(this->bia_.budgetAccountCodes().has<Nontrack>(changed));
		QVERIFY_THAT(
			this->bia_.budgetAccountCodes().value<Nontrack>(changed).name(),
			equals(changed.name() + ' ' + BudgetLedgers::budgetItemAccountSuffix_)
		);
	}

	//}

	//{ Post Budget Item

	void post_hasBillBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{this->bia_.postBill(bill.source(), chequeing.code(), amount, occurredOn)};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		// N.B. Even though we're triggering the budget item in November, the last time it was to
		// occur was in  July, therefore the next time it should be due again is in August.
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const auto& billLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source))};
		QVERIFY_THAT(billLedger.account_.balance(), equals(amount));
		const Transaction billTxn{billLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(billTxn.amount(), equals(amount));
		QVERIFY_THAT(billTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(billTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(billTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(billTxn.debiting(), equals(billLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals(
				"Processed the budget item '" + bill.source().name() + "' that was due on "
					+ bill.nextOccurOn().toString() + '.'
			)
		);
	}

	void post_billBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& billLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postBill(
				bill.source(),
				billLedger.account_.code(),
				Money{125, 5000u, Iso4217Codes::USD},
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(billLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(billLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void post_hasDebtBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{this->bia_.postDebt(debt.source(), chequeing.code(), amount, occurredOn)};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		// N.B. Even though we're triggering the budget item in November, the last time it was to
		// occur was in  July, therefore the next time it should be due again is in August.
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const auto& debtLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source))};
		QVERIFY_THAT(debtLedger.account_.balance(), equals(borrowed - amount));
		const Transaction debtTxn{debtLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(debtTxn.amount(), equals(amount));
		QVERIFY_THAT(debtTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(debtTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(debtTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(debtTxn.debiting(), equals(debtLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals(
				"Processed the budget item '" + debt.source().name() + "' that was due on "
					+ debt.nextOccurOn().toString() + '.'
			)
		);
	}

	void post_debtBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& debtLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postDebt(
				debt.source(),
				debtLedger.account_.code(),
				Money{125, 5000u, Iso4217Codes::USD},
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_THAT(debtLedger.account_.balance(), equals(borrowed));
		// Confirm that the only transactions in the debt ledger is for the initial borrowed.
		QVERIFY_THAT(debtLedger.transactions_.size(), equals(static_cast<size_t>(1)));
		QVERIFY_THAT(
			debtLedger.transactions_.at(TransactionNumber{1}).amount(),
			equals(borrowed)
		);
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void post_hasGoalBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{this->bia_.postGoal(goal.source(), chequeing.code(), amount, occurredOn)};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		// N.B. Even though we're triggering the budget item in November, the last time it was to
		// occur was in  July, therefore the next time it should be due again is in August.
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const auto& goalLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source))};
		QVERIFY_THAT(goalLedger.account_.balance(), equals(amount));
		const Transaction goalTxn{goalLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(goalTxn.amount(), equals(amount));
		QVERIFY_THAT(goalTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(goalTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(goalTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(goalTxn.debiting(), equals(goalLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals(
				"Processed the budget item '" + goal.source().name() + "' that was due on "
					+ goal.nextOccurOn().toString() + '.'
			)
		);
	}

	void post_goalBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& goalLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postGoal(
				goal.source(),
				goalLedger.account_.code(),
				Money{125, 5000u, Iso4217Codes::USD},
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(goalLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(goalLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void post_hasNontrackBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postNontrack(nontrack.source(), chequeing.code(), amount, occurredOn)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		// N.B. Even though we're triggering the budget item in November, the last time it was to
		// occur was in  July, therefore the next time it should be due again is in August.
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(expectedNextOccurOn)
		);
		const auto& nontrackLedger{
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Nontrack>(source))
		};
		QVERIFY_THAT(nontrackLedger.account_.balance(), equals(amount));
		const Transaction nontrackTxn{nontrackLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(nontrackTxn.amount(), equals(amount));
		QVERIFY_THAT(nontrackTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(nontrackTxn.status(), equals(TransactionStatuses::Unreconciled));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals(
				"Processed the budget item '" + nontrack.source().name() + "' that was due on "
					+ nontrack.nextOccurOn().toString() + '.'
			)
		);
	}

	void post_nontrackBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{BudgetSource{QStringLiteral("Discretionary Spending")}};
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& nontrackLedger{
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Nontrack>(source))
		};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postNontrack(
				nontrack.source(),
				nontrackLedger.account_.code(),
				Money{125, 5000u, Iso4217Codes::USD},
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(expectedNextOccurOn)
		);
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(nontrackLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(nontrackLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void post_hasWageBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Burger King"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{this->bia_.postWage(wage.source(), chequeing.code(), amount, occurredOn)};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		// N.B. Even though we're triggering the budget item in November, the last time it was to
		// occur was in  July, therefore the next time it should be due again is in August.
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const auto& wageLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source))};
		QVERIFY_THAT(wageLedger.account_.balance(), equals(amount));
		const Transaction wageTxn{wageLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(wageTxn.amount(), equals(amount));
		QVERIFY_THAT(wageTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(wageTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(wageTxn.crediting(), equals(wageLedger.account_.code()));
		QVERIFY_THAT(wageTxn.debiting(), equals(chequeing.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals(
				"Processed the budget item '" + wage.source().name() + "' that was due on "
					+ wage.nextOccurOn().toString() + '.'
			)
		);
	}

	void post_wageBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& wageLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postWage(
				wage.source(),
				wageLedger.account_.code(),
				Money{125, 5000u, Iso4217Codes::USD},
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(wageLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(wageLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	//}

	//{ postUnexpected Tests

	void postUnexpected_billValid_ShouldBePosted()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postUnexpected(
				{BudgetItemTypes::Bill, bill.source()},
				amount,
				chequeing.code(),
				occurredOn
			)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(bill.nextOccurOn()));
		const auto& billLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source))};
		QVERIFY_THAT(billLedger.account_.balance(), equals(amount));
		const Transaction billTxn{billLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(billTxn.amount(), equals(amount));
		QVERIFY_THAT(billTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(billTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(billTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(billTxn.debiting(), equals(billLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals("Processed unexpected budget item '" + bill.source().name() + "'.")
		);
	}

	void postUnexpected_billBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& billLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Bill, bill.source()},
				Money{125, 5000u, Iso4217Codes::USD},
				billLedger.account_.code(),
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(bill.nextOccurOn()));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(billLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(billLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_billSourceNotKnown_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& billLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Bill, BudgetSource{"Not Known"}},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(bill.nextOccurOn()));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(billLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(billLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_hasDebtBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postUnexpected(
				{BudgetItemTypes::Debt, debt.source()},
				amount,
				chequeing.code(),
				occurredOn
			)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(debt.nextOccurOn()));
		const auto& debtLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source))};
		QVERIFY_THAT(debtLedger.account_.balance(), equals(borrowed - amount));
		const Transaction debtTxn{debtLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(debtTxn.amount(), equals(amount));
		QVERIFY_THAT(debtTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(debtTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(debtTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(debtTxn.debiting(), equals(debtLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals("Processed unexpected budget item '" + debt.source().name() + "'.")
		);
	}

	void postUnexpected_debtBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& debtLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Debt, debt.source()},
				Money{125, 5000u, Iso4217Codes::USD},
				debtLedger.account_.code(),
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_THAT(debtLedger.account_.balance(), equals(borrowed));
		// Confirm that the only transactions in the debt ledger is for the initial borrowed.
		QVERIFY_THAT(debtLedger.transactions_.size(), equals(static_cast<size_t>(1)));
		QVERIFY_THAT(
			debtLedger.transactions_.at(TransactionNumber{1}).amount(),
			equals(borrowed)
		);
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_debtSourceNotKnown_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& debtLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Debt, BudgetSource{"Not Known"}},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_THAT(debtLedger.account_.balance(), equals(borrowed));
		// Confirm that the only transactions in the debt ledger is for the initial borrowed.
		QVERIFY_THAT(debtLedger.transactions_.size(), equals(static_cast<size_t>(1)));
		QVERIFY_THAT(
			debtLedger.transactions_.at(TransactionNumber{1}).amount(),
			equals(borrowed)
		);
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_hasGoalBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postUnexpected(
				{BudgetItemTypes::Goal, goal.source()},
				amount,
				chequeing.code(),
				occurredOn
			)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(goal.nextOccurOn()));
		const auto& goalLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source))};
		QVERIFY_THAT(goalLedger.account_.balance(), equals(amount));
		const Transaction goalTxn{goalLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(goalTxn.amount(), equals(amount));
		QVERIFY_THAT(goalTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(goalTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(goalTxn.crediting(), equals(chequeing.code()));
		QVERIFY_THAT(goalTxn.debiting(), equals(goalLedger.account_.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals("Processed unexpected budget item '" + goal.source().name() + "'.")
		);
	}

	void postUnexpected_goalBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& goalLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Goal, goal.source()},
				Money{125, 5000u, Iso4217Codes::USD},
				goalLedger.account_.code(),
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(goal.nextOccurOn()));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(goalLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(goalLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_goalSourceNotKnown_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& goalLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Goal, BudgetSource{"Nont Known"}},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(goal.nextOccurOn()));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(goalLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(goalLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_hasNontrackBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Discretionary Spending"};
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postUnexpected(
				{BudgetItemTypes::Nontrack, nontrack.source()},
				amount,
				chequeing.code(),
				occurredOn
			)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(nontrack.nextOccurOn())
		);
		const auto& nontrackLedger{
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Nontrack>(source))
		};
		QVERIFY_THAT(nontrackLedger.account_.balance(), equals(amount));
		const Transaction nontrackTxn{nontrackLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(nontrackTxn.amount(), equals(amount));
		QVERIFY_THAT(nontrackTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(nontrackTxn.status(), equals(TransactionStatuses::Unreconciled));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(-amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals("Processed unexpected budget item '" + nontrack.source().name() + "'.")
		);
	}

	void postUnexpected_nontrackBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{BudgetSource{QStringLiteral("Discretionary Spending")}};
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& nontrackLedger{
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Nontrack>(source))
		};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Nontrack, nontrack.source()},
				Money{125, 5000u, Iso4217Codes::USD},
				nontrackLedger.account_.code(),
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(nontrack.nextOccurOn())
		);
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(nontrackLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(nontrackLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_nontrackSourceNotKnown_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{BudgetSource{QStringLiteral("Discretionary Spending")}};
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& nontrackLedger{
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Nontrack>(source))
		};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Nontrack, BudgetSource{"Not Known"}},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(nontrack.nextOccurOn())
		);
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(nontrackLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(nontrackLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_hasWageBudgetItem_ShouldPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Burger King"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		const auto txnNum{
			this->bia_.postUnexpected(
				{BudgetItemTypes::Wage, wage.source()},
				amount,
				chequeing.code(),
				occurredOn
			)
		};
		QVERIFY_THAT(txnNum.integer(), notEquals(TransactionNumber::invalid_));
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(wage.nextOccurOn()));
		const auto& wageLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source))};
		QVERIFY_THAT(wageLedger.account_.balance(), equals(amount));
		const Transaction wageTxn{wageLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(wageTxn.amount(), equals(amount));
		QVERIFY_THAT(wageTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(wageTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(wageTxn.crediting(), equals(wageLedger.account_.code()));
		QVERIFY_THAT(wageTxn.debiting(), equals(chequeing.code()));
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(amount));
		const Transaction chequeingTxn{chequeingLedger.transactions_.at(txnNum)};
		QVERIFY_THAT(chequeingTxn.amount(), equals(amount));
		QVERIFY_THAT(chequeingTxn.occurredOn(), equals(occurredOn));
		QVERIFY_THAT(chequeingTxn.status(), equals(TransactionStatuses::Unreconciled));
		QVERIFY_THAT(
			*chequeingTxn.description(),
			equals("Processed unexpected budget item '" + wage.source().name() + "'.")
		);
	}

	void postUnexpected_wageBudgetItemInvalidTransaction_ShouldNotPost()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& wageLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Wage, wage.source()},
				Money{125, 5000u, Iso4217Codes::USD},
				wageLedger.account_.code(),
				occurredOn
			),
			TransactionError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(wageLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(wageLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	void postUnexpected_wageSourceNotKnown_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		TransactionNumber txnNum{};
		const auto& wageLedger{this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source))};
		QVERIFY_EXCEPTION_THROWN(
			txnNum = this->bia_.postUnexpected(
				{BudgetItemTypes::Wage, BudgetSource{"Not Known"}},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
		QVERIFY_THAT(txnNum.integer(), equals(TransactionNumber::invalid_));
		const QDate expectedNextOccurOn{9999, 7, 5};
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		const Money expectedAmount{0, 0u, Iso4217Codes::USD};
		QVERIFY_THAT(wageLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(wageLedger.transactions_.empty());
		const auto& chequeingLedger{this->ledgers_.ledger(chequeing.code())};
		QVERIFY_THAT(chequeingLedger.account_.balance(), equals(expectedAmount));
		QVERIFY_TRUE(chequeingLedger.transactions_.empty());
	}

	//}

	void postUnexpected_unknownBudgetItemType_ShouldThrow()
	{
		const Account chequeing{
			AccountTypes::Asset, AccountCode{AccountNumber{1012}}, Money{0, 0, Iso4217Codes::USD}
		};
		this->ledgers_.add(chequeing, this->sang_);
		const BudgetSource source{"Computer"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		const Money amount{125, 5000u, Iso4217Codes::USD};
		const QDate occurredOn{9999, 11, 8};
		QVERIFY_EXCEPTION_THROWN(
			this->bia_.postUnexpected(
				{BudgetItemTypes::Unknown, wage.source()},
				amount,
				chequeing.code(),
				occurredOn
			),
			BudgetItemError
		);
	}

	//{ Skip Budget Item

	void skip_billBudgetItem_ShouldSkip()
	{
		const BudgetSource source{"Computer"};
		const Bill bill{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(bill, this->sang_));
		QVERIFY_NO_THROW(this->bia_.skipBill(source));
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.bills_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_TRUE(
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Bill>(source)).transactions_.empty()
		);
	}

	void skip_debtBudgetItem_ShouldSkip()
	{
		const BudgetSource source{"Computer"};
		const Money borrowed{1500, 2500u, Iso4217Codes::USD};
		const Debt debt{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			borrowed,
			Percentage{12}
		};
		QVERIFY_NO_THROW(this->bia_.add(debt, this->sang_));
		QVERIFY_NO_THROW(this->bia_.skipDebt(source));
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.debts_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_THAT(
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source)).transactions_.size(),
			equals(static_cast<size_t>(1))
		);
		QVERIFY_THAT(
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Debt>(source)).account_.balance(),
			equals(borrowed)
		);
	}

	void skip_goalBudgetItem_ShouldSkip()
	{
		const BudgetSource source{"Computer"};
		const Goal goal{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5},
			Money{1500, 2500u, Iso4217Codes::USD}
		};
		QVERIFY_NO_THROW(this->bia_.add(goal, this->sang_));
		QVERIFY_NO_THROW(this->bia_.skipGoal(source));
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.goals_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_TRUE(
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Goal>(source)).transactions_.empty()
		);
	}

	void skip_nontrackBudgetItem_ShouldSkip()
	{
		const Nontrack nontrack{
			BudgetSource{QStringLiteral("Discretionary Spending")},
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(nontrack, this->sang_));
		QVERIFY_NO_THROW(this->bia_.skipNontrack(nontrack.source()));
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(
			this->bia_.budget().nontracks_.at(nontrack.source()).nextOccurOn(),
			equals(expectedNextOccurOn)
		);
		QVERIFY_TRUE(
			this->ledgers_.ledger(
				this->bia_.budgetAccountCode<Nontrack>(nontrack.source())
			).transactions_.empty()
		);
	}

	void skip_wageBudgetItem_ShouldSkip()
	{
		const BudgetSource source{"Computer"};
		const Wage wage{
			source,
			Money{150, 2500u, Iso4217Codes::USD},
			EventFrequency::Monthly,
			{9999, 7, 5}
		};
		QVERIFY_NO_THROW(this->bia_.add(wage, this->sang_));
		QVERIFY_NO_THROW(this->bia_.skipWage(source));
		const QDate expectedNextOccurOn{9999, 8, 5};
		QVERIFY_THAT(this->budget_.wages_.at(source).nextOccurOn(), equals(expectedNextOccurOn));
		QVERIFY_TRUE(
			this->ledgers_.ledger(this->bia_.budgetAccountCode<Wage>(source)).transactions_.empty()
		);
	}

	//}

public:
	BudgetLedgersUT() :
		sang_{},
		budget_{},
		ledgers_{},
		bia_{&this->budget_, &this->ledgers_, {}}
	{}
};

}}}}

QTEST_MAIN(drn::navigation::internal::tests::BudgetLedgersUT)
#include "BudgetLedgers-unit-tests.moc"
