23using core::domain::Transaction;
24using core::domain::catalog::WorkspaceCatalog;
28enum class AnalysisFilterOperator {
37enum class AnalysisDateField { BookingDate, Valuta };
39struct AnalysisFilterClause {
42 AnalysisFilterOperator op;
45std::string cleanValue(
const std::string &value);
47std::vector<std::string> splitList(
const std::string &value) {
48 std::vector<std::string> wanted;
50 for (
const char ch : cleanValue(value)) {
53 const auto token =
trim(current);
55 wanted.push_back(token);
60 current.push_back(ch);
63 const auto tail =
trim(current);
65 wanted.push_back(tail);
69std::string cleanValue(
const std::string &value) {
70 auto cleaned =
trim(value);
71 if (cleaned.size() >= 2 &&
72 ((cleaned.front() ==
'"' && cleaned.back() ==
'"') ||
73 (cleaned.front() ==
'\'' && cleaned.back() ==
'\''))) {
74 cleaned = cleaned.substr(1, cleaned.size() - 2);
79int dateToInt(
const std::string &value) {
83 std::string cleaned = cleanValue(value);
84 if (cleaned.find(
'-') != std::string::npos) {
85 std::string digits = cleaned;
86 digits.erase(std::remove(digits.begin(), digits.end(),
'-'), digits.end());
87 if (digits.size() == 8) {
89 return std::stoi(digits);
99 std::vector<std::string> parts;
100 std::istringstream stream(cleaned);
102 while (std::getline(stream, token,
104 parts.push_back(
trim(token));
107 if (parts.size() == 3) {
108 std::string dd = parts[0];
109 std::string mm = parts[1];
110 std::string yyyy = parts[2];
111 if (yyyy.size() == 4 && !dd.empty() && !mm.empty()) {
113 dd = std::string(
"0") + dd;
115 mm = std::string(
"0") + mm;
116 const std::string out = yyyy + mm + dd;
118 return std::stoi(out);
128 for (
const char ch : cleaned) {
129 if (std::isdigit(
static_cast<unsigned char>(ch)))
130 digits.push_back(ch);
132 if (digits.size() == 8) {
134 return std::stoi(digits);
142bool compareInt(AnalysisFilterOperator op,
int lhs,
int rhs) {
144 case AnalysisFilterOperator::GreaterEqual:
146 case AnalysisFilterOperator::LessEqual:
148 case AnalysisFilterOperator::Greater:
150 case AnalysisFilterOperator::Less:
152 case AnalysisFilterOperator::Equal:
154 case AnalysisFilterOperator::NotEqual:
160bool compareDouble(AnalysisFilterOperator op,
double lhs,
double rhs) {
162 case AnalysisFilterOperator::GreaterEqual:
164 case AnalysisFilterOperator::LessEqual:
166 case AnalysisFilterOperator::Greater:
168 case AnalysisFilterOperator::Less:
170 case AnalysisFilterOperator::Equal:
172 case AnalysisFilterOperator::NotEqual:
178std::optional<AnalysisFilterClause> parseClause(
const std::string &token) {
179 struct OperatorSpec {
180 std::string_view token;
181 AnalysisFilterOperator op;
184 static const OperatorSpec operators[] = {
186 AnalysisFilterOperator::GreaterEqual},
188 AnalysisFilterOperator::LessEqual},
190 AnalysisFilterOperator::NotEqual},
192 AnalysisFilterOperator::Greater},
194 AnalysisFilterOperator::Less},
196 AnalysisFilterOperator::Equal},
199 for (
const auto &spec : operators) {
200 const auto pos = token.find(spec.token);
201 if (pos == std::string::npos)
204 return AnalysisFilterClause{
205 trim(token.substr(0, pos)),
206 trim(token.substr(pos + spec.token.size())),
214std::vector<std::string> splitContractTypes(
const std::string &value) {
215 std::vector<std::string> wanted;
217 for (
const char ch :
lowerAscii(cleanValue(value))) {
220 const auto token =
trim(current);
222 wanted.push_back(token);
227 current.push_back(ch);
230 const auto tail =
trim(current);
232 wanted.push_back(tail);
236AnalysisDateField parseDateField(
const std::string &value) {
237 return lowerAscii(cleanValue(value)) ==
"valuta"
238 ? AnalysisDateField::Valuta
239 : AnalysisDateField::BookingDate;
242void addDatePredicate(core::application::analysis::AnalysisFilter &filter,
243 const AnalysisFilterClause &clause,
244 AnalysisDateField dateField) {
245 const int target = dateToInt(clause.value);
246 filter.
addPredicate([target, op = clause.op, dateField](
247 const std::shared_ptr<Transaction> &transaction,
251 const std::string sourceDate = dateField == AnalysisDateField::Valuta
252 ? transaction->valuta()
253 : transaction->bookingDate();
254 return compareInt(op, dateToInt(sourceDate), target);
258void addAmountPredicate(core::application::analysis::AnalysisFilter &filter,
259 const AnalysisFilterClause &clause) {
262 value = std::stod(clause.value);
268 [value, op = clause.op](
const std::shared_ptr<Transaction> &transaction,
270 return transaction && compareDouble(op, transaction->amount(), value);
274void addContractTypePredicate(
275 core::application::analysis::AnalysisFilter &filter,
276 const AnalysisFilterClause &clause) {
277 const auto wanted = splitContractTypes(clause.value);
281 filter.
addPredicate([wanted](
const std::shared_ptr<Transaction> &transaction,
286 const bool allowUnassigned =
287 std::find(wanted.begin(), wanted.end(),
290 if (transaction->contractId().empty())
291 return allowUnassigned;
293 const std::string contractId =
294 lowerAscii(cleanValue(transaction->contractId()));
295 for (
const auto &contract : state.
contracts()) {
298 if (
lowerAscii(cleanValue(contract->id())) != contractId)
301 const std::string contractType =
lowerAscii(cleanValue(contract->type()));
302 return std::find(wanted.begin(), wanted.end(), contractType) !=
310void addPropertyPredicate(core::application::analysis::AnalysisFilter &filter,
311 const AnalysisFilterClause &clause) {
312 const auto wanted = splitList(clause.value);
316 filter.
addPredicate([wanted](
const std::shared_ptr<Transaction> &transaction,
320 const bool allowUnassigned =
321 std::find(wanted.begin(), wanted.end(),
324 bool hasAnyProperty =
false;
326 for (
const auto &propertyId : wanted) {
327 if (std::find(transaction->propertyIds().begin(),
328 transaction->propertyIds().end(),
329 propertyId) != transaction->propertyIds().end()) {
333 if (!transaction->propertyIds().empty()) {
334 hasAnyProperty =
true;
337 if (transaction->contractId().empty())
338 return allowUnassigned && !hasAnyProperty;
339 const std::string contractId = cleanValue(transaction->contractId());
340 for (
const auto &contract : state.
contracts()) {
343 if (cleanValue(contract->id()) != contractId)
346 if (!contract->propertyIds().empty()) {
347 hasAnyProperty =
true;
349 for (
const auto &propertyId : wanted) {
350 if (std::find(contract->propertyIds().begin(),
351 contract->propertyIds().end(),
352 propertyId) != contract->propertyIds().end()) {
356 return allowUnassigned && !hasAnyProperty;
359 return allowUnassigned && !hasAnyProperty;
363void addAllocatablePredicate(
364 core::application::analysis::AnalysisFilter &filter,
365 const AnalysisFilterClause &clause) {
366 const std::string mode =
lowerAscii(cleanValue(clause.value));
367 if (mode !=
"allocatable" && mode !=
"non-allocatable")
370 const bool expected = mode ==
"allocatable";
372 [expected](
const std::shared_ptr<Transaction> &transaction,
374 return transaction && transaction->isAllocatable() == expected;
385 std::vector<AnalysisFilterClause> clauses;
386 AnalysisDateField dateField = AnalysisDateField::BookingDate;
387 std::istringstream ss(spec);
395 const auto clause = parseClause(token);
399 dateField = parseDateField(clause->value);
402 clauses.push_back(*clause);
405 for (
const auto &clause : clauses) {
407 addDatePredicate(f, clause, dateField);
409 addAmountPredicate(f, clause);
411 addContractTypePredicate(f, clause);
413 addPropertyPredicate(f, clause);
415 addAllocatablePredicate(f, clause);
422std::vector<std::shared_ptr<core::domain::Transaction>>
426 std::vector<std::shared_ptr<core::domain::Transaction>> out;
433 if (!filter.
empty() && !filter.
matches(transaction, state)) {
436 out.push_back(transaction);
Declares internal analysis filter predicates and collection helpers.
Defines stable analysis type, configuration, filter, and result keys.
Domain model for contracts.
Declares private string helpers shared across core implementation files.
Domain model for transactions.
const ContractList & contracts() const noexcept
Returns all contracts in the catalog.
Aggregates transaction predicates parsed from one analysis filter specification.
bool empty() const
Checks whether no predicates have been added.
void addPredicate(Pred p)
Adds one predicate to the composed filter.
bool matches(const std::shared_ptr< core::domain::Transaction > &t, const core::domain::catalog::WorkspaceCatalog &state) const
Evaluates all predicates against one transaction.
const TransactionList & transactions() const noexcept
Returns all transactions in the catalog.
constexpr std::string_view kNotEqual
constexpr std::string_view kGreaterEqual
constexpr std::string_view kEqual
constexpr std::string_view kGreater
constexpr std::string_view kLess
constexpr std::string_view kLessEqual
constexpr char kDateParts
constexpr char kAlternatives
constexpr std::string_view kDateField
constexpr std::string_view kContractType
constexpr std::string_view kAmount
constexpr std::string_view kDate
constexpr std::string_view kPropertyId
constexpr std::string_view kAllocatable
constexpr std::string_view kUnassigned
std::vector< std::shared_ptr< core::domain::Transaction > > collectAnalysisTransactions(const core::domain::catalog::WorkspaceCatalog &state, const AnalysisFilter &filter)
Collects transactions that satisfy the supplied filter.
AnalysisFilter parseAnalysisFilterSpec(const std::string &spec)
Parses a raw filter specification into predicate form.
std::string lowerAscii(std::string s)
Converts ASCII letters in a string to lowercase.
std::string trim(std::string s)
Removes leading and trailing ASCII whitespace from a string.
std::string lowerAscii(std::string s)
Converts ASCII letters in a string to lowercase.
std::string trim(std::string s)
Removes leading and trailing ASCII whitespace from a string.