Mercurial > hgweb.cgi > drn
changeset 899:563ef254bc3a
IN 32: Adds object verification for the construction of a ReconciledSurvey even after completing the survey.
Adds customisation for the presentationText of a Money object, including if it should be locale aware.
author | John Schneiderman <JohnMS@member.fsf.org> |
---|---|
date | Mon, 03 Oct 2022 19:37:42 +0200 |
parents | 97c104efbb5e |
children | 5c3c58aa9448 |
files | src/file-storage/internal/XmlBudgetFile.cpp src/foundation/external/foundation/PresentationText.cpp src/foundation/external/foundation/PresentationText.h src/surveying/external/surveying/ReconciledSurvey.cpp src/surveying/external/surveying/ReconciledSurvey.h src/surveying/unit-tests/ReconciledSurvey-unit-tests.cpp |
diffstat | 6 files changed, 180 insertions(+), 25 deletions(-) [+] |
line wrap: on
line diff
--- a/src/file-storage/internal/XmlBudgetFile.cpp Mon Oct 03 17:52:12 2022 +0200 +++ b/src/file-storage/internal/XmlBudgetFile.cpp Mon Oct 03 19:37:42 2022 +0200 @@ -643,7 +643,7 @@ }, reconciliationElement.date_ }, - {} // TODO: distribution amounts + {} // TODO: distribution amounts and validity value } ); }
--- a/src/foundation/external/foundation/PresentationText.cpp Mon Oct 03 17:52:12 2022 +0200 +++ b/src/foundation/external/foundation/PresentationText.cpp Mon Oct 03 19:37:42 2022 +0200 @@ -43,14 +43,20 @@ return QString::fromStdString(toStdString(code)); } -QString drn::foundation::presentationText(const Money& money) +QString drn::foundation::presentationText( + const Money& money, + const bool localeAware, + const bool withCurrency +) { const auto major{QString::number(money.major())}; const auto minor{QString::number(money.minor())}; const QLocale locale{}; - return QString{"%1%2%3"}.arg(major) - .arg(locale.decimalPoint()) - .arg(minor, minorUnitDigits(money.code()) + precision, '0'); + return QString{"%1%2%3%4"} + .arg(major) + .arg(localeAware ? locale.decimalPoint() : QChar{'.'}) + .arg(minor, minorUnitDigits(money.code()) + precision, '0') + .arg(withCurrency ? presentationText(money.code()) : QString{}); } QString drn::foundation::presentationText(const QDate& value)
--- a/src/foundation/external/foundation/PresentationText.h Mon Oct 03 17:52:12 2022 +0200 +++ b/src/foundation/external/foundation/PresentationText.h Mon Oct 03 19:37:42 2022 +0200 @@ -46,7 +46,11 @@ { DRN_FOUNDATION_EXPORT ::QString presentationText(const pecunia::currency::Iso4217Codes& code); -DRN_FOUNDATION_EXPORT ::QString presentationText(const pecunia::currency::Money& money); +DRN_FOUNDATION_EXPORT ::QString presentationText( + const pecunia::currency::Money& money, + const bool localeAware = true, // TODO: strong type or leave as bool? + const bool withCurrency = false +); DRN_FOUNDATION_EXPORT ::QString presentationText(const ::QDate& value); DRN_FOUNDATION_EXPORT ::QString presentationText(const ::qint8& value); DRN_FOUNDATION_EXPORT ::QString presentationText(const ::quint8& value);
--- a/src/surveying/external/surveying/ReconciledSurvey.cpp Mon Oct 03 17:52:12 2022 +0200 +++ b/src/surveying/external/surveying/ReconciledSurvey.cpp Mon Oct 03 19:37:42 2022 +0200 @@ -19,18 +19,28 @@ *******************************************************************************/ #include "ReconciledSurvey.h" using pecunia::currency::Money; +using ::QString; using std::map; using drn::banking::BankAccount; using drn::banking::presentationText; using drn::banking::ReconciledBankAccount; using drn::budgeting::BudgetItemIdentifier; +using drn::foundation::Optional; using drn::surveying::ReconciledSurvey; using drn::surveying::ReconciledSurveys; #include <pecunia/Math.h> using pecunia::math::sum; +#include <QCryptographicHash> +using ::QCryptographicHash; #include <QDate> using ::QDate; +#include <QObject> +using ::QObject; +#include <QStringBuilder> +// using operator% +#include <Qt> +using Qt::DateFormat; #include <ostream> using std::ostream; @@ -45,22 +55,57 @@ using drn::banking::BankName; #include <banking/BankingErrors.h> using drn::banking::BankError; -#include <foundation/Optional.hpp> -using drn::foundation::Optional; +#include <foundation/Error.h> +using drn::foundation::Error; #include <foundation/PresentationText.h> using drn::foundation::presentationText; +namespace +{ + +QString calculateValidity( + ReconciledBankAccount reconciled, + map<BudgetItemIdentifier, Money> distribution +) +{ + QString value{presentationText(reconciled.bankAccount())}; + value = value % presentationText(reconciled.balance(), false, true); + + if (reconciled.reconciledOn().hasValue()) + value = value % reconciled.reconciledOn()->toString(DateFormat::ISODate); + + for (const auto& itemAmount : distribution) + value = value % presentationText(itemAmount.first) + % presentationText(itemAmount.second, false, true); + return QCryptographicHash::hash(value.toUtf8(), QCryptographicHash::Sha256).toBase64(); +} + +} + //{ ReconciledSurvey ReconciledSurvey::ReconciledSurvey( ReconciledBankAccount reconciled, - map<BudgetItemIdentifier, Money> distribution + map<BudgetItemIdentifier, Money> distribution, + Optional<QString> validity ) : reconciled_{move(reconciled)}, - distribution_{move(distribution)} + distribution_{move(distribution)}, + validity_{move(validity)} { - // TODO: perform expected verifications. Note that distribution may not always be exactly equal due to currency exchange rate fluctuations, so use a has of the values to verify everything was correct at the time it was created. + if (this->validity_.hasValue()) + { + const auto validityCheck{calculateValidity(this->reconciled_, this->distribution_)}; + + if (this->validity_ != validityCheck) + throw Error{ + QObject::tr( + "The supplied values for a reconciled account does not match the expected " + "validity check value, %1." + ).arg(validityCheck) + }; + } } const ReconciledBankAccount& ReconciledSurvey::reconciled() const noexcept @@ -68,12 +113,16 @@ return this->reconciled_; } -const map<BudgetItemIdentifier, Money>& ReconciledSurvey::distribution( -) const noexcept +const map<BudgetItemIdentifier, Money>& ReconciledSurvey::distribution() const noexcept { return this->distribution_; } +const Optional<QString>& ReconciledSurvey::validity() const noexcept +{ + return this->validity_; +} + bool drn::surveying::operator==(const ReconciledSurvey& lhs, const ReconciledSurvey& rhs) { return tie(lhs.reconciled(), lhs.distribution()) == tie(rhs.reconciled(), rhs.distribution()); @@ -151,6 +200,7 @@ .arg(presentationText(reconciledBalance)) .arg(presentationText(expectedBalance)) }; + // TODO: survey.verification_ = calculateVerification(); if (this->count(ba) == 0) {
--- a/src/surveying/external/surveying/ReconciledSurvey.h Mon Oct 03 17:52:12 2022 +0200 +++ b/src/surveying/external/surveying/ReconciledSurvey.h Mon Oct 03 19:37:42 2022 +0200 @@ -21,6 +21,7 @@ #define DRN_SURVEYING_RECONCILEDSURVEY_H_ #include <pecunia/Money.h> +#include <QString> #include <iosfwd> #include <map> @@ -28,6 +29,7 @@ #include <budgeting/BudgetItemIdentifier.h> #include <banking/BankAccount.h> #include <banking/Reconciliation.h> +#include <foundation/Optional.hpp> #include "surveying_export.h" @@ -42,38 +44,46 @@ class AccountNumber; } -namespace foundation -{ - -template<typename> -class Optional; - -} namespace surveying { class DRN_SURVEYING_EXPORT ReconciledSurvey { banking::ReconciledBankAccount reconciled_; - std::map< - budgeting::BudgetItemIdentifier, - pecunia::currency::Money - > distribution_; + std::map<budgeting::BudgetItemIdentifier, pecunia::currency::Money> distribution_; + foundation::Optional<::QString> validity_; public: ReconciledSurvey() = default; + /** + * @brief Full and partial initialisation constructor. + * + * @exception Error When a supplied validity value does not match the calculated value. + * + * @param reconciled The bank account that was reconciled. + * @param distribution The distribution of the balance in the account. + * @param validity When supplied, the value that represents an object whose values were verified + * after having completed a survey. This is in lieu of verifying the balances upon object + * creation since exchange rates change over time. If not supplied, no verification is + * performed, i.e. the object is partially initialised. + */ ReconciledSurvey( banking::ReconciledBankAccount reconciled, std::map< budgeting::BudgetItemIdentifier, pecunia::currency::Money - > distribution + > distribution, + foundation::Optional<::QString> validity = {} ); const banking::ReconciledBankAccount& reconciled() const noexcept; const std::map< budgeting::BudgetItemIdentifier, pecunia::currency::Money >& distribution() const noexcept; + /** + * @brief The value that represents a valid object after having completed a survey. + */ + const foundation::Optional<::QString>& validity() const noexcept; }; class DRN_SURVEYING_EXPORT ReconciledSurveys : std::map<banking::BankAccount, ReconciledSurvey>
--- a/src/surveying/unit-tests/ReconciledSurvey-unit-tests.cpp Mon Oct 03 17:52:12 2022 +0200 +++ b/src/surveying/unit-tests/ReconciledSurvey-unit-tests.cpp Mon Oct 03 19:37:42 2022 +0200 @@ -37,10 +37,30 @@ #include <map> using std::map; +#include <accounting/AccountCode.h> +using drn::accounting::AccountCode; +#include <accounting/AccountNumber.h> +using drn::accounting::AccountNumber; +#include <banking/BankAccount.h> +using drn::banking::BankAccount; +#include <banking/BankAccountType.h> +using drn::banking::BankAccountType; +using drn::banking::SupportedAccountTypes; +#include <banking/BankName.h> +using drn::banking::BankName; #include <banking/Reconciliation.h> using drn::banking::ReconciledBankAccount; #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 <foundation/Error.h> +using drn::foundation::Error; +#include <foundation/Optional.hpp> +using drn::foundation::inPlace; +using drn::foundation::Optional; #include <surveying/ReconciledSurvey.h> using drn::surveying::ReconciledSurvey; @@ -56,12 +76,77 @@ { Q_OBJECT + const map<BudgetItemIdentifier, Money> distribution_{ + { + { + BudgetItemIdentifier{BudgetItemTypes::Bill, BudgetSource{"Web Hosting"}}, + Money{50, 1234, Iso4217Codes::USD} + } + } + }; + const ReconciledBankAccount reconciled_{ + BankAccount{ + BankName{"C1st Credit Union"}, + BankAccountType{ + AccountCode{AccountNumber{4321}, "Primary Share"}, + SupportedAccountTypes::Savings + } + }, + Money{50, 1234, Iso4217Codes::USD}, + Optional<QDate>{inPlace, 2022, 10, 3} + }; + const Optional<QString> validity_{ + inPlace, + QStringLiteral("MxlBAZPxUL+Ken7+CdjrmJhKe37ejHWG3USj5PBeCWw=") + }; + private slots: void constructor_DefaultInit_ShouldSet() { ReconciledSurvey survey{}; QVERIFY_THAT(survey.distribution(), equals(map<BudgetItemIdentifier, Money>{})); QVERIFY_THAT(survey.reconciled(), equals(ReconciledBankAccount{})); + QVERIFY_FALSE(survey.validity().hasValue()); + } + + void constructor_PartialInit_ShouldSet() + { + const ReconciledSurvey survey{this->reconciled_, this->distribution_}; + QVERIFY_THAT(survey.distribution(), equals(this->distribution_)); + QVERIFY_THAT(survey.reconciled(), equals(this->reconciled_)); + QVERIFY_FALSE(survey.validity().hasValue()); + } + + void constructor_FullInit_ShouldSet() + { + const ReconciledSurvey survey{this->reconciled_, this->distribution_, this->validity_}; + QVERIFY_THAT(survey.distribution(), equals(this->distribution_)); + QVERIFY_THAT(survey.reconciled(), equals(this->reconciled_)); + QVERIFY_THAT(survey.validity(), equals(this->validity_)); + } + + void constructor_ReconciledMissmatchFromValidity_ShouldThrow() + { + QVERIFY_EXCEPTION_THROWN( + (ReconciledSurvey{{}, this->distribution_, this->validity_}), + Error + ); + } + + void constructor_DistributionMissmatchFromValidity_ShouldThrow() + { + QVERIFY_EXCEPTION_THROWN( + (ReconciledSurvey{this->reconciled_, {}, this->validity_}), + Error + ); + } + + void constructor_ValidityMissmatch_ShouldThrow() + { + QVERIFY_EXCEPTION_THROWN( + (ReconciledSurvey{this->reconciled_, this->distribution_, QStringLiteral("123456789")}), + Error + ); } };