changeset 903:a5377eca373b

IN 32: Adds the distribution to the ReconciledSurvey of the budget file. Adds a toWeak mapping for the BudgetItemTypes (useful when translation is not desired).
author John Schneiderman <JohnMS@member.fsf.org>
date Thu, 06 Oct 2022 20:08:49 +0200
parents 56c171f78d39
children ab20e0cc063d
files src/budgeting/external/budgeting/BudgetItemTypes.cpp src/budgeting/external/budgeting/BudgetItemTypes.h src/file-storage/internal/BudgetElements.cpp src/file-storage/internal/BudgetElements.h src/file-storage/internal/XmlBudgetFile.cpp src/file-storage/unit-tests/BudgetFileContents.cpp src/file-storage/unit-tests/XmlBudgetFile-unit-tests.cpp src/navigation/internal/BudgetBankLedgers.h
diffstat 8 files changed, 377 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/src/budgeting/external/budgeting/BudgetItemTypes.cpp	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/budgeting/external/budgeting/BudgetItemTypes.cpp	Thu Oct 06 20:08:49 2022 +0200
@@ -53,6 +53,18 @@
 	return o << "Value ("<< presentationText(bit).toStdString() << ')';
 }
 
+QString drn::budgeting::toWeak(const BudgetItemTypes& bit)
+{
+	BEGIN_ENUMERATION_TO_OTHER(bit);
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Unknown, "Unknown");
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Bill, "Bill");
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Debt, "Debt");
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Goal, "Goal");
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Nontrack, "Nontrack");
+		MAP_ENUMERATION_TO_OTHER(BudgetItemTypes::Wage, "Wage");
+	END_ENUMERATION_TO_OTHER(bit, BudgetItemTypes);
+}
+
 QString drn::budgeting::presentationText(const BudgetItemTypes& bit)
 {
 	BEGIN_ENUMERATION_TO_OTHER(bit);
--- a/src/budgeting/external/budgeting/BudgetItemTypes.h	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/budgeting/external/budgeting/BudgetItemTypes.h	Thu Oct 06 20:08:49 2022 +0200
@@ -47,6 +47,7 @@
 };
 
 DRN_BUDGETING_EXPORT std::ostream& operator<<(std::ostream& o, const BudgetItemTypes& bit);
+DRN_BUDGETING_EXPORT ::QString toWeak(const BudgetItemTypes& bit);
 DRN_BUDGETING_EXPORT ::QString presentationText(const BudgetItemTypes& bit);
 DRN_BUDGETING_EXPORT BudgetItemTypes toBudgetItemTypes(const ::QString& raw);
 DRN_BUDGETING_EXPORT BudgetItemTypes toBudgetItemTypes(const std::type_index& type);
--- a/src/file-storage/internal/BudgetElements.cpp	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/file-storage/internal/BudgetElements.cpp	Thu Oct 06 20:08:49 2022 +0200
@@ -35,10 +35,11 @@
 using drn::file_storage::internal::BanksElement;
 using drn::file_storage::internal::BillElement;
 using drn::file_storage::internal::BillsElement;
+using drn::file_storage::internal::DebtElement;
+using drn::file_storage::internal::DebtsElement;
 using drn::file_storage::internal::DiscretionariesElement;
 using drn::file_storage::internal::DiscretionaryElement;
-using drn::file_storage::internal::DebtElement;
-using drn::file_storage::internal::DebtsElement;
+using drn::file_storage::internal::DistributionElement;
 using drn::file_storage::internal::GoalElement;
 using drn::file_storage::internal::GoalsElement;
 using drn::file_storage::internal::IndexedElement;
@@ -1633,6 +1634,116 @@
 
 //}
 
+//{ DistributionElement
+
+const QLatin1String DistributionElement::typeAttribute_{"type"};
+const QLatin1String DistributionElement::budgetItemIdAttribute_{"id"};
+const QLatin1String DistributionElement::majorAttribute_{"major"};
+const QLatin1String DistributionElement::minorAttribute_{"minor"};
+const QLatin1String DistributionElement::currencyAttribute_{"currency"};
+
+DistributionElement::DistributionElement() :
+	BasicElement{},
+	type_{},
+	budgetItemId_{},
+	major_{},
+	minor_{},
+	currency_{}
+{}
+
+DistributionElement::DistributionElement(
+	QString type,
+	const IndexedElement::IdType& budgetItemId,
+	MajorType major,
+	MinorType minor,
+	QString currency
+) :
+	BasicElement{},
+	type_{move(type)},
+	budgetItemId_{budgetItemId},
+	major_{move(major)},
+	minor_{move(minor)},
+	currency_{move(currency)}
+{}
+
+const QLatin1String& DistributionElement::tag() const
+{
+	static const QLatin1String tag{"distribution"};
+	return tag;
+}
+
+void DistributionElement::read(QXmlStreamReader& xml)
+{
+	qDebug() << "Reading Element:" << this->tag();
+
+	if ( ! xml.isStartElement())
+		throw BudgetFileError{
+			xml.errorString(),
+			xml.lineNumber(),
+			xml.columnNumber(),
+			QObject::tr(
+				"The XML element, %1, is not the starting element of %2."
+			).arg(xml.name()).arg(this->tag())
+		};
+
+	if (xml.name() != this->tag())
+		throw BudgetFileError{
+			xml.errorString(),
+			xml.lineNumber(),
+			xml.columnNumber(),
+			QObject::tr(
+				"The XML element tag, %1, is not valid under the current element %2."
+			).arg(xml.name()).arg(this->tag())
+		};
+
+	if (xml.attributes().hasAttribute(DistributionElement::typeAttribute_))
+		this->type_ = readAttributeString(xml, DistributionElement::typeAttribute_);
+	const auto budgetItemId{
+		readAttributeUnsigned<quint32>(xml, DistributionElement::budgetItemIdAttribute_)
+	};
+
+	if (budgetItemId == IndexedElement::invalidId_)
+		throw BudgetFileError{
+			xml.errorString(),
+			xml.lineNumber(),
+			xml.columnNumber(),
+			QObject::tr("The budget item ID for a distribution element cannot be invalid.")
+		};
+	this->budgetItemId_ = budgetItemId;
+
+	if (xml.attributes().hasAttribute(DistributionElement::majorAttribute_))
+		this->major_ = readAttributeSigned<DistributionElement::MajorType>(
+			xml,
+			DistributionElement::majorAttribute_
+		);
+
+	if (xml.attributes().hasAttribute(DistributionElement::minorAttribute_))
+		this->minor_ = readAttributeUnsigned<DistributionElement::MinorType>(
+			xml,
+			DistributionElement::minorAttribute_
+		);
+
+	if (xml.attributes().hasAttribute(DistributionElement::currencyAttribute_))
+		this->currency_ = readAttributeString(xml, DistributionElement::currencyAttribute_);
+	xml.skipCurrentElement(); // Finished reading element.
+}
+
+void DistributionElement::write(QXmlStreamWriter& xml) const
+{
+	xml.writeStartElement(this->tag());
+	xml.writeAttribute(DistributionElement::typeAttribute_, this->type_);
+	xml.writeAttribute(
+		DistributionElement::budgetItemIdAttribute_,
+		QString::number(this->budgetItemId_)
+	);
+	xml.writeAttribute(DistributionElement::majorAttribute_, QString::number(this->major_));
+	xml.writeAttribute(DistributionElement::minorAttribute_, QString::number(this->minor_));
+	xml.writeAttribute(DistributionElement::currencyAttribute_, this->currency_);
+	xml.writeEndElement();
+}
+
+//}
+
 //{ ReconciliationElement
 
 const QLatin1String ReconciliationElement::accountIdAttribute_{"account"};
@@ -1652,7 +1763,7 @@
 {}
 
 ReconciliationElement::ReconciliationElement(const IndexedElement::IdType& accountId) :
-	ReconciliationElement{accountId, {}, {}, {}, {}, {}}
+	ReconciliationElement{accountId, {}, {}, {}, {}, {}, {}}
 {}
 
 ReconciliationElement::ReconciliationElement(
@@ -1661,7 +1772,8 @@
 	MinorType minor,
 	QString currency,
 	Optional<QDate> date,
-	Optional<QString> checksum
+	Optional<QString> checksum,
+	vector<DistributionElement> distributions
 ) :
 	BasicElement{},
 	accountId_{accountId},
@@ -1669,7 +1781,8 @@
 	minor_{move(minor)},
 	currency_{move(currency)},
 	date_{move(date)},
-	checksum_{move(checksum)}
+	checksum_{move(checksum)},
+	distributions_{move(distributions)}
 {
 	if (this->accountId_ == IndexedElement::invalidId_)
 		throw BudgetFileError{
@@ -1742,7 +1855,19 @@
 
 	if (xml.attributes().hasAttribute(ReconciliationElement::checksumAttribute_))
 		this->checksum_ = readAttributeString(xml, ReconciliationElement::checksumAttribute_);
-	xml.skipCurrentElement(); // Finished reading element.
+
+	while (xml.readNextStartElement())
+	{
+		DistributionElement distribution{};
+
+		if (xml.name() == distribution.tag())
+		{
+			distribution.read(xml);
+			this->distributions_.emplace_back(move(distribution));
+		}
+		else
+			break;
+	};
 }
 
 void ReconciliationElement::write(QXmlStreamWriter& xml) const
@@ -1766,6 +1891,9 @@
 
 	if (this->checksum_.hasValue())
 		xml.writeAttribute(ReconciliationElement::checksumAttribute_, *this->checksum_);
+
+	for (const auto& distribution : this->distributions_)
+		distribution.write(xml);
 	xml.writeEndElement();
 }
 
--- a/src/file-storage/internal/BudgetElements.h	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/file-storage/internal/BudgetElements.h	Thu Oct 06 20:08:49 2022 +0200
@@ -465,6 +465,35 @@
 	std::vector<BankElement> banks_;
 };
 
+struct DistributionElement : BasicElement
+{
+	using MajorType = pecunia::UnitStorage;
+	using MinorType = pecunia::MinorUnit;
+
+	DistributionElement();
+	DistributionElement(
+		::QString type,
+		const IndexedElement::IdType& budgetItemId,
+		MajorType major,
+		MinorType minor,
+		::QString currency
+	);
+	const ::QLatin1String& tag() const override;
+	void read(::QXmlStreamReader& xml) override;
+	void write(::QXmlStreamWriter& xml) const override;
+
+	static const ::QLatin1String typeAttribute_;
+	::QString type_;
+	static const ::QLatin1String budgetItemIdAttribute_;
+	IndexedElement::IdType budgetItemId_;
+	static const ::QLatin1String majorAttribute_;
+	MajorType major_;
+	static const ::QLatin1String minorAttribute_;
+	MinorType minor_;
+	static const ::QLatin1String currencyAttribute_;
+	::QString currency_;
+};
+
 struct ReconciliationElement : BasicElement
 {
 	using MajorType = pecunia::UnitStorage;
@@ -478,7 +507,8 @@
 		MinorType minor,
 		::QString currency,
 		foundation::Optional<::QDate> date,
-		foundation::Optional<::QString> checksum
+		foundation::Optional<::QString> checksum,
+		std::vector<DistributionElement> distributions
 	);
 	const ::QLatin1String& tag() const override;
 	void read(::QXmlStreamReader& xml) override;
@@ -496,6 +526,7 @@
 	foundation::Optional<::QDate> date_;
 	static const ::QLatin1String checksumAttribute_;
 	foundation::Optional<::QString> checksum_;
+	std::vector<DistributionElement> distributions_;
 };
 
 struct ReconciliationsElement : BasicElement
--- a/src/file-storage/internal/XmlBudgetFile.cpp	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/file-storage/internal/XmlBudgetFile.cpp	Thu Oct 06 20:08:49 2022 +0200
@@ -107,6 +107,11 @@
 using drn::file_storage::internal::WagesElement;
 #include <budgeting/Budget.h>
 using drn::budgeting::Budget;
+#include <budgeting/BudgetItemTypes.h>
+using drn::budgeting::BudgetItemIdentifier;
+using drn::budgeting::BudgetItemTypes;
+using drn::budgeting::toBudgetItemTypes;
+using drn::budgeting::toWeak;
 #include <budgeting/BudgetSource.h>
 using drn::budgeting::BudgetSource;
 #include <budgeting/Nontrack.h>
@@ -139,6 +144,82 @@
 using drn::surveying::ReconciledSurveys;
 
 
+namespace
+{
+
+class BudgetItemIdentifierIndexer
+{
+	std::map<BudgetItemTypes, map<IndexedElement::IdType, BudgetSource>> budgetIds_;
+
+public:
+	void addMapping(
+		const BudgetItemTypes& type,
+		const IndexedElement::IdType& id,
+		const BudgetSource& source
+	)
+	{
+		this->budgetIds_[type][id] = source;
+	}
+
+	const BudgetSource& lookUp(const BudgetItemTypes& type, const IndexedElement::IdType& id)
+	try
+	{
+		return this->budgetIds_.at(type).at(id);
+	}
+	catch (const exception& error)
+	{
+		throw BudgetFileError{
+			{},
+			{},
+			{},
+			QObject::tr(
+				"The budget file contains a malformed budget item ID mapping. There is not an "
+					"identifier of %1 of the type %2."
+			)
+				.arg(id)
+				.arg(presentationText(type)),
+			error
+		};
+	}
+
+	const IndexedElement::IdType& lookUpId(const BudgetItemTypes& type, const BudgetSource& source)
+	try
+	{
+		const auto& types{this->budgetIds_.at(type)};
+		const auto idSource{
+			find_if(
+				types.cbegin(),
+				types.cend(),
+				[&source] (const auto& IdSourceFromType)
+				{
+					return IdSourceFromType.second == source;
+				}
+			)
+		};
+
+		if (idSource == types.cend())
+			throw Error{QObject::tr("Failed to find budget source.")};
+		return idSource->first;
+	}
+	catch (const exception& error)
+	{
+		throw BudgetFileError{
+			{},
+			{},
+			{},
+			QObject::tr(
+				"The budget file contains a malformed budget item ID mapping. There is not a "
+					"budget source '%1' of the type %2."
+			)
+				.arg(presentationText(source))
+				.arg(presentationText(type)),
+			error
+		};
+	}
+};
+
+}
+
 tuple<
 	GeneralLedger,
 	Budget,
@@ -253,14 +334,16 @@
 			xml.columnNumber(),
 			QObject::tr("An unexpected error appeared in the file.")
 		};
+	BudgetItemIdentifierIndexer budgetIndexer{};
 	Budget b{};
 
 	for (const auto& wageElement : wagesElement.wages_)
 		try
 		{
+			const BudgetSource source{wageElement.sourceName_};
 			b.wages_.add(
 				Wage{
-					BudgetSource{wageElement.sourceName_},
+					source,
 					Money{
 						wageElement.major_,
 						wageElement.minor_,
@@ -270,6 +353,7 @@
 					wageElement.nextOccurOn_
 				}
 			);
+			budgetIndexer.addMapping(BudgetItemTypes::Wage, wageElement.id_, source);
 		}
 		catch (const exception& error)
 		{
@@ -282,21 +366,23 @@
 			};
 		}
 
-	for (const auto& billsElement : billsElements.bills_)
+	for (const auto& billElement : billsElements.bills_)
 		try
 		{
+			const BudgetSource source{billElement.sourceName_};
 			b.bills_.add(
 				Bill{
-					BudgetSource{billsElement.sourceName_},
+					source,
 					Money{
-						billsElement.major_,
-						billsElement.minor_,
-						toIso4217Code(billsElement.currency_.toStdString())
+						billElement.major_,
+						billElement.minor_,
+						toIso4217Code(billElement.currency_.toStdString())
 					},
-					toEventFrequency(billsElement.period_),
-					billsElement.nextOccurOn_
+					toEventFrequency(billElement.period_),
+					billElement.nextOccurOn_
 				}
 			);
+			budgetIndexer.addMapping(BudgetItemTypes::Bill, billElement.id_, source);
 		}
 		catch (const exception& error)
 		{
@@ -312,9 +398,10 @@
 	for (const auto& debtElement : debtsElements.debts_)
 		try
 		{
+			const BudgetSource source{debtElement.sourceName_};
 			b.debts_.add(
 				Debt{
-					BudgetSource{debtElement.sourceName_},
+					source,
 					Money{
 						debtElement.minimumMajor_,
 						debtElement.minimumMinor_,
@@ -330,6 +417,7 @@
 					Percentage{debtElement.interest_}
 				}
 			);
+			budgetIndexer.addMapping(BudgetItemTypes::Debt, debtElement.id_, source);
 		}
 		catch (const exception& error)
 		{
@@ -342,26 +430,28 @@
 			};
 		}
 
-	for (const auto& goalsElement : goalsElements.goals_)
+	for (const auto& goalElement : goalsElements.goals_)
 		try
 		{
+			const BudgetSource source{goalElement.sourceName_};
 			b.goals_.add(
 				Goal{
-					BudgetSource{goalsElement.sourceName_},
+					source,
 					Money{
-						goalsElement.major_,
-						goalsElement.minor_,
-						toIso4217Code(goalsElement.currency_.toStdString())
+						goalElement.major_,
+						goalElement.minor_,
+						toIso4217Code(goalElement.currency_.toStdString())
 					},
-					toEventFrequency(goalsElement.period_),
-					goalsElement.nextOccurOn_,
+					toEventFrequency(goalElement.period_),
+					goalElement.nextOccurOn_,
 					Money{
-						goalsElement.finalMajor_,
-						goalsElement.finalMinor_,
-						toIso4217Code(goalsElement.currency_.toStdString())
+						goalElement.finalMajor_,
+						goalElement.finalMinor_,
+						toIso4217Code(goalElement.currency_.toStdString())
 					}
 				}
 			);
+			budgetIndexer.addMapping(BudgetItemTypes::Goal, goalElement.id_, source);
 		}
 		catch (const exception& error)
 		{
@@ -374,21 +464,23 @@
 			};
 		}
 
-	for (const auto& discretionaryElement : nontracksElement.discretionaries_)
+	for (const auto& nontrackElement : nontracksElement.discretionaries_)
 		try
 		{
+			const BudgetSource source{nontrackElement.sourceName_};
 			b.nontracks_.add(
 				Nontrack{
-					BudgetSource{discretionaryElement.sourceName_},
+					source,
 					Money{
-						discretionaryElement.major_,
-						discretionaryElement.minor_,
-						toIso4217Code(discretionaryElement.currency_.toStdString())
+						nontrackElement.major_,
+						nontrackElement.minor_,
+						toIso4217Code(nontrackElement.currency_.toStdString())
 					},
-					toEventFrequency(discretionaryElement.period_),
-					discretionaryElement.nextOccurOn_
+					toEventFrequency(nontrackElement.period_),
+					nontrackElement.nextOccurOn_
 				}
 			);
+			budgetIndexer.addMapping(BudgetItemTypes::Nontrack, nontrackElement.id_, source);
 		}
 		catch (const exception& error)
 		{
@@ -541,7 +633,9 @@
 					{
 						AccountNumber number{account.accountId_};
 						numbers.emplace(number);
-						bankAccountTypes[name][move(number)] = toSupportedAccountTypes(account.type_);
+						bankAccountTypes[name][move(number)] = toSupportedAccountTypes(
+							account.type_
+						);
 					}
 					rawBanks[name] = Bank{name, move(numbers), bankElement.isClosed_};
 				}
@@ -628,6 +722,20 @@
 				bankInfo->second.at(accountNumber)
 			}
 		};
+		map<BudgetItemIdentifier, Money> distributions{};
+
+		for (const auto& distribution : reconciliationElement.distributions_)
+		{
+			const auto type{toBudgetItemTypes(distribution.type_)};
+			distributions.emplace(
+				BudgetItemIdentifier{type, budgetIndexer.lookUp(type, distribution.budgetItemId_)},
+				Money{
+					distribution.major_,
+					distribution.minor_,
+					toIso4217Code(distribution.currency_.toStdString())
+				}
+			);
+		}
 		surveys.emplace(
 			ba,
 			ReconciledSurvey{
@@ -640,7 +748,7 @@
 					},
 					reconciliationElement.date_
 				},
-				{}, // TODO: distribution amounts
+				move(distributions),
  				reconciliationElement.checksum_
 			}
 		);
@@ -679,9 +787,10 @@
 
 	qDebug() << "Filling application element";
 	const ApplicationElement applicationElement{};
+	BudgetItemIdentifierIndexer budgetIndexer{};
 	qDebug() << "Filling wage elements";
 	const WagesElement wageElements{
-		[&b, &budgetCodes] ()
+		[&b, &budgetCodes, &budgetIndexer] ()
 		{
 			WagesElement we;
 			auto id{static_cast<IndexedElement::IdType>(IndexedElement::invalidId_ + 1)};
@@ -698,6 +807,7 @@
 					wage.second.nextOccurOn(),
 					budgetCodes.value<Wage>(wage.second.source()).number().integer()
 				);
+				budgetIndexer.addMapping(BudgetItemTypes::Wage, id, wage.first);
 				++id;
 			}
 			return we;
@@ -705,7 +815,7 @@
 	};
 	qDebug() << "Filling bill elements";
 	const BillsElement billElements{
-		[&b, &budgetCodes] ()
+		[&b, &budgetCodes, &budgetIndexer] ()
 		{
 			BillsElement ee;
 			auto id{static_cast<IndexedElement::IdType>(IndexedElement::invalidId_ + 1)};
@@ -722,6 +832,7 @@
 					bill.second.nextOccurOn(),
 					budgetCodes.value<Bill>(bill.second.source()).number().integer()
 				);
+				budgetIndexer.addMapping(BudgetItemTypes::Bill, id, bill.first);
 				++id;
 			}
 			return ee;
@@ -729,7 +840,7 @@
 	};
 	qDebug() << "Filling debt elements";
 	const DebtsElement debtsElements{
-		[&b, &budgetCodes] ()
+		[&b, &budgetCodes, &budgetIndexer] ()
 		{
 			DebtsElement le;
 			auto id{static_cast<IndexedElement::IdType>(IndexedElement::invalidId_ + 1)};
@@ -749,6 +860,7 @@
 					debt.second.nextOccurOn(),
 					budgetCodes.value<Debt>(debt.second.source()).number().integer()
 				);
+				budgetIndexer.addMapping(BudgetItemTypes::Debt, id, debt.first);
 				++id;
 			}
 			return le;
@@ -756,7 +868,7 @@
 	};
 	qDebug() << "Filling goal elements";
 	const GoalsElement goalElements{
-		[&b, &budgetCodes] ()
+		[&b, &budgetCodes, &budgetIndexer] ()
 		{
 			GoalsElement ge;
 			auto id{static_cast<IndexedElement::IdType>(IndexedElement::invalidId_ + 1)};
@@ -775,6 +887,7 @@
 					goal.second.nextOccurOn(),
 					budgetCodes.value<Goal>(goal.second.source()).number().integer()
 				);
+				budgetIndexer.addMapping(BudgetItemTypes::Goal, id, goal.first);
 				++id;
 			}
 			return ge;
@@ -782,7 +895,7 @@
 	};
 	qDebug() << "Filling discretionary elements";
 	const DiscretionariesElement nontrackElements{
-		[&b, &budgetCodes] ()
+		[&b, &budgetCodes, &budgetIndexer] ()
 		{
 			DiscretionariesElement des;
 			auto id{static_cast<IndexedElement::IdType>(IndexedElement::invalidId_ + 1)};
@@ -799,6 +912,7 @@
 					nontrack.second.nextOccurOn(),
 					budgetCodes.value<Nontrack>(nontrack.second.source()).number().integer()
 				);
+				budgetIndexer.addMapping(BudgetItemTypes::Nontrack, id, nontrack.first);
 				++id;
 			}
 			return des;
@@ -883,7 +997,7 @@
 	};
 	qDebug() << "Filling reconciled bank accounts elements";
 	const ReconciliationsElement reconciliationsElement{
-		[&surveys] ()
+		[&surveys, &budgetIndexer] ()
 		{
 			ReconciliationsElement elements{};
 
@@ -891,6 +1005,16 @@
 			{
 				const auto& bankAccount{bankAccountReconciledSurvey.first};
 				const auto& reconciledSurvey{bankAccountReconciledSurvey.second};
+				vector<DistributionElement> distributions{};
+
+				for (const auto& unused : reconciledSurvey.distribution())
+					distributions.emplace_back(
+						toWeak(unused.first.type_),
+						budgetIndexer.lookUpId(unused.first.type_, unused.first.source_),
+						unused.second.major(),
+						unused.second.minor(),
+						QString::fromStdString(toStdString(unused.second.code()))
+					);
 				elements.reconciliations_.emplace_back(
 					bankAccount.account_.code_.number().integer(),
 					reconciledSurvey.reconciled().balance().major(),
@@ -899,7 +1023,8 @@
 						toStdString(reconciledSurvey.reconciled().balance().code())
 					),
 					reconciledSurvey.reconciled().reconciledOn(),
-					reconciledSurvey.validity()
+					reconciledSurvey.validity(),
+					move(distributions)
 				);
 			}
 			return elements;
--- a/src/file-storage/unit-tests/BudgetFileContents.cpp	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/file-storage/unit-tests/BudgetFileContents.cpp	Thu Oct 06 20:08:49 2022 +0200
@@ -64,6 +64,10 @@
 using drn::banking::ReconciledBankAccount;
 #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/Nontrack.h>
@@ -167,7 +171,11 @@
 	</banks>
 	<reconciliations>
 		<reconciliation account="1010" major="0" minor="0" currency="PLN"/>
-		<reconciliation account="1020" major="1234" minor="5600" currency="PLN" date="2022-08-31" checksum="LWaIuzOUeNn/eXPBDC7VSNYh5xuBRUCjUpCJs8dZgjc="/>
+		<reconciliation account="1020" major="1234" minor="5600" currency="PLN" date="2022-08-31" checksum="b1ZfLVuVIYKVMVJ0/QQLRNMtIXjtcbbm0BUmJ37gQKQ=">
+			<distribution type="Bill" id="1" major="411" minor="5200" currency="PLN"/>
+			<distribution type="Goal" id="1" major="411" minor="5200" currency="PLN"/>
+			<distribution type="Goal" id="3" major="411" minor="5200" currency="PLN"/>
+		</reconciliation>
 	</reconciliations>
 </DuxReiNummariae>)~"};
 	return budgetContents;
@@ -669,14 +677,14 @@
 	static BankAccount homeFederalChequeing{
 		BankName{"Home Federal"},
 		BankAccountType{
-			AccountCode{AccountNumber{1020}},
+			validGeneralLedger().ledger(AccountNumber{1020}).account_.code(),
 			SupportedAccountTypes::Chequeing
 		}
 	};
 	static BankAccount c1stSavings{
 		BankName{"Community 1st Credit Union"},
 		BankAccountType{
-			AccountCode{AccountNumber{1010}},
+			validGeneralLedger().ledger(AccountNumber{1010}).account_.code(),
 			SupportedAccountTypes::Savings
 		}
 	};
@@ -690,8 +698,32 @@
 						Money{1234, 5600u, Iso4217Codes::PLN},
 						QDate{2022, 8, 31}
 					},
-					{},
-					{inPlace, QStringLiteral("c4k+LysGJesR5edD/SMfWCJWZDwu7003I7fzbglyVQg=")}
+					{
+						{
+							{
+								BudgetItemIdentifier{
+									BudgetItemTypes::Bill,
+									BudgetSource{"Club Dues"}
+								},
+								Money{411, 5200u, Iso4217Codes::PLN}
+							},
+							{
+								BudgetItemIdentifier{
+									BudgetItemTypes::Goal,
+									BudgetSource{"Computer"}
+								},
+								Money{411, 5200u, Iso4217Codes::PLN}
+							},
+							{
+								BudgetItemIdentifier{
+									BudgetItemTypes::Goal,
+									BudgetSource{"Vacation"}
+								},
+								Money{411, 5200u, Iso4217Codes::PLN}
+							}
+						}
+					},
+					{inPlace, QStringLiteral("b1ZfLVuVIYKVMVJ0/QQLRNMtIXjtcbbm0BUmJ37gQKQ=")}
 				}
 			},
 			{
--- a/src/file-storage/unit-tests/XmlBudgetFile-unit-tests.cpp	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/file-storage/unit-tests/XmlBudgetFile-unit-tests.cpp	Thu Oct 06 20:08:49 2022 +0200
@@ -167,7 +167,6 @@
 		);
 		const string actual{budgetFileContents.constData()};
 		const string expected{validContents()};
-		QSKIP("Expected to fail due to the survey distribution missing."); // TODO: Remove when distribution is added.
 		QVERIFY_THAT(actual, equals(expected));
 	}
 
--- a/src/navigation/internal/BudgetBankLedgers.h	Thu Oct 06 17:05:23 2022 +0200
+++ b/src/navigation/internal/BudgetBankLedgers.h	Thu Oct 06 20:08:49 2022 +0200
@@ -295,7 +295,7 @@
 	void update(
 		BudgetItemType item,
 		const foundation::Optional<banking::BankAccount>& ba = {}
-	);
+	); // TODO: Need to handle when the change in source for budget item in the survey checksum.
 	void removeWage(const budgeting::BudgetSource& source);
 	void removeBill(const budgeting::BudgetSource& source);
 	void removeDebt(const budgeting::BudgetSource& source);