42 const QString&
phase() const noexcept {
45 const QString&
error() const noexcept {
58 return hasDraft_ ? &draft_ :
nullptr;
61 return hasDraft_ ? &draft_ :
nullptr;
65 return catalogSnapshot_;
68 return currentTransactionIndex_;
72 return artifactCount_;
79 bool addFiles(
const QStringList& paths);
97 const std::map<std::string, std::vector<uint8_t>>& artifacts,
105 void clearDraftState();
106 void clearTransientImportState();
107 void resetCancellationState();
109 bool isRunning_ =
false;
110 double progress_ = 0.0;
113 QString selectedFile_;
114 QStringList queuedFiles_;
116 bool hasDraft_ =
false;
117 int currentTransactionIndex_ = 0;
119 int artifactCount_ = 0;
120 bool canceled_ =
false;
121 bool paused_ =
false;
122 bool cancelClearsQueue_ =
false;
123 QString currentImportFile_;
126void ImportWorkflowState::clearDraftState() {
128 catalogSnapshot_ = {};
130 currentTransactionIndex_ = 0;
134 if (selectedFile_ == path) {
137 selectedFile_ = path;
143 cleaned.reserve(paths.size());
144 for (
const auto& path : paths) {
145 const auto trimmed = path.trimmed();
146 if (trimmed.isEmpty()) {
149 cleaned.push_back(trimmed);
151 if (cleaned.isEmpty()) {
155 bool changed =
false;
156 const bool hasSelection = !selectedFile_.trimmed().isEmpty();
163 for (
int i = startIndex; i < cleaned.size(); ++i) {
164 if (cleaned[i] == selectedFile_ || queuedFiles_.contains(cleaned[i])) {
167 queuedFiles_.push_back(cleaned[i]);
183 cancelClearsQueue_ =
false;
185 selectedFile_.clear();
186 currentImportFile_.clear();
187 queuedFiles_.clear();
196 if (!isRunning_ && queuedFiles_.isEmpty()) {
197 selectedFile_.clear();
198 currentImportFile_.clear();
200 return !isRunning_ && !queuedFiles_.isEmpty();
204 if (!hasDraft_ || draft_.transactions.empty()) {
205 currentTransactionIndex_ = 0;
208 const int maxIndex =
static_cast<int>(draft_.transactions.size()) - 1;
209 currentTransactionIndex_ = std::clamp(index, 0, maxIndex);
215 clearTransientImportState();
216 resetCancellationState();
217 selectedFile_.clear();
218 currentImportFile_.clear();
222 return currentImportFile_.isEmpty() ? selectedFile_ : currentImportFile_;
226 const QString trimmed = selectedFile_.trimmed();
227 if (trimmed.isEmpty()) {
230 selectedFile_.clear();
235 if (isRunning_ || queuedFiles_.isEmpty()) {
238 nextFile = queuedFiles_.takeFirst();
242void ImportWorkflowState::clearTransientImportState() {
247void ImportWorkflowState::resetCancellationState() {
249 cancelClearsQueue_ =
false;
254 selectedFile_ = path;
255 currentImportFile_ = path;
262 resetCancellationState();
266 error_ = errorMessage;
267 queuedFiles_.clear();
268 currentImportFile_.clear();
270 clearTransientImportState();
271 resetCancellationState();
280 cancelClearsQueue_ = clearQueue;
282 queuedFiles_.clear();
288 if (!isRunning_ || canceled_) {
291 if (paused_ == paused) {
302 if (cancelClearsQueue_) {
303 queuedFiles_.clear();
306 clearTransientImportState();
307 resetCancellationState();
308 selectedFile_.clear();
309 currentImportFile_.clear();
313 error_ = errorMessage;
314 queuedFiles_.clear();
316 clearTransientImportState();
317 resetCancellationState();
318 currentImportFile_.clear();
324 const std::map<std::string, std::vector<uint8_t>>& artifacts,
330 artifactCount_ =
static_cast<int>(artifacts.size());
334 catalogSnapshot_ = state;
351 if (!draftId.isEmpty()) {
354 catalogSnapshot_ = state;
361 const QString&
phase) {
362 if (!isRunning_ || canceled_ || paused_) {
367 if (
phase.isEmpty()) {
380 return QUuid::createUuid().toString(QUuid::WithoutBraces);
383double clampedProgress(
double progress) {
396 std::shared_ptr<ui::adapters::ImportAdapter> importAdapter,
397 std::shared_ptr<core::ports::diagnostics::IErrorReporter> errorReporter,
401 state_(std::make_unique<
importing::ImportWorkflowState>()),
402 importAdapter_(std::move(importAdapter)),
403 stateSnapshotProvider_(std::move(stateSnapshotProvider)),
404 commands_(commands), selectors_(selectors),
405 errorReporter_(std::move(errorReporter)) {
406 if (!errorReporter_) {
407 throw std::invalid_argument(
"ImportWorkflow requires an error reporter");
415 commands_ = commands;
416 selectors_ = selectors;
421 return state_->isRunning();
425 return state_->isPaused();
429 return state_->progress();
433 return state_->phase();
437 return state_->error();
441 return state_->selectedFile();
445 return static_cast<int>(state_->queuedFiles().size());
449 return state_->queuedFiles();
453 return state_->hasDraft();
457 return state_->currentTransactionIndex();
460bool ImportWorkflow::hasActiveImportHandle() const noexcept {
461 return hasActiveImportHandle_ && !activeImportHandle_.
importId.empty();
464void ImportWorkflow::clearActiveImportSubscription() {
465 if (importAdapter_ && hasActiveImportHandle()) {
466 importAdapter_->unsubscribe(activeImportHandle_);
468 activeImportHandle_ = {};
469 hasActiveImportHandle_ =
false;
472void ImportWorkflow::saveImportLog(
const QString& logId,
const QString& status,
473 const QString& message,
bool draftAttached,
474 const QString& draftId,
475 const QString& statementId,
476 const QString& importFile) {
480 const QString sourceFile =
481 importFile.isEmpty() ? state_->currentImportFile() : importFile;
482 commands_->upsertImportLog(logId, status, message, draftAttached, draftId,
483 statementId, sourceFile);
486void ImportWorkflow::clearStoredDraft(
const QString& draftId) {
491 commands_->clearStatementDraft(draftId);
494 std::current_exception());
499 if (!commands_ || !state_->hasDraft()) {
510 if (!commands_ || !state_->hasDraft()) {
515 clearStoredDraft(draftId);
518 state_->selectedFile());
523 if (!commands_ || !state_->hasDraft()) {
529 "Statement draft finalize submitted",
530 {{
"draftId", draftId.toStdString()}});
534 statementId = finalizeCurrentStatementDraft();
537 std::current_exception());
540 if (statementId.isEmpty()) {
546 clearStoredDraft(draftId);
554 const QString& draftId) {
556 clearStoredDraft(draftId);
559 if (commands_ && !logId.isEmpty()) {
560 commands_->deleteImportLog(logId);
565 if (state_->addFiles(paths)) {
571 if (state_->setSelectedFile(path)) {
577 if (state_->resetStatus()) {
583 rememberCurrentDraftTransactionIndex();
584 const bool shouldStartNext = state_->clearDraft();
585 activeDraftId_.clear();
587 if (shouldStartNext) {
588 startNextQueuedImport();
592void ImportWorkflow::rejectImportStart(
const QString& errorMessage,
593 const char* traceMessage) {
594 state_->rejectStart(errorMessage);
604void ImportWorkflow::startNextQueuedImport() {
606 if (!state_->takeNextQueuedFile(next)) {
610 startImportForFile(next);
614 if (state_->isRunning() || state_->hasDraft()) {
618 const auto selected = state_->takeSelectedFileForStart();
619 if (!selected.isEmpty()) {
621 startImportForFile(selected);
625 startNextQueuedImport();
628void ImportWorkflow::startImportForFile(
const QString& path) {
629 if (state_->isRunning()) {
632 if (!importAdapter_) {
634 "Import start rejected: workflow unavailable");
637 if (path.trimmed().isEmpty()) {
639 "Import start rejected: no file selected");
643 state_->beginImport(path);
644 activeImportLogId_ = newLogId();
645 activeImportTerminalHandled_ =
false;
646 hasPendingTerminalEvent_ =
false;
647 activeImportHandle_ = {};
648 hasActiveImportHandle_ =
false;
650 pendingTerminalMessage_.clear();
659 std::to_string(state_->queuedFiles().size())}});
661 errorReporter_.get(),
665 {{observability::context::kFile, strings::toStdString(path)},
666 {observability::context::kQueuedCount,
667 std::to_string(state_->queuedFiles().size())}});
669 core::ports::importing::ImportRequest request;
671 activeImportHandle_ = importAdapter_->startStatementImport(
673 [
this](
const core::ports::importing::StatementImportEvent& event) {
674 handleImportEvent(event);
676 hasActiveImportHandle_ = !activeImportHandle_.importId.empty();
678 if (!hasActiveImportHandle_) {
680 "Import failed: unable to start job");
684void ImportWorkflow::updateProgress(
double progress,
const QString& phase) {
685 state_->updateProgress(progress, phase);
689void ImportWorkflow::reportException(
const char* origin,
690 std::exception_ptr exception)
const {
691 if (!errorReporter_) {
698void ImportWorkflow::requestImportCancellation(
bool clearQueue,
700 const char* traceMessage) {
701 if (!state_->isRunning()) {
705 const auto queuedBeforeCancel =
706 clearQueue ? state_->queuedFiles() : QStringList{};
707 state_->beginCancel(clearQueue);
709 if (clearQueue && commands_) {
710 for (
const auto& queuedPath : queuedBeforeCancel) {
711 commands_->upsertImportLog(newLogId(), text::importing::statusCanceled(),
712 text::importing::messageCanceledBeforeStart(),
713 false, {}, {}, queuedPath);
717 if (importAdapter_ && hasActiveImportHandle()) {
718 importAdapter_->cancel(activeImportHandle_);
720 observability::reportFlow(
721 errorReporter_.get(),
723 observability::codes::FlowImportCanceled, origin, traceMessage,
724 {{observability::context::kFile,
725 strings::toStdString(state_->currentImportFile())},
726 {observability::context::kQueuedCount,
727 std::to_string(state_->queuedFiles().size())}});
732 requestImportCancellation(
false,
734 "Import cancellation requested");
738 requestImportCancellation(
740 "Cancel-all requested for import queue");
743void ImportWorkflow::setImportPaused(
bool paused) {
744 if (state_->isPaused() == paused) {
747 if (!state_->setPaused(paused)) {
750 if (importAdapter_ && hasActiveImportHandle()) {
751 if (state_->isPaused()) {
752 importAdapter_->pause(activeImportHandle_);
754 importAdapter_->resume(activeImportHandle_);
757 if (!activeImportLogId_.isEmpty()) {
758 saveImportLog(activeImportLogId_,
759 state_->isPaused() ? text::importing::statusPaused()
760 : text::importing::statusRunning(),
761 state_->isPaused() ? text::importing::messageImportPaused()
762 : text::importing::messageImportResumed());
766 if (!state_->isPaused() && hasPendingTerminalEvent_) {
767 const auto terminalState = pendingTerminalState_;
768 const auto message = pendingTerminalMessage_;
769 hasPendingTerminalEvent_ =
false;
770 pendingTerminalState_ =
772 pendingTerminalMessage_.clear();
773 onJobTerminal(terminalState, message);
778 setImportPaused(
true);
782 setImportPaused(
false);
785void ImportWorkflow::handleImportEvent(
787 const double progress = clampedProgress(event.
progress);
788 const QString phase = QString::fromStdString(event.
message);
789 const auto eventState =
event.state;
790 const QString message = QString::fromStdString(event.
message);
792 QMetaObject::invokeMethod(
794 [
this, progress, phase, eventState, message]() {
801 onJobTerminal(eventState, message);
804 updateProgress(progress, phase);
806 Qt::QueuedConnection);
809void ImportWorkflow::handleImportCanceled() {
810 state_->recordCanceled();
811 if (!activeImportTerminalHandled_) {
812 saveImportLog(activeImportLogId_, text::importing::statusCanceled(),
813 text::importing::phaseCanceled());
814 activeImportTerminalHandled_ =
true;
816 observability::reportFlow(
817 errorReporter_.get(),
819 observability::codes::FlowImportCanceled,
820 observability::origins::workflow::import::kTerminal,
"Import canceled",
821 {{observability::context::kStatus,
822 strings::toStdString(text::importing::statusCanceled())}});
824 emit importCanceled();
827void ImportWorkflow::handleImportFailed(
const QString& errorMessage,
828 const char* traceMessage) {
829 const QString traceDetail =
830 traceMessage ? QString::fromUtf8(traceMessage) : QString();
831 const QString visibleError =
832 errorMessage == text::workflowErrors::importFailed() &&
833 traceDetail.startsWith(QStringLiteral(
"Import failed:"))
836 state_->recordFailed(visibleError);
837 if (!activeImportTerminalHandled_) {
838 saveImportLog(activeImportLogId_, text::importing::statusFailed(),
840 activeImportTerminalHandled_ =
true;
842 observability::reportFlow(errorReporter_.get(),
844 observability::codes::FlowImportFailed,
845 observability::origins::workflow::import::kTerminal,
847 {{observability::context::kError,
848 strings::toStdString(state_->error())}});
853bool ImportWorkflow::populateDraftFromResult() {
854 if (!importAdapter_ || !hasActiveImportHandle()) {
855 handleImportFailed(text::workflowErrors::importFailed(),
856 "Import failed: import runner unavailable");
860 const auto imported = importAdapter_->importResult(activeImportHandle_);
861 if (!imported.hasStatement) {
862 handleImportFailed(text::workflowErrors::importFailed(),
863 "Import failed: missing statement result");
867 const auto snapshot = stateSnapshot();
868 const QString draftId = activeImportLogId_;
869 const bool hadVisibleDraft = state_->hasDraft();
871 const auto preparedDraft = importAdapter_->buildStatementDraft(
872 strings::toStdString(state_->currentImportFile()), imported.statement,
873 snapshot, imported.transactions, strings::toStdString(draftId));
874 if (preparedDraft.id.empty() && preparedDraft.transactions.empty()) {
875 handleImportFailed(text::workflowErrors::importFailed(),
876 "Import failed: unable to create draft state");
880 const auto draftSnapshot =
881 importAdapter_->buildStatementDraftSnapshot(preparedDraft, snapshot);
882 if (!commands_ || draftSnapshot.id.empty()) {
883 handleImportFailed(text::workflowErrors::importFailed(),
884 "Import failed: unable to save draft state");
887 commands_->saveStatementDraft(draftSnapshot);
889 if (hadVisibleDraft) {
890 state_->recordFinished();
892 if (!state_->populateDraft(preparedDraft, snapshot, imported.artifacts,
894 handleImportFailed(text::workflowErrors::importFailed(),
895 "Import failed: unable to create statement draft");
900 saveImportLog(activeImportLogId_, text::importing::statusDraft(),
901 text::importing::messageDraftReady(),
true, draftId);
902 if (!hadVisibleDraft) {
903 activeDraftId_ = draftId;
905 activeImportTerminalHandled_ =
true;
907 observability::reportFlow(
908 errorReporter_.get(),
910 observability::codes::FlowImportFinished,
911 observability::origins::workflow::import::kTerminal,
"Import finished",
912 {{observability::context::kStatus,
913 strings::toStdString(text::importing::statusSuccess())},
914 {observability::context::kArtifactCount,
915 std::to_string(state_->artifactCount())}});
917 emit importFinished();
921void ImportWorkflow::onJobTerminal(
923 const QString& message) {
924 if ((!state_->isRunning() && !state_->cancelRequested()) ||
925 activeImportTerminalHandled_) {
929 if (state_->isPaused() &&
931 hasPendingTerminalEvent_ =
true;
932 pendingTerminalState_ = state;
933 pendingTerminalMessage_ = message;
938 state_->cancelRequested()) {
939 clearActiveImportSubscription();
940 handleImportCanceled();
945 clearActiveImportSubscription();
946 handleImportFailed(message.isEmpty() ? text::workflowErrors::importFailed()
952 const bool populated = populateDraftFromResult();
953 clearActiveImportSubscription();
958 startNextQueuedImport();
961QString ImportWorkflow::resolveDraftContextId()
const {
962 if (!activeDraftId_.isEmpty()) {
963 return activeDraftId_;
965 return currentDraftId();
968void ImportWorkflow::rememberCurrentDraftTransactionIndex() {
969 const QString draftId = currentDraftId();
970 if (draftId.isEmpty()) {
973 draftTransactionIndexByDraftId_.insert(draftId,
974 state_->currentTransactionIndex());
977int ImportWorkflow::rememberedDraftTransactionIndex(
978 const QString& draftId)
const {
979 return draftTransactionIndexByDraftId_.value(draftId, 0);
983 if (!stateSnapshotProvider_ || !selectors_) {
986 rememberCurrentDraftTransactionIndex();
989 QString requestedDraftId = !draftId.isEmpty() ? draftId : activeDraftId_;
990 if (requestedDraftId.isEmpty()) {
991 for (
const auto&
id : selectors_->attachedImportDraftIds()) {
992 requestedDraftId = id;
996 if (!requestedDraftId.isEmpty()) {
1000 activeDraftId_ = requestedDraftId;
1003 const bool restored = restoreDraftFromState(stateSnapshot());
1010bool ImportWorkflow::restoreDraftFromState(
1017 const QString requestedDraftId = resolveDraftContextId();
1018 if (!requestedDraftId.isEmpty()) {
1020 if (QString::fromStdString(draft.id) == requestedDraftId) {
1021 storedDraft = &draft;
1033 const QString restoredDraftId = QString::fromStdString(storedDraft->
id);
1034 if (!restoredDraftId.isEmpty()) {
1035 activeDraftId_ = restoredDraftId;
1038 auto draft = importAdapter_->restoreStatementDraft(*storedDraft);
1039 return state_->restoreDraft(draft, snapshot, restoredDraftId,
1040 rememberedDraftTransactionIndex(restoredDraftId));
1044ImportWorkflow::currentTransactionDraft() {
1045 auto* draft = state_->draft();
1049 const int index = state_->currentTransactionIndex();
1051 static_cast<std::size_t
>(index) >= draft->transactions.size()) {
1054 return &draft->transactions[
static_cast<std::size_t
>(index)];
1058ImportWorkflow::currentTransactionDraft()
const {
1059 return const_cast<ImportWorkflow*
>(
this)->currentTransactionDraft();
1063 const auto* draft = state_->draft();
1064 return draft ?
static_cast<int>(draft->transactions.size()) : 0;
1068ImportWorkflow::stateSnapshot()
const {
1069 return stateSnapshotProvider_ ? stateSnapshotProvider_()
1070 :
core::
ports::workspace::WorkspaceSnapshot{};
1074 const auto* draft = state_->draft();
1075 return draft ? QString::fromStdString(draft->id) : QString();
1079 const auto* draft = state_->draft();
1080 return draft ? QString::fromStdString(draft->name) : QString();
1084ImportWorkflow::statementDraft() noexcept {
1085 return state_->draft();
1089ImportWorkflow::statementDraft() const noexcept {
1090 return state_->draft();
1094 if (!state_->hasDraft()) {
1097 const int previous = state_->currentTransactionIndex();
1098 state_->setCurrentTransactionIndex(index);
1099 if (state_->currentTransactionIndex() != previous) {
1104void ImportWorkflow::notifyDraftChanged() {
1105 emit stateChanged();
1109 auto* draft = statementDraft();
1110 if (!draft || !importAdapter_) {
1116 const auto result = importAdapter_->updateStatementDraft(*draft, edit);
1117 if (result.changed) {
1118 notifyDraftChanged();
1120 return result.changed;
1124 auto* draft = statementDraft();
1125 if (!draft || !importAdapter_) {
1129 edit.
kind = core::ports::importing::draft::StatementDraftEditKind::
1130 InsertTransactionAfter;
1132 const int newIndex = importAdapter_->updateStatementDraft(*draft, edit)
1133 .selectedTransactionIndex;
1134 if (newIndex >= 0) {
1141 auto* draft = statementDraft();
1142 if (!draft || !importAdapter_) {
1146 edit.
kind = core::ports::importing::draft::StatementDraftEditKind::
1147 RemoveTransactionAt;
1149 const int newIndex = importAdapter_->updateStatementDraft(*draft, edit)
1150 .selectedTransactionIndex;
1151 if (newIndex >= 0) {
1157bool ImportWorkflow::applyCurrentTransactionPatch(
1159 auto* transaction = currentTransactionDraft();
1160 if (!transaction || !importAdapter_) {
1166 if (!importAdapter_->updateTransactionDraft(*transaction, {}, edit)) {
1169 notifyDraftChanged();
1177 return applyCurrentTransactionPatch(patch);
1181 const QString& bookingDate) {
1185 return applyCurrentTransactionPatch(patch);
1192 return applyCurrentTransactionPatch(patch);
1199 return applyCurrentTransactionPatch(patch);
1207 return applyCurrentTransactionPatch(patch);
1211 const QString& amountText) {
1212 auto* transaction = currentTransactionDraft();
1213 if (!transaction || !importAdapter_) {
1220 if (!importAdapter_->updateTransactionDraft(*transaction, {}, edit)) {
1223 notifyDraftChanged();
1228 auto* transaction = currentTransactionDraft();
1229 if (!transaction || !importAdapter_) {
1236 const bool changed =
1237 importAdapter_->updateTransactionDraft(*transaction, {}, edit);
1239 notifyDraftChanged();
1245 auto* transaction = currentTransactionDraft();
1246 if (!transaction || !importAdapter_) {
1252 const bool changed =
1253 importAdapter_->updateTransactionDraft(*transaction, {}, edit);
1255 notifyDraftChanged();
1261 const QString& propertyId,
bool selected) {
1262 auto* transaction = currentTransactionDraft();
1263 if (!transaction || !importAdapter_) {
1267 edit.
kind = core::ports::importing::draft::TransactionDraftEditKind::
1268 SetPropertySelected;
1271 const bool changed =
1272 importAdapter_->updateTransactionDraft(*transaction, {}, edit);
1274 notifyDraftChanged();
1280 const QString& contractId) {
1281 auto* transaction = currentTransactionDraft();
1282 if (!transaction || !importAdapter_) {
1286 edit.
kind = core::ports::importing::draft::TransactionDraftEditKind::
1287 SelectContractFromCatalog;
1289 const bool changed = importAdapter_->updateTransactionDraft(
1290 *transaction, catalogSnapshotForDraft(), edit);
1292 notifyDraftChanged();
1298 auto* transaction = currentTransactionDraft();
1299 if (!transaction || !importAdapter_) {
1305 const bool changed =
1306 importAdapter_->updateTransactionDraft(*transaction, {}, edit);
1308 notifyDraftChanged();
1314 const QString& actorName) {
1315 if (!commands_ || !selectors_) {
1318 const QString trimmedName = actorName.trimmed();
1319 if (trimmedName.isEmpty()) {
1322 QString actorId = selectors_->actorIdByName(trimmedName);
1323 if (actorId.isEmpty()) {
1324 actorId = commands_->saveActor({}, trimmedName);
1330 const QString& propertyName) {
1331 if (!commands_ || !selectors_) {
1334 const QString trimmedName = propertyName.trimmed();
1335 if (trimmedName.isEmpty()) {
1338 QString propertyId = selectors_->propertyIdByName(trimmedName);
1339 if (propertyId.isEmpty()) {
1340 propertyId = commands_->saveProperty({}, trimmedName);
1342 return !propertyId.isEmpty() &&
1347 const QString& contractName,
const QString& contractType,
1348 const QString& allocatableMode) {
1349 auto* transaction = currentTransactionDraft();
1350 if (!transaction || !commands_ || !selectors_) {
1353 const QString trimmedType = contractType.trimmed();
1354 if (trimmedType.isEmpty()) {
1357 const QString effectiveName = contractName.trimmed().isEmpty()
1358 ? selectors_->nextContractName()
1359 : contractName.trimmed();
1360 QStringList actorIds;
1361 const QString actorId =
1362 QString::fromStdString(transaction->actorId).trimmed();
1363 if (!actorId.isEmpty()) {
1364 actorIds.push_back(actorId);
1366 QStringList propertyIds;
1367 propertyIds.reserve(
static_cast<int>(transaction->propertyIds.size()));
1368 for (
const auto& propertyId : transaction->propertyIds) {
1369 propertyIds.push_back(QString::fromStdString(propertyId));
1372 QString contractId = selectors_->contractIdBySignature(
1373 effectiveName, trimmedType, actorIds, propertyIds);
1374 if (contractId.isEmpty()) {
1375 QString mode = allocatableMode.trimmed().toLower();
1376 if (mode.isEmpty()) {
1377 mode = QStringLiteral(
"mixed");
1379 contractId = commands_->saveContract({}, effectiveName, trimmedType,
1380 actorIds, propertyIds, {}, mode);
1386 const auto* transaction = currentTransactionDraft();
1387 if (!transaction || !importAdapter_) {
1391 return importAdapter_->transactionDraftView(*transaction,
1392 catalogSnapshotForDraft());
1395 std::current_exception());
1401ImportWorkflow::currentStatementDraftSnapshot()
const {
1402 const auto* draft = statementDraft();
1403 if (!draft || !importAdapter_) {
1406 return importAdapter_->buildStatementDraftSnapshot(*draft,
1407 catalogSnapshotForDraft());
1410QString ImportWorkflow::finalizeCurrentStatementDraft() {
1411 if (!commands_ || !importAdapter_ || !state_->hasDraft()) {
1414 flushActiveDraftToWorkspace();
1415 const auto snapshot = currentStatementDraftSnapshot();
1419 return commands_->finalizeStatementDraft(snapshot);
1423 if (!commands_ || !importAdapter_ || !state_->hasDraft()) {
1427 const auto* draft = state_->draft();
1428 if (!draft || draft->id.empty()) {
1432 const auto snapshot = importAdapter_->buildStatementDraftSnapshot(
1433 *draft, catalogSnapshotForDraft());
1434 if (snapshot.id.
empty()) {
1438 commands_->saveStatementDraft(snapshot);
1442ImportWorkflow::catalogSnapshotForDraft()
const {
1443 const auto liveSnapshot = stateSnapshot();
1444 if (!state_->hasDraft() || !importAdapter_) {
1445 return liveSnapshot;
1448 return importAdapter_->mergeWorkspaceState(state_->catalogSnapshot(),
Defines UI-specific runtime constants that are not shared with core.
Declares the import adapter used by UI import workflows.
Coordinates asynchronous statement imports and in-memory draft sessions.
Declarations for the UI Origins component.
Declares lightweight conversions between common Qt string types and standard strings.
Provides translated C++ UI strings for shell, workflow, adapter and view model code.
Declarations for the UI Trace component.
Declares read-only workspace selectors for QML payloads.
bool clearCurrentTransactionContract()
Clears the contract selection for the current transaction draft.
void startStatementImport()
Starts importing the selected or next queued statement file.
void discardActiveDraft()
Discards the active draft and removes its workspace draft state.
bool createActorForCurrentTransaction(const QString &actorName)
Creates or reuses an actor and selects it for the current draft.
bool setCurrentTransactionPropertySelected(const QString &propertyId, bool selected)
Selects or deselects a property for the current transaction draft.
ImportWorkflow(std::shared_ptr< ui::adapters::ImportAdapter > importAdapter, std::shared_ptr< core::ports::diagnostics::IErrorReporter > errorReporter, StateSnapshotProvider stateSnapshotProvider={}, WorkspaceCommands *commands=nullptr, WorkspaceSelectors *selectors=nullptr, QObject *parent=nullptr)
Creates an import workflow.
void cancelImport()
Cancels the current running import.
void setSelectedFile(const QString &path)
Sets the selected import file.
QString currentDraftId() const
Returns the active draft id.
void setWorkspaceRoles(WorkspaceCommands *commands, WorkspaceSelectors *selectors)
Rebinds workspace roles used by the workflow.
bool commitCurrentTransactionName(const QString &name)
Updates the current transaction draft name.
void addFiles(const QStringList &paths)
Adds files to the import selection or queue.
bool clearCurrentTransactionActor()
Clears the actor selection for the current transaction draft.
bool openStoredDraft(const QString &draftId={})
Opens a statement draft stored in workspace state.
void finalizeActiveDraft()
Finalizes the active draft into workspace statement data.
void resumeImport()
Resumes the current paused import.
bool commitCurrentTransactionValuta(const QString &valuta)
Updates the current transaction draft valuta date.
bool createOrSelectContractForCurrentTransaction(const QString &contractName, const QString &contractType, const QString &allocatableMode)
Creates or reuses a contract and selects it for the current draft.
bool selectCurrentTransactionContract(const QString &contractId)
Selects a contract for the current transaction draft.
bool setCurrentTransactionStatus(int status)
Updates the current transaction draft status.
void setCurrentTransactionIndex(int index)
Selects the current transaction draft by index.
void importFailed(const QString &error)
Emitted when an import failed.
void pauseImport()
Pauses the current running import.
bool isRunning() const noexcept
Reports whether an import is running.
QString selectedFile() const
Returns the selected import file.
TransactionDraftView currentTransactionView() const
Builds the QML-facing view of the current transaction draft.
void clearDraft()
Clears the active draft and may continue queued imports.
double progress() const noexcept
Returns import progress.
bool selectCurrentTransactionActor(const QString &actorId)
Selects an actor for the current transaction draft.
bool hasDraft() const noexcept
Reports whether an active statement draft exists.
int queuedCount() const noexcept
Returns queued import file count.
bool applyCurrentTransactionAmountText(const QString &amountText)
Applies user-entered amount text to the current transaction draft.
void stateChanged()
Emitted when import workflow state changed.
void removeAttachedImportLog(const QString &logId, bool draftAttached, const QString &draftId)
Removes an import log and optionally its attached draft.
void cancelQueuedImports()
Cancels the running import and clears queued imports.
void flushActiveDraftToWorkspace()
Saves the active in-memory draft into workspace draft state.
bool createPropertyForCurrentTransaction(const QString &propertyName)
Creates or reuses a property and selects it for the current draft.
bool isPaused() const noexcept
Reports whether the running import is paused.
bool commitCurrentTransactionBookingDate(const QString &bookingDate)
Updates the current transaction draft booking date.
~ImportWorkflow() override
Destroys the workflow and its internal state.
QString currentStatementName() const
Returns the editable name of the active statement draft.
int insertTransactionAfterCurrent()
Inserts a transaction draft after the current one.
void resetStatus()
Resets import status fields when no import is running.
void pauseActiveDraft()
Saves the active draft and returns to the import overview.
int removeCurrentTransaction()
Removes the current transaction draft.
int currentTransactionIndex() const noexcept
Returns the selected transaction draft index.
QString phase() const
Returns the current import phase text.
QStringList queuedFiles() const
Returns queued import files.
QString error() const
Returns the current import error text.
std::function< core::ports::workspace::WorkspaceSnapshot()> StateSnapshotProvider
bool renameCurrentStatementDraft(const QString &name)
Renames the active statement draft.
int transactionCount() const noexcept
Returns transaction count in the active draft.
bool setCurrentTransactionAllocatable(bool allocatable)
Updates the current transaction draft allocatable flag.
Maps UI intent to core workspace writer commands.
Projects workspace store data into QML-facing read models.
bool setSelectedFile(const QString &path)
const core::ports::workspace::WorkspaceSnapshot & catalogSnapshot() const noexcept
void beginCancel(bool clearQueue)
core::ports::importing::draft::StatementDraft * draft() noexcept
bool setPaused(bool paused)
bool populateDraft(const core::ports::importing::draft::StatementDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state, const std::map< std::string, std::vector< uint8_t > > &artifacts, int currentTransactionIndex)
bool isPaused() const noexcept
const QString & selectedFile() const noexcept
const core::ports::importing::draft::StatementDraft * draft() const noexcept
bool addFiles(const QStringList &paths)
bool restoreDraft(const core::ports::importing::draft::StatementDraft &draft, const core::ports::workspace::WorkspaceSnapshot &state, const QString &draftId, int currentTransactionIndex)
void updateProgress(double progress, const QString &phase)
QString currentImportFile() const
double progress() const noexcept
bool isRunning() const noexcept
void beginImport(const QString &path)
bool takeNextQueuedFile(QString &nextFile)
const QString & error() const noexcept
void setCurrentTransactionIndex(int index)
int currentTransactionIndex() const noexcept
ImportWorkflowState()=default
bool cancelRequested() const noexcept
int artifactCount() const noexcept
const QString & phase() const noexcept
QString takeSelectedFileForStart()
void rejectStart(const QString &errorMessage)
const QStringList & queuedFiles() const noexcept
bool hasDraft() const noexcept
void recordFailed(const QString &errorMessage)
Defines shared structured error codes.
constexpr double kMinimum
Minimum visible progress value shown by the import UI.
constexpr double kMaximum
Terminal progress value shown after a successful import.
constexpr double kInitial
Initial progress value shown once an import has started.
constexpr const char * FlowImportRejected
constexpr const char * FlowImportStarted
constexpr auto kQueuedCount
constexpr auto kCancelQueued
void reportFlow(core::ports::diagnostics::IErrorReporter *reporter, core::errors::ErrorSeverity severity, const char *code, const char *origin, std::string message, core::errors::ErrorContext context={})
Reports a UI flow error event.
void traceWorkflow(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a workflow trace event.
std::string toStdString(const QString &value)
Converts a QString to a UTF-8 encoded std::string.
std::string toEncodedPath(const QString &path)
Converts a filesystem path to the platform-encoded byte representation.
QString phaseRunning()
Phase text after a paused import resumes.
QString statusDraft()
Import log status for draft rows.
QString statusDraftDiscarded()
Import log status for discarded drafts.
QString messageFinalizeFailed()
Import log detail shown when finalizing a draft fails.
QString phaseStopping()
Phase text while a cancellation request is being processed.
QString messageDraftDiscarded()
Import log detail shown after a draft is discarded.
QString messageFinalized()
Import log detail shown after a draft becomes a statement.
QString statusFinalized()
Import log status for finalized drafts.
QString phaseStarting()
Phase text when an import has just been submitted.
QString statusRunning()
Import log status for active runs.
QString phaseFinished()
Phase text after an import finished.
QString phasePaused()
Phase text while an import is paused for draft review.
QString phaseFailed()
Phase text after an import failed.
QString statusFinalizeFailed()
Import log status for failed draft finalization.
QString phaseCanceled()
Phase text after an import was canceled.
QString messageDraftPaused()
Import log detail shown while a draft waits for review.
QString noFileSelected()
Error text when an operation requires a selected file.
QString importWorkflowUnavailable()
Error text when import workflow wiring is unavailable.
QString importFailed()
Generic import failure text.
StatementDraftEditKind kind
TransactionDraftEditKind kind
TransactionDraftPatch patch
bool empty() const noexcept
Checks whether all snapshot collections are empty and no file is open.
std::vector< StatementDraftSnapshot > statementDrafts
std::vector< TransactionSnapshot > transactions
Qt-friendly projection of the currently edited import transaction.
Declares UI-side workspace command submission.