FOSSredder 0.5.0
Loading...
Searching...
No Matches
StatementImportRunner.cpp
Go to the documentation of this file.
1
5
7
17#include "core/jobs/JobTypes.h"
20
21#include <algorithm>
22#include <cctype>
23#include <chrono>
24#include <cstdint>
25#include <exception>
26#include <filesystem>
27#include <iomanip>
28#include <memory>
29#include <random>
30#include <sstream>
31#include <string>
32#include <utility>
33#include <vector>
34
36namespace {
37
38constexpr int kKeepImportRuns = 20;
39constexpr auto kRunNameImport = "import";
40
41std::string normalizedAllocatableMode(std::string mode) {
43 std::transform(mode.begin(), mode.end(), mode.begin(), [](unsigned char ch) {
44 return static_cast<char>(std::tolower(ch));
45 });
46 return mode;
47}
48
49std::string fallbackDraftTransactionId() {
50 static thread_local std::mt19937_64 generator{std::random_device{}()};
51 std::uniform_int_distribution<std::uint64_t> distribution;
52 std::ostringstream out;
53 out << std::hex << distribution(generator) << distribution(generator);
54 return out.str();
55}
56
57std::string fileStemFromPath(const std::string &path) {
58 try {
59 return std::filesystem::path(path).stem().string();
60 } catch (...) {
61 return {};
62 }
63}
64
66toStatementImportState(core::jobs::JobState state) {
67 switch (state) {
79 default:
81 }
82}
83
84core::ports::workspace::StatementSnapshot
85toStatementSnapshot(const std::shared_ptr<core::domain::Statement> &statement) {
86 core::ports::workspace::StatementSnapshot out;
87 if (!statement)
88 return out;
89 out.id = statement->id();
90 out.name = statement->name();
91 out.transactionIds = statement->transactionIds();
92 out.createdAt = statement->createdAt();
93 out.updatedAt = statement->updatedAt();
94 return out;
95}
96
97core::ports::importing::draft::TransactionDraft
98toPortDraft(const core::application::importing::draft::TransactionDraft &draft) {
99 core::ports::importing::draft::TransactionDraft out;
100 out.id = draft.id;
101 out.statementDraftId = draft.statementDraftId;
102 out.position = draft.position;
103 out.name = draft.name;
104 out.bookingDate = draft.bookingDate;
105 out.valuta = draft.valuta;
106 out.amount = draft.amount;
107 out.actorText = draft.actorText;
108 out.propertyText = draft.propertyText;
109 out.actorId = draft.actorId;
110 out.actorSelected = draft.actorSelected;
111 out.contractId = draft.contractId;
112 out.contractSelected = draft.contractSelected;
113 out.metadata = draft.metadata;
114 out.proofImageData = draft.proofImageData;
115 out.type = draft.type;
116 out.allocatable = draft.allocatable;
117 out.allocatableSelected = draft.allocatableSelected;
118 out.status = static_cast<int>(draft.status);
119 out.propertyIds = draft.propertyIds;
120 out.createdAt = draft.createdAt;
121 out.updatedAt = draft.updatedAt;
122 return out;
123}
124
125core::application::importing::draft::TransactionDraft
126toCoreDraft(const core::ports::importing::draft::TransactionDraft &draft) {
127 core::application::importing::draft::TransactionDraft out;
128 out.id = draft.id;
129 out.statementDraftId = draft.statementDraftId;
130 out.position = draft.position;
131 out.name = draft.name;
132 out.bookingDate = draft.bookingDate;
133 out.valuta = draft.valuta;
134 out.amount = draft.amount;
135 out.actorText = draft.actorText;
136 out.propertyText = draft.propertyText;
137 out.actorId = draft.actorId;
138 out.actorSelected = draft.actorSelected;
139 out.contractId = draft.contractId;
140 out.contractSelected = draft.contractSelected;
141 out.metadata = draft.metadata;
142 out.proofImageData = draft.proofImageData;
143 out.type = draft.type;
144 out.allocatable = draft.allocatable;
145 out.allocatableSelected = draft.allocatableSelected;
146 out.status = static_cast<core::domain::Transaction::Status>(draft.status);
147 out.propertyIds = draft.propertyIds;
148 out.createdAt = draft.createdAt;
149 out.updatedAt = draft.updatedAt;
150 return out;
151}
152
153core::ports::importing::draft::StatementDraft toPortStatementDraft(
154 const core::application::importing::draft::StatementDraft &draft) {
155 core::ports::importing::draft::StatementDraft out;
156 out.id = draft.id;
157 out.name = draft.name;
158 out.transactionIds = draft.transactionIds;
159 out.createdAt = draft.createdAt;
160 out.updatedAt = draft.updatedAt;
161 out.transactions.reserve(draft.transactions.size());
162 for (const auto &transaction : draft.transactions) {
163 out.transactions.push_back(toPortDraft(transaction));
164 }
165 return out;
166}
167
168core::application::importing::draft::StatementDraft toCoreStatementDraft(
169 const core::ports::importing::draft::StatementDraft &draft) {
170 core::application::importing::draft::StatementDraft out;
171 out.id = draft.id;
172 out.name = draft.name;
173 out.transactionIds = draft.transactionIds;
174 out.createdAt = draft.createdAt;
175 out.updatedAt = draft.updatedAt;
176 out.transactions.reserve(draft.transactions.size());
177 for (const auto &transaction : draft.transactions) {
178 out.transactions.push_back(toCoreDraft(transaction));
179 }
180 return out;
181}
182
183core::application::importing::draft::DraftSuggestionBucket toCoreBucket(
184 const core::ports::importing::draft::DraftSuggestionBucket &bucket) {
185 core::application::importing::draft::DraftSuggestionBucket out;
186 out.sourceText = bucket.sourceText;
187 out.candidates.reserve(bucket.candidates.size());
188 for (const auto &candidate : bucket.candidates) {
189 core::application::importing::draft::DraftSuggestionCandidate item;
190 item.entityId = candidate.entityId;
191 item.label = candidate.label;
192 item.entityType = candidate.entityType;
193 item.sourceText = candidate.sourceText;
194 item.rationale = candidate.rationale;
195 item.lastUsedAt = candidate.lastUsedAt;
196 item.matchedAliases = candidate.matchedAliases;
197 item.confidence = candidate.confidence;
198 item.score = candidate.score;
199 item.aliasWeight = candidate.aliasWeight;
200 item.recencyWeight = candidate.recencyWeight;
201 item.hitCount = candidate.hitCount;
202 out.candidates.push_back(std::move(item));
203 }
204 return out;
205}
206
207core::ports::importing::draft::DraftSuggestionBucket toPortBucket(
208 const core::application::importing::draft::DraftSuggestionBucket &bucket) {
209 core::ports::importing::draft::DraftSuggestionBucket out;
210 out.sourceText = bucket.sourceText;
211 out.candidates.reserve(bucket.candidates.size());
212 for (const auto &candidate : bucket.candidates) {
213 core::ports::importing::draft::DraftSuggestionCandidate item;
214 item.entityId = candidate.entityId;
215 item.label = candidate.label;
216 item.entityType = candidate.entityType;
217 item.sourceText = candidate.sourceText;
218 item.rationale = candidate.rationale;
219 item.lastUsedAt = candidate.lastUsedAt;
220 item.matchedAliases = candidate.matchedAliases;
221 item.confidence = candidate.confidence;
222 item.score = candidate.score;
223 item.aliasWeight = candidate.aliasWeight;
224 item.recencyWeight = candidate.recencyWeight;
225 item.hitCount = candidate.hitCount;
226 out.candidates.push_back(std::move(item));
227 }
228 return out;
229}
230
231core::application::importing::draft::DraftLinkSelection toCoreSelection(
232 const core::ports::importing::draft::DraftLinkSelection &selection) {
233 core::application::importing::draft::DraftLinkSelection out;
234 out.name = selection.name;
235 out.metadata = selection.metadata;
236 out.actorText = selection.actorText;
237 out.propertyText = selection.propertyText;
238 out.actorId = selection.actorId;
239 out.actorSelected = selection.actorSelected;
240 out.contractId = selection.contractId;
241 out.contractSelected = selection.contractSelected;
242 out.type = selection.type;
243 out.allocatable = selection.allocatable;
245 out.propertyIds = selection.propertyIds;
246 out.actorSuggestions = toCoreBucket(selection.actorSuggestions);
247 out.propertySuggestions = toCoreBucket(selection.propertySuggestions);
248 out.contractSuggestions = toCoreBucket(selection.contractSuggestions);
249 return out;
250}
251
252core::ports::importing::draft::DraftDerivedState toPortDerivedState(
253 const core::application::importing::draft::DraftDerivedState &derived) {
254 core::ports::importing::draft::DraftDerivedState out;
255 out.proofSource = derived.proofSource;
256 out.actorSeedText = derived.actorSeedText;
272 toPortBucket({{}, {derived.actorTopSuggestion}}).candidates.front();
274 toPortBucket({{}, {derived.propertyTopSuggestion}}).candidates.front();
276 toPortBucket({{}, {derived.contractTopSuggestion}}).candidates.front();
280 for (const auto &row : derived.actorChoices)
281 out.actorChoices.push_back({row.id, row.name, row.display, row.type,
282 row.allocatableMode, row.aliases, row.actorIds,
283 row.propertyIds, row.synthetic, row.confidence,
284 row.sourceText});
285 for (const auto &row : derived.contractChoices)
286 out.contractChoices.push_back({row.id, row.name, row.display, row.type,
287 row.allocatableMode, row.aliases,
288 row.actorIds, row.propertyIds,
289 row.synthetic, row.confidence,
290 row.sourceText});
291 for (const auto &row : derived.propertyRows)
292 out.propertyRows.push_back({row.id, row.name, row.display, row.type,
293 row.allocatableMode, row.aliases, row.actorIds,
294 row.propertyIds, row.synthetic, row.confidence,
295 row.sourceText});
296 out.autoPropertyIds = derived.autoPropertyIds;
297 return out;
298}
299
300core::ports::importing::draft::DraftLinkSelection toPortSelection(
301 const core::ports::importing::draft::TransactionDraft &draft) {
302 core::ports::importing::draft::DraftLinkSelection out;
303 out.name = draft.name;
304 out.metadata = draft.metadata;
305 out.actorText = draft.actorText;
306 out.propertyText = draft.propertyText;
307 out.actorId = draft.actorId;
308 out.actorSelected = draft.actorSelected;
309 out.contractId = draft.contractId;
310 out.contractSelected = draft.contractSelected;
311 out.type = draft.type;
312 out.allocatable = draft.allocatable;
313 out.allocatableSelected = draft.allocatableSelected;
314 out.propertyIds = draft.propertyIds;
315 return out;
316}
317
318core::application::importing::draft::DraftSuggestionCandidate toCoreCandidate(
319 const core::ports::importing::draft::DraftSuggestionCandidate &candidate) {
320 return {candidate.entityId,
321 candidate.label,
322 candidate.entityType,
323 candidate.sourceText,
324 candidate.rationale,
325 candidate.lastUsedAt,
326 candidate.matchedAliases,
327 candidate.confidence,
328 candidate.score,
329 candidate.aliasWeight,
330 candidate.recencyWeight,
331 candidate.hitCount};
332}
333
334core::application::importing::draft::DraftChoiceRow toCoreChoiceRow(
335 const core::ports::importing::draft::DraftChoiceRow &row) {
336 return {row.id, row.name, row.display, row.type,
337 row.allocatableMode, row.aliases, row.actorIds, row.propertyIds,
338 row.synthetic, row.confidence, row.sourceText};
339}
340
341core::application::importing::draft::DraftDerivedState toCoreDerivedState(
342 const core::ports::importing::draft::DraftDerivedState &derived) {
343 core::application::importing::draft::DraftDerivedState out;
344 out.proofSource = derived.proofSource;
345 out.actorSeedText = derived.actorSeedText;
360 out.actorTopSuggestion = toCoreCandidate(derived.actorTopSuggestion);
361 out.propertyTopSuggestion = toCoreCandidate(derived.propertyTopSuggestion);
362 out.contractTopSuggestion = toCoreCandidate(derived.contractTopSuggestion);
366 for (const auto &row : derived.actorChoices) {
367 out.actorChoices.push_back(toCoreChoiceRow(row));
368 }
369 for (const auto &row : derived.contractChoices) {
370 out.contractChoices.push_back(toCoreChoiceRow(row));
371 }
372 for (const auto &row : derived.propertyRows) {
373 out.propertyRows.push_back(toCoreChoiceRow(row));
374 }
375 out.autoPropertyIds = derived.autoPropertyIds;
376 return out;
377}
378
380toCoreAutoSelectionMode(
382 switch (mode) {
384 return core::application::importing::draft::DraftAutoSelectionMode::
385 InteractiveSync;
387 default:
388 return core::application::importing::draft::DraftAutoSelectionMode::
389 InitialImport;
390 }
391}
392
393core::application::importing::draft::TransactionDraftPatch toCorePatch(
394 const core::ports::importing::draft::TransactionDraftPatch &patch) {
395 core::application::importing::draft::TransactionDraftPatch out;
396 out.hasName = patch.hasName;
397 out.name = patch.name;
398 out.hasBookingDate = patch.hasBookingDate;
399 out.bookingDate = patch.bookingDate;
400 out.hasValuta = patch.hasValuta;
401 out.valuta = patch.valuta;
402 out.hasAmount = patch.hasAmount;
403 out.amount = patch.amount;
404 out.hasStatus = patch.hasStatus;
405 out.status = patch.status;
406 out.hasAllocatable = patch.hasAllocatable;
407 out.allocatable = patch.allocatable;
409 return out;
410}
411
412core::ports::workspace::WorkspaceSnapshot
413mergeSnapshots(core::ports::workspace::WorkspaceSnapshot primary,
414 const core::ports::workspace::WorkspaceSnapshot &secondary) {
415 if (primary.actors.empty())
416 primary.actors = secondary.actors;
417 if (primary.properties.empty())
418 primary.properties = secondary.properties;
419 if (primary.contracts.empty())
420 primary.contracts = secondary.contracts;
421 if (primary.statements.empty())
422 primary.statements = secondary.statements;
423 if (primary.transactions.empty())
424 primary.transactions = secondary.transactions;
425 if (primary.analyses.empty())
426 primary.analyses = secondary.analyses;
427 if (primary.annuals.empty())
428 primary.annuals = secondary.annuals;
429 if (primary.statementDrafts.empty())
430 primary.statementDrafts = secondary.statementDrafts;
431 if (primary.importLogs.empty())
432 primary.importLogs = secondary.importLogs;
433 if (primary.exportLogs.empty())
434 primary.exportLogs = secondary.exportLogs;
435 if (!primary.hasCurrentPath && secondary.hasCurrentPath) {
436 primary.currentPath = secondary.currentPath;
437 primary.hasCurrentPath = true;
438 }
439 return primary;
440}
441
442std::filesystem::path defaultRunBasePath() {
443 return std::filesystem::temp_directory_path() / "fossredder" / "import-runs";
444}
445
446std::string utcTimestamp() {
447 const auto now = std::chrono::system_clock::now();
448 const auto millis =
449 std::chrono::duration_cast<std::chrono::milliseconds>(
450 now.time_since_epoch()) %
451 1000;
452 const auto time = std::chrono::system_clock::to_time_t(now);
453 std::tm tm{};
454#ifdef _WIN32
455 gmtime_s(&tm, &time);
456#else
457 gmtime_r(&time, &tm);
458#endif
459 std::ostringstream out;
460 out << std::put_time(&tm, "%Y%m%d%H%M%S") << std::setw(3)
461 << std::setfill('0') << millis.count();
462 return out.str();
463}
464
465bool isImportRunDir(const std::filesystem::directory_entry &entry) {
466 if (!entry.is_directory())
467 return false;
468 const auto name = entry.path().filename().string();
469 return name.find(std::string("_") + kRunNameImport + "_") !=
470 std::string::npos;
471}
472
473void cleanupOldImportRuns(const std::filesystem::path &basePath) {
474 if (!std::filesystem::exists(basePath))
475 return;
476
477 std::vector<std::filesystem::directory_entry> entries;
478 for (const auto &entry : std::filesystem::directory_iterator(basePath)) {
479 if (isImportRunDir(entry))
480 entries.push_back(entry);
481 }
482
483 std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b) {
484 return a.last_write_time() > b.last_write_time();
485 });
486
487 for (std::size_t i = kKeepImportRuns; i < entries.size(); ++i) {
488 std::error_code ec;
489 std::filesystem::remove_all(entries[i].path(), ec);
490 }
491}
492
494buildImportSpec(const core::ports::importing::ImportRequest &request,
495 const std::filesystem::path &basePath) {
496 cleanupOldImportRuns(basePath);
497 std::filesystem::create_directories(basePath);
498
499 const auto timestamp = utcTimestamp();
500 int suffix = 1;
501 while (true) {
502 const auto runName = timestamp + "_" + kRunNameImport + "_" +
503 std::to_string(suffix);
504 const auto runRoot = basePath / runName;
505 if (!std::filesystem::exists(runRoot)) {
506 std::filesystem::create_directories(runRoot);
507 return {request.sourcePath, runRoot.string(), timestamp};
508 }
509 ++suffix;
510 }
511}
512
513} // namespace
514
516public:
517 Impl(std::shared_ptr<StatementImportJobSystem> jobSystem,
518 std::string runBasePath,
519 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter)
520 : jobSystem(std::move(jobSystem)),
521 runBasePath(runBasePath.empty() ? defaultRunBasePath()
522 : std::filesystem::path(runBasePath)),
523 errorReporter(std::move(errorReporter)) {}
524
525 std::shared_ptr<StatementImportJobSystem> jobSystem;
526 std::filesystem::path runBasePath;
527 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter;
528};
529
531 std::shared_ptr<IImportStatement> importService, std::string runBasePath,
532 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter)
534 std::make_shared<StatementImportJobSystem>(std::move(importService),
535 errorReporter),
536 std::move(runBasePath), std::move(errorReporter)) {}
537
539 std::shared_ptr<StatementImportJobSystem> jobSystem, std::string runBasePath,
540 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter)
541 : impl_(std::make_unique<Impl>(std::move(jobSystem), std::move(runBasePath),
542 std::move(errorReporter))) {}
543
545
550 if (!impl_->jobSystem)
551 return {};
552
553 try {
554 const auto spec = buildImportSpec(request, impl_->runBasePath);
555 const auto importId = impl_->jobSystem->startImportStatement(spec);
556 const auto subscriptionId = impl_->jobSystem->subscribe(
557 importId, [callback = std::move(callback)](
558 const core::jobs::JobEvent &event) {
559 if (!callback)
560 return;
561 callback({event.jobId, toStatementImportState(event.state),
562 event.progress, event.message});
563 });
564 return {importId, subscriptionId};
565 } catch (const std::exception &) {
566 if (impl_->errorReporter) {
567 impl_->errorReporter->reportException(
569 "core::application::importing::StatementImportRunner::startStatementImport",
570 std::current_exception());
571 }
572 return {};
573 } catch (...) {
574 if (impl_->errorReporter) {
575 impl_->errorReporter->reportException(
577 "core::application::importing::StatementImportRunner::startStatementImport",
578 std::current_exception());
579 }
580 return {};
581 }
582}
583
586 if (impl_->jobSystem && !handle.importId.empty() && handle.subscriptionId != 0)
587 impl_->jobSystem->unsubscribe(handle.importId, handle.subscriptionId);
588}
589
592 if (impl_->jobSystem && !handle.importId.empty())
593 impl_->jobSystem->cancel(handle.importId);
594}
595
598 if (impl_->jobSystem && !handle.importId.empty())
599 impl_->jobSystem->pause(handle.importId);
600}
601
604 if (impl_->jobSystem && !handle.importId.empty())
605 impl_->jobSystem->resume(handle.importId);
606}
607
612 if (!impl_->jobSystem || handle.importId.empty())
613 return result;
614
615 result.statement =
616 toStatementSnapshot(impl_->jobSystem->statementResult(handle.importId));
617 result.hasStatement =
618 !result.statement.id.empty() || !result.statement.name.empty();
619 const auto transactions =
620 impl_->jobSystem->statementTransactions(handle.importId);
621 result.transactions.reserve(transactions.size());
622 for (const auto &transaction : transactions) {
623 result.transactions.push_back(toPortDraft(transaction));
624 }
625 result.artifacts =
626 impl_->jobSystem->takeStatementArtifacts(handle.importId);
627 return result;
628}
629
638
643 auto coreDraft = toCoreDraft(draft);
644 bool changed = false;
645 switch (edit.kind) {
647 changed = draft::applyTransactionPatch(coreDraft, toCorePatch(edit.patch));
648 break;
650 if (const auto parsed = transaction::parseAmountString(edit.text)) {
652 patch.hasAmount = true;
653 patch.amount = *parsed;
654 changed = draft::applyTransactionPatch(coreDraft, patch);
655 }
656 break;
658 changed = draft::applyActorSelection(coreDraft, edit.id);
659 break;
661 changed = draft::clearActorSelection(coreDraft);
662 break;
664 changed = draft::applyPropertySelection(coreDraft, edit.id);
665 break;
666 case core::ports::importing::draft::TransactionDraftEditKind::
667 SetPropertySelected:
668 changed = draft::setPropertySelected(coreDraft, edit.id, edit.selected);
669 break;
671 changed = draft::applyContractSelection(coreDraft,
672 toCoreChoiceRow(edit.choice));
673 break;
674 case core::ports::importing::draft::TransactionDraftEditKind::
675 SelectContractFromCatalog:
678 edit.id);
679 break;
681 changed = draft::clearContractSelection(coreDraft);
682 break;
683 case core::ports::importing::draft::TransactionDraftEditKind::
684 ApplyDerivedSelections:
686 coreDraft, toCoreDerivedState(edit.derived),
687 toCoreAutoSelectionMode(edit.autoSelectionMode));
688 break;
689 }
690 if (!changed) {
691 return false;
692 }
693 draft = toPortDraft(coreDraft);
694 return true;
695}
696
701 auto coreDraft = toCoreStatementDraft(draft);
703 switch (edit.kind) {
705 result.changed = draft::renameStatementDraft(coreDraft, edit.text);
706 result.selectedTransactionIndex = edit.index;
707 break;
708 case core::ports::importing::draft::StatementDraftEditKind::
709 InsertTransactionAfter:
711 draft::insertTransactionAfter(coreDraft, edit.index);
712 result.changed = result.selectedTransactionIndex >= 0;
713 break;
714 case core::ports::importing::draft::StatementDraftEditKind::
715 RemoveTransactionAt:
717 draft::removeTransactionAt(coreDraft, edit.index);
718 result.changed = result.selectedTransactionIndex >= 0;
719 break;
720 }
721 if (result.changed) {
722 draft = toPortStatementDraft(coreDraft);
723 }
724 return result;
725}
726
729 const std::string &sourceFile,
732 const std::vector<core::ports::importing::draft::TransactionDraft>
733 &transactions,
734 const std::string &draftId) const {
736 draft.id = draftId;
737 draft.name = !statement.name.empty() ? statement.name : fileStemFromPath(sourceFile);
738 draft.transactionIds = statement.transactionIds;
739 draft.createdAt = statement.createdAt;
740 draft.updatedAt = statement.updatedAt;
741 draft.transactions.reserve(transactions.size());
742
743 for (std::size_t i = 0; i < transactions.size(); ++i) {
744 auto transaction = transactions[i];
745 const auto signals = draft::buildDraftTextSignals(
747 toCoreDraft(transaction));
748 if (transaction.id.empty()) {
749 transaction.id = fallbackDraftTransactionId();
750 }
751 transaction.statementDraftId = draft.id;
752 transaction.position = static_cast<int>(i);
753 transaction.name = "Transaction " + std::to_string(i + 1);
754 transaction.actorText = signals.actorText;
755 transaction.propertyText = signals.propertyText;
756 transaction.type = signals.typeText;
757
759 edit.kind = core::ports::importing::draft::TransactionDraftEditKind::
760 ApplyDerivedSelections;
761 edit.derived = buildDraftDerivedState(state, toPortSelection(transaction));
762 edit.autoSelectionMode =
765 draft.transactionIds.push_back(transaction.id);
766 draft.transactions.push_back(std::move(transaction));
767 }
768
769 return draft;
770}
771
776 draft.id = snapshot.id;
777 draft.name = snapshot.name;
778 draft.transactionIds = snapshot.transactionIds;
779 draft.createdAt = snapshot.createdAt;
780 draft.updatedAt = snapshot.updatedAt;
781 draft.transactions.reserve(snapshot.transactions.size());
782
783 for (const auto &tx : snapshot.transactions) {
785 transaction.id = tx.id;
786 transaction.statementDraftId = tx.statementDraftId;
787 transaction.name = tx.name;
788 transaction.bookingDate = tx.bookingDate;
789 transaction.valuta = tx.valuta;
790 transaction.amount = tx.amount;
791 transaction.actorId = tx.actorId;
792 transaction.contractId = tx.contractId;
793 transaction.propertyIds = tx.propertyIds;
794 transaction.status = tx.status;
795 transaction.allocatable = tx.allocatable;
796 transaction.position = tx.position;
797 transaction.metadata = tx.metadata;
798 transaction.proofImageData = tx.proofImageData;
799 draft.transactions.push_back(std::move(transaction));
800 }
801
802 std::sort(draft.transactions.begin(), draft.transactions.end(),
803 [](const auto &lhs, const auto &rhs) {
804 return lhs.position < rhs.position;
805 });
806 if (draft.transactionIds.empty()) {
807 draft.transactionIds.reserve(draft.transactions.size());
808 for (const auto &transaction : draft.transactions) {
809 draft.transactionIds.push_back(transaction.id);
810 }
811 }
812 return draft;
813}
814
820 input.id = draft.id;
821 input.name = draft.name;
822 input.createdAt = draft.createdAt;
823 input.updatedAt = draft.updatedAt;
824 input.transactions.reserve(draft.transactions.size());
825
826 for (const auto &draftTransaction : draft.transactions) {
828 transaction.id = draftTransaction.id;
829 transaction.statementDraftId = input.id;
830 transaction.name = draftTransaction.name;
831 transaction.bookingDate = draftTransaction.bookingDate;
832 transaction.valuta = draftTransaction.valuta;
833 transaction.amount = draftTransaction.amount;
834 transaction.proofImageData = draftTransaction.proofImageData;
835 transaction.status = draftTransaction.status;
836 transaction.actorId = draftTransaction.actorId;
837 transaction.contractId = draftTransaction.contractId;
838
839 bool inferredAllocatable = draft::contractIsFullyAllocatable(
841 draftTransaction.contractId);
842 if (!draftTransaction.contractId.empty()) {
843 for (const auto &contract : state.contracts) {
844 if (contract.id != draftTransaction.contractId) {
845 continue;
846 }
847 const auto mode = normalizedAllocatableMode(contract.allocatableMode);
848 if (mode == "allocatable") {
849 inferredAllocatable = true;
850 } else if (mode == "non-allocatable") {
851 inferredAllocatable = false;
852 }
853 break;
854 }
855 }
856 transaction.allocatable =
857 draftTransaction.allocatableSelected
858 ? draftTransaction.allocatable
859 : (inferredAllocatable || draftTransaction.allocatable);
860 transaction.propertyIds = draftTransaction.propertyIds;
861 transaction.position = static_cast<int>(input.transactions.size());
862 input.transactionIds.push_back(transaction.id);
863 input.transactions.push_back(std::move(transaction));
864 }
865
866 return input;
867}
868
872 const core::ports::workspace::WorkspaceSnapshot &secondary) const {
873 return mergeSnapshots(std::move(primary), secondary);
874}
875
876} // namespace core::application::importing
Declares helpers for parsing imported transaction amount strings.
Declares import-draft matching helpers shared by the import pipeline and draft finalization.
Declares the structured diagnostics reporting port.
Declares the public statement import service interface and factory.
Defines shared job identifiers, states, events, and snapshots.
Domain model for statements.
Declares asynchronous statement import job orchestration.
Declares the application-level asynchronous statement import runner.
Domain model for transactions.
Draft transaction model used by import finalization and core application flow.
Shared transaction transition and normalization helpers.
Immutable workspace read-model snapshot exposed through workspace ports.
Maps workspace port snapshots into internal catalog state.
std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter
Impl(std::shared_ptr< StatementImportJobSystem > jobSystem, std::string runBasePath, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter)
core::ports::workspace::WorkspaceSnapshot mergeWorkspaceState(core::ports::workspace::WorkspaceSnapshot primary, const core::ports::workspace::WorkspaceSnapshot &secondary) const override
Merges two workspace snapshots for import matching context.
core::ports::importing::ImportResult importResult(const core::ports::importing::StatementImportHandle &handle) override
Returns the completed import result for a handle.
void resume(const core::ports::importing::StatementImportHandle &handle) override
Resumes a paused import.
core::ports::importing::draft::StatementDraft restoreStatementDraft(const core::ports::workspace::StatementDraftSnapshot &draft) const override
Restores a runtime statement draft from a workspace draft snapshot.
core::ports::importing::draft::StatementDraftEditResult updateStatementDraft(core::ports::importing::draft::StatementDraft &draft, const core::ports::importing::draft::StatementDraftEdit &edit) const override
Applies one edit command to a statement draft.
void cancel(const core::ports::importing::StatementImportHandle &handle) override
Requests cancellation of an import.
core::ports::importing::StatementImportHandle startStatementImport(const core::ports::importing::ImportRequest &request, core::ports::importing::StatementImportEventCallback callback) override
Starts an asynchronous statement import.
core::ports::workspace::StatementDraftSnapshot buildStatementDraftSnapshot(const core::ports::importing::draft::StatementDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state) const override
Converts a runtime statement draft to a workspace draft snapshot.
StatementImportRunner(std::shared_ptr< IImportStatement > importService, std::string runBasePath, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter)
void unsubscribe(const core::ports::importing::StatementImportHandle &handle) override
Removes a progress subscription for an import.
void pause(const core::ports::importing::StatementImportHandle &handle) override
Pauses a running import.
core::ports::importing::draft::DraftDerivedState buildDraftDerivedState(const core::ports::workspace::WorkspaceSnapshot &state, const core::ports::importing::draft::DraftLinkSelection &selection) const override
Builds derived selection state for a draft link selection.
core::ports::importing::draft::StatementDraft buildStatementDraft(const std::string &sourceFile, const core::ports::workspace::StatementSnapshot &statement, const core::ports::workspace::WorkspaceSnapshot &state, const std::vector< core::ports::importing::draft::TransactionDraft > &transactions, const std::string &draftId) const override
Builds a statement draft from imported statement data.
bool updateTransactionDraft(core::ports::importing::draft::TransactionDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state, const core::ports::importing::draft::TransactionDraftEdit &edit) const override
Applies one edit command to a transaction draft.
constexpr std::string_view kRunNameImport
bool setPropertySelected(TransactionDraft &draft, const std::string &propertyId, bool selected)
bool contractIsFullyAllocatable(const core::domain::catalog::WorkspaceCatalog &state, const std::string &contractId)
Checks whether all transactions assigned to a contract are allocatable.
bool applyDerivedSelections(TransactionDraft &draft, const DraftDerivedState &derived, DraftAutoSelectionMode mode)
int insertTransactionAfter(StatementDraft &draft, int currentIndex)
DraftDerivedState buildDraftDerivedState(const core::domain::catalog::WorkspaceCatalog &state, const DraftLinkSelection &selection)
Derives UI-agnostic matching state for the currently edited draft link.
bool applyContractSelection(TransactionDraft &draft, const DraftChoiceRow &contract)
bool clearContractSelection(TransactionDraft &draft)
DraftTextSignals buildDraftTextSignals(const core::domain::catalog::WorkspaceCatalog &state, const TransactionDraft &transaction)
Derives text signals from a workspace transaction draft.
bool applyActorSelection(TransactionDraft &draft, const std::string &actorId)
bool renameStatementDraft(StatementDraft &draft, const std::string &name)
int removeTransactionAt(StatementDraft &draft, int index)
bool applyPropertySelection(TransactionDraft &draft, const std::string &propertyId)
bool clearActorSelection(TransactionDraft &draft)
bool applyTransactionPatch(TransactionDraft &draft, const TransactionDraftPatch &patch)
std::optional< double > parseAmountString(const std::string &s)
core::domain::catalog::WorkspaceCatalog toWorkspaceCatalog(const core::ports::workspace::WorkspaceSnapshot &snapshot)
std::string trimCopy(std::string value)
Trims leading and trailing whitespace from text.
JobState
Runtime lifecycle state of a job.
Definition JobTypes.h:33
std::function< void(const StatementImportEvent &)> StatementImportEventCallback
std::vector< DraftSuggestionCandidate > candidates
Event emitted when a job changes state or progress.
Definition JobTypes.h:52
std::map< std::string, std::vector< std::uint8_t > > artifacts
std::vector< core::ports::importing::draft::TransactionDraft > transactions
core::ports::workspace::StatementSnapshot statement
std::vector< TransactionDraftSnapshot > transactions
std::vector< AnalysisSnapshot > analyses
std::vector< PropertySnapshot > properties
std::vector< ContractSnapshot > contracts
std::vector< StatementDraftSnapshot > statementDrafts
std::vector< StatementSnapshot > statements
std::vector< ImportLogSnapshot > importLogs
std::vector< ExportLogSnapshot > exportLogs
std::vector< TransactionSnapshot > transactions