32bool isValidAnnual(
int year,
const std::vector<std::string>& analysisIds) {
return isValidYear(year) && (!analysisIds.empty()); }
35 const auto begin = std::find_if_not(value.begin(), value.end(), [](
unsigned char c) { return std::isspace(c) != 0; });
36 const auto end = std::find_if_not(value.rbegin(), value.rend(), [](
unsigned char c) { return std::isspace(c) != 0; }).base();
37 if (begin >= end)
return {};
38 return std::string(begin, end);
41bool containsAnalysisId(
const std::vector<std::string>& analysisIds,
const std::string& analysisId) {
43 return std::find(analysisIds.begin(), analysisIds.end(), normalized) != analysisIds.end();
47 std::vector<std::string> unique;
48 unique.reserve(analysisIds.size());
49 for (
const auto& rawAnalysisId : analysisIds) {
51 if (analysisId.empty())
continue;
52 if (std::find(unique.begin(), unique.end(), analysisId) != unique.end())
return false;
53 unique.push_back(analysisId);
58bool containsAllAnalysisIds(
const std::vector<std::string>& analysisIds,
const std::vector<std::string>& requiredIds) {
59 return std::all_of(requiredIds.begin(), requiredIds.end(), [&](
const std::string&
id) { return containsAnalysisId(analysisIds, id); });
63 std::vector<std::string> normalized;
64 normalized.reserve(values.size());
65 for (
auto& rawValue : values) {
70 if (std::find(normalized.begin(), normalized.end(), value) != normalized.end()) {
73 normalized.push_back(std::move(value));
75 values = std::move(normalized);
80 if (trimmed.empty())
return std::nullopt;
81 static const std::regex isoYearPattern(R
"(^(\d{4})-\d{2}-\d{2}$)");
82 static const std::regex dottedYearPattern(R
"(^\d{1,2}\.\d{1,2}\.(\d{4})$)");
83 static const std::regex slashYearPattern(R
"(^\d{1,2}/\d{1,2}/(\d{4})$)");
84 static const std::regex standaloneYearPattern(R
"((?:^|[^0-9])(\d{4})(?:[^0-9]|$))");
86 auto parseYear = [](
const std::string& text) -> std::optional<int> {
88 const auto result = std::from_chars(text.data(), text.data() + text.size(), value);
89 if (result.ec != std::errc{} || result.ptr != text.data() + text.size())
return std::nullopt;
92 if (std::regex_match(trimmed, match, isoYearPattern) && match.size() > 1)
return parseYear(match[1].str());
93 if (std::regex_match(trimmed, match, dottedYearPattern) && match.size() > 1)
return parseYear(match[1].str());
94 if (std::regex_match(trimmed, match, slashYearPattern) && match.size() > 1)
return parseYear(match[1].str());
95 if (std::regex_search(trimmed, match, standaloneYearPattern) && match.size() > 1)
return parseYear(match[1].str());
102 result.
addIssue(
"",
"annual year is invalid");
112 if (*bookingYear != year) result.
addIssue(
transaction->id(),
"booking date year does not match annual year");
Shared annual validation helpers.
static bool isValid(int value) noexcept
Checks whether a raw year is valid.
const std::string & reason() const noexcept
void setReason(std::string value)
void setTransactionId(std::string value)
const std::string & transactionId() const noexcept
bool isValid() const noexcept
const std::vector< AnnualValidationIssue > & issues() const noexcept
void setIssues(std::vector< AnnualValidationIssue > value)
void addIssue(std::string transactionId, std::string reason)
bool hasIssues() const noexcept
std::optional< int > extractBookingYear(const std::string &bookingDate)
Extracts the year component from a booking date.
AnnualValidationResult validateTransactionsForYear(int year, const std::vector< std::shared_ptr< core::domain::Transaction > > &transactions)
Validates that transaction booking years match the annual year.
bool containsAllAnalysisIds(const std::vector< std::string > &analysisIds, const std::vector< std::string > &requiredIds)
Checks whether a list contains all required analysis ids.
void normalizeIds(std::vector< std::string > &values)
Normalizes and deduplicates analysis ids.
bool isValidAnnual(int year, const std::vector< std::string > &analysisIds)
Checks whether an annual aggregate is structurally valid.
bool containsAnalysisId(const std::vector< std::string > &analysisIds, const std::string &analysisId)
Checks whether an analysis id exists in a list.
bool hasUniqueAnalysisIds(const std::vector< std::string > &analysisIds)
Checks whether all analysis ids are unique.
bool isValidYear(int year)
Checks whether a year is valid for annual aggregates.
std::string normalizeId(std::string value)
Trims leading and trailing whitespace from an id.