28constexpr int kYearMin = 1900;
29constexpr int kYearMax = 2500;
30constexpr auto kPlot =
"plot";
31constexpr auto kTab =
"tab";
32constexpr auto kXlsx =
"xlsx";
33constexpr auto kCsv =
"csv";
34constexpr auto kPng =
"png";
35constexpr auto kJpg =
"jpg";
36constexpr auto kDeduplicated =
"deduplicated";
37constexpr auto kSimilar =
"similar";
38constexpr auto kDivergent =
"divergent";
39constexpr auto kWorkspaceOnly =
"workspaceOnly";
40constexpr auto kMissingLive =
"missingLive";
42QString qstr(
const char* value) {
43 return QString::fromLatin1(value);
46QString rowIdAt(
const QVariantList& rows,
int index,
47 const QString& idKey = QStringLiteral(
"id")) {
48 if (index < 0 || index >= rows.size()) {
51 return rows.at(index).toMap().value(idKey).toString();
54int indexOfId(
const QVariantList& rows,
const QString&
id,
55 const QString& idKey = QStringLiteral(
"id")) {
59 for (
int i = 0; i < rows.size(); ++i) {
60 if (rows.at(i).toMap().value(idKey).toString() ==
id) {
67QVariantMap rowById(
const QVariantList& rows,
const QString&
id,
68 const QString& idKey = QStringLiteral(
"id")) {
69 const int index = indexOfId(rows,
id.trimmed(), idKey);
70 return index >= 0 ? rows.at(index).toMap() : QVariantMap{};
73QString navigatedSelectionId(
const QVariantList& rows,
const QString& currentId,
74 int delta,
int defaultIndex = 0,
75 const QString& idKey = QStringLiteral(
"id")) {
76 const int currentIndex = indexOfId(rows, currentId, idKey);
77 if (currentIndex < 0) {
82 return rowIdAt(rows, 0, idKey);
85 return rowIdAt(rows, rows.size() - 1, idKey);
87 return rowIdAt(rows, defaultIndex, idKey);
90 return currentIndex >= rows.size() - 1
92 : rowIdAt(rows, currentIndex + 1, idKey);
95 return currentIndex <= 0 ? QString()
96 : rowIdAt(rows, currentIndex - 1, idKey);
98 return rowIdAt(rows, currentIndex, idKey);
101QString deleteNextSelectionId(
const QVariantList& rows,
102 const QString& removedId,
int defaultIndex = 0,
103 const QString& idKey = QStringLiteral(
"id")) {
104 if (rows.isEmpty()) {
107 const int removedIndex = indexOfId(rows, removedId, idKey);
108 const int nextIndex = removedIndex >= 0 ? removedIndex + 1 : defaultIndex;
109 const int wrapped = nextIndex % rows.size();
110 return rowIdAt(rows, wrapped < 0 ? wrapped + rows.size() : wrapped, idKey);
113double numberFromVariant(
const QVariant& value) {
115 const double out = value.toDouble(&ok);
116 return ok && std::isfinite(out) ? out : 0.0;
119QString amountText(
double value) {
120 return QString::number(value,
'f', 2);
123QString amountText(
const QVariant& value) {
124 return amountText(numberFromVariant(value));
127QString nonEmptyString(
const QVariantMap& map,
const QString& key,
128 const QString& defaultValue = {}) {
129 const QString value = map.value(key).toString();
130 return value.isEmpty() ? defaultValue : value;
135 sectionExpanded_.insert(qstr(kDeduplicated),
true);
136 sectionExpanded_.insert(qstr(kSimilar),
true);
137 sectionExpanded_.insert(qstr(kDivergent),
true);
138 sectionExpanded_.insert(qstr(kWorkspaceOnly),
true);
139 sectionExpanded_.insert(qstr(kMissingLive),
true);
140 verificationIssues_ = emptyIssues();
141 statusMetrics_ = emptyStatusMetrics();
142 year_ = defaultYear();
149 if (store_ == store && commands_ == commands && selection_ == selection &&
150 selectors_ == selectors) {
153 bindWorkspaceRoles(store, commands, selection, selectors);
159 if (annualWorkflow_ == value) {
162 annualWorkflow_ = value;
163 lastPreviewRevision_ = -1;
173 return selectors_ ? selectors_->annualRows() : QVariantList();
177 return selection_ ? selection_->selectedAnnualId() : QString();
181 if (name_ == value) {
189 const int next = std::clamp(value, kYearMin, kYearMax);
194 rebuildAnnualResultState();
206 QStringList current = analysisIds_;
207 QStringList saved = savedAnalysisIds_;
210 return savedName_ != name_ || savedYear_ != year_ || saved != current ||
211 analysisMetadataDirty_;
215 loadSelectedAnnual();
216 rebuildAnnualResultState();
221 if (
isEdit() && selection_) {
222 selection_->selectAnnual({});
225 rebuildAnnualResultState();
230 contentIndex_ = contentIndex_ == 0 ? 1 : 0;
238int AnnualViewModel::workspaceRevision()
const {
242void AnnualViewModel::bindWorkspaceRoles(WorkspaceStore* store,
243 WorkspaceCommands* commands,
244 WorkspaceSelection* selection,
245 WorkspaceSelectors* selectors) {
247 disconnect(store_,
nullptr,
this,
nullptr);
250 disconnect(selection_,
nullptr,
this,
nullptr);
253 commands_ = commands;
254 selection_ = selection;
255 selectors_ = selectors;
256 if (!store_ || !selection_) {
265void AnnualViewModel::emitChanged() {
270AnnualViewModel::normalizedAnalysisIds(
const QVariant& values)
const {
273 const QVariantList list = values.toList();
274 if (!list.isEmpty()) {
275 for (
const QVariant& value : list) {
276 const QString
id = analysisIdFromVariant(value);
277 if (
id.isEmpty() || seen.contains(
id)) {
285 const QString single = analysisIdFromVariant(values);
286 if (!single.isEmpty()) {
287 out.push_back(single);
292QString AnnualViewModel::analysisIdFromVariant(
const QVariant& value)
const {
293 if (!value.isValid()) {
296 if (value.typeId() == QMetaType::QString) {
297 return value.toString().trimmed();
299 const QVariantMap map = value.toMap();
300 if (!map.isEmpty()) {
301 const QString
id = map.value(QStringLiteral(
"id")).toString().trimmed();
303 ? map.value(QStringLiteral(
"objectId")).toString().trimmed()
306 return value.toString().trimmed();
309QVariantMap AnnualViewModel::analysisRowById(
const QString&
id)
const {
310 return rowById(normalizedAnalysisRows(),
id);
313QVariantMap AnnualViewModel::normalizeAnalysisRow(
const QVariant& value)
const {
314 const QVariantMap source = value.toMap();
315 const QString
id = analysisIdFromVariant(value);
319 const QString
name = source.value(QStringLiteral(
"name")).toString();
320 const QString display = source.value(QStringLiteral(
"display")).toString();
322 nonEmptyString(source, QStringLiteral(
"type"), qstr(kTab));
324 out.insert(QStringLiteral(
"id"),
id);
325 out.insert(QStringLiteral(
"name"),
name);
326 out.insert(QStringLiteral(
"display"),
327 !display.isEmpty() ? display : (!
name.isEmpty() ?
name :
id));
328 out.insert(QStringLiteral(
"type"), type);
329 out.insert(QStringLiteral(
"config"),
330 source.value(QStringLiteral(
"config"), QStringLiteral(
"{}")));
332 QStringLiteral(
"filter"),
333 source.value(QStringLiteral(
"filter"),
334 source.value(QStringLiteral(
"filterSpec"), QString())));
335 out.insert(QStringLiteral(
"exportFormat"),
336 source.value(QStringLiteral(
"exportFormat"), QString()));
340 out.insert(QStringLiteral(
"snapshotTransactions"),
341 source.value(QStringLiteral(
"snapshotTransactions"),
342 QStringLiteral(
"[]")));
343 return augmentAnalysisRow(out);
346QVariantList AnnualViewModel::normalizedAnalysisRows()
const {
351 const QVariantList source = selectors_->analysisRows();
352 rows.reserve(source.size());
353 for (
const QVariant& value : source) {
354 const QVariantMap row = normalizeAnalysisRow(value);
355 if (!row.isEmpty()) {
362QVariantList AnnualViewModel::buildAssignedAnalysisRows()
const {
363 const QVariantList rows = normalizedAnalysisRows();
365 out.reserve(analysisIds_.size());
366 for (
const QString&
id : analysisIds_) {
367 const QVariantMap row = rowById(rows,
id);
368 if (!row.isEmpty()) {
375QVariantList AnnualViewModel::buildAvailableAnalysisRows()
const {
376 const QVariantList rows = normalizedAnalysisRows();
377 const QSet<QString> selected(analysisIds_.begin(), analysisIds_.end());
379 for (
const QVariant& value : rows) {
380 const QString
id = value.toMap().value(QStringLiteral(
"id")).toString();
381 if (!
id.isEmpty() && !selected.contains(
id)) {
382 out.push_back(value);
388QVariantMap AnnualViewModel::augmentAnalysisRow(
const QVariantMap& row)
const {
389 QVariantMap out = row;
390 const QString type = row.value(QStringLiteral(
"type")).toString().toLower();
391 const QVariantList
options = exportOptionsForType(type);
392 out.insert(QStringLiteral(
"typeLabel"),
393 type == qstr(kPlot) ? tr(
"Plot") : tr(
"Table"));
394 out.insert(QStringLiteral(
"exportFormatOptions"), options);
396 QStringLiteral(
"exportFormatIndex"),
397 exportFormatIndex(options,
398 row.value(QStringLiteral(
"exportFormat")).toString()));
402QVariantList AnnualViewModel::exportOptionsForType(
const QString& type)
const {
403 if (type.trimmed().toLower() == qstr(kPlot)) {
404 return {QStringLiteral(
"PNG"), QStringLiteral(
"JPG")};
406 return {QStringLiteral(
"XLSX"), QStringLiteral(
"CSV")};
409int AnnualViewModel::exportFormatIndex(
const QVariantList& options,
410 const QString& exportFormat)
const {
411 const QString normalized = exportFormat.trimmed().toUpper();
412 for (
int i = 0; i <
options.size(); ++i) {
413 if (
options.at(i).toString().toUpper() == normalized) {
420QString AnnualViewModel::normalizedExportFormat(
const QString& value,
421 const QString& type)
const {
422 const QString normalized = value.trimmed().toLower();
423 const QString normalizedType = type.trimmed().toLower();
424 const QStringList allowed = normalizedType == qstr(kPlot)
425 ? QStringList{qstr(kPng), qstr(kJpg)}
426 : QStringList{qstr(kXlsx), qstr(kCsv)};
427 return allowed.contains(normalized) ? normalized : allowed.first();
432 out.reserve(analysisIds_.size());
433 for (
const QString&
id : analysisIds_) {
440 if (index < 0 || index >= availableAnalysisRows_.size()) {
443 const QString
id = availableAnalysisRows_.at(index)
445 .value(QStringLiteral(
"id"))
447 if (
id.isEmpty() || analysisIds_.contains(
id)) {
450 QStringList next = analysisIds_;
452 setAnalysisIds(next);
456 const QString trimmed =
id.trimmed();
457 if (trimmed.isEmpty() || !analysisIds_.contains(trimmed)) {
460 QStringList next = analysisIds_;
461 next.removeAll(trimmed);
462 setAnalysisIds(next);
466 const QString& exportFormat) {
470 const QVariantMap row = analysisRowById(
id.trimmed());
474 const QString type = nonEmptyString(row, QStringLiteral(
"type"), qstr(kTab));
475 const QString nextFormat = normalizedExportFormat(exportFormat, type);
476 commands_->updateAnalysisExportFormat(
id.trimmed(), nextFormat);
477 analysisMetadataDirty_ =
true;
478 refreshAnalysisSelectionRows();
479 rebuildAnnualResultState();
483void AnnualViewModel::refreshAnalysisSelectionRows() {
484 assignedAnalysisRows_ = buildAssignedAnalysisRows();
485 availableAnalysisRows_ = buildAvailableAnalysisRows();
488void AnnualViewModel::setAnalysisIds(
const QStringList& ids) {
491 for (
const QString&
id : ids) {
492 const QString trimmed =
id.trimmed();
493 if (trimmed.isEmpty() || seen.contains(trimmed)) {
496 seen.insert(trimmed);
497 next.push_back(trimmed);
499 if (analysisIds_ == next) {
503 refreshAnalysisSelectionRows();
504 rebuildAnnualResultState();
509 if (!commands_ || !selection_ || !
canSubmit()) {
513 "AnnualViewModel::submitCreate",
"Annual create submitted",
515 const QString
id = commands_->addAnnual(name_, year_, analysisIds_);
519 selection_->selectAnnual(
id);
529 "AnnualViewModel::submitUpdate",
"Annual update submitted",
543 "AnnualViewModel::deleteCurrent",
"Annual delete submitted",
545 commands_->deleteAnnual(removedId);
547 const QString nextId = deleteNextSelectionId(
annualRows(), removedId);
548 selection_->selectAnnual(nextId);
549 if (nextId.isEmpty()) {
560 if (rows.isEmpty()) {
563 selection_->selectAnnual(
569 selection_->selectAnnual(
id.trimmed());
573QVariantMap AnnualViewModel::annualRowById(
const QString&
id)
const {
577int AnnualViewModel::defaultYear()
const {
578 return QDate::currentDate().year() - 1;
581void AnnualViewModel::loadCreateState() {
583 year_ = defaultYear();
584 analysisIds_.clear();
586 analysisMetadataDirty_ =
false;
587 refreshAnalysisSelectionRows();
591void AnnualViewModel::loadSelectedAnnual() {
597 if (payload.isEmpty()) {
601 name_ = payload.value(QStringLiteral(
"name")).toString();
602 year_ = payload.value(QStringLiteral(
"year"), defaultYear()).toInt();
604 normalizedAnalysisIds(payload.value(QStringLiteral(
"analysisIds")));
605 analysisMetadataDirty_ =
false;
606 refreshAnalysisSelectionRows();
610void AnnualViewModel::captureSavedState() {
613 savedAnalysisIds_ = analysisIds_;
614 analysisMetadataDirty_ =
false;
620 qstr(kDeduplicated), tr(
"Included entries (exact matches)"),
621 annualTransactionGroups_.value(qstr(kDeduplicated)).toList()),
623 qstr(kSimilar), tr(
"Included entries (possible variants)"),
624 annualTransactionGroups_.value(qstr(kSimilar)).toList()),
626 qstr(kDivergent), tr(
"Included entries (unique)"),
627 annualTransactionGroups_.value(qstr(kDivergent)).toList()),
629 qstr(kWorkspaceOnly),
630 tr(
"Missing live transactions from selected year"),
631 annualTransactionGroups_.value(qstr(kWorkspaceOnly)).toList()),
633 qstr(kMissingLive), tr(
"Included deleted transactions"),
634 annualTransactionGroups_.value(qstr(kMissingLive)).toList()),
640 QVariantMap{{QStringLiteral(
"label"), tr(
"Included transactions")},
641 {QStringLiteral(
"value"), annualTransactions_.size()},
642 {QStringLiteral(
"tone"), QStringLiteral(
"primary")}},
644 {QStringLiteral(
"label"), tr(
"Duplicate entries")},
645 {QStringLiteral(
"value"),
646 verificationIssues_.value(QStringLiteral(
"duplicateCount")).toInt()},
647 {QStringLiteral(
"tone"), QStringLiteral(
"primary")}},
648 QVariantMap{{QStringLiteral(
"label"),
649 tr(
"Missing live transactions from selected year")},
650 {QStringLiteral(
"value"),
651 verificationIssues_.value(QStringLiteral(
"missingFromYear"))
653 {QStringLiteral(
"tone"), QStringLiteral(
"primary")}},
655 {QStringLiteral(
"label"), tr(
"Included deleted transactions")},
656 {QStringLiteral(
"value"),
657 verificationIssues_.value(QStringLiteral(
"missingLive")).toInt()},
658 {QStringLiteral(
"tone"), QStringLiteral(
"primary")}},
660 {QStringLiteral(
"label"),
661 tr(
"Transactions from a different year than selected")},
662 {QStringLiteral(
"value"),
663 verificationIssues_.value(QStringLiteral(
"mixedInAnnual")).toInt()},
664 {QStringLiteral(
"tone"),
665 verificationIssues_.value(QStringLiteral(
"mixedInAnnual")).toInt() ==
667 ? QStringLiteral(
"success")
668 : QStringLiteral(
"danger")}},
669 QVariantMap{{QStringLiteral(
"label"), tr(
"Assigned analyses")},
670 {QStringLiteral(
"value"), analysisIds_.size()},
671 {QStringLiteral(
"tone"), QStringLiteral(
"primary")}},
676 return tr(
"Neutral: %1, Unverified: %2, Verified: %3, Completed: %4")
677 .arg(statusMetrics_.value(QStringLiteral(
"neutral")).toInt())
678 .arg(statusMetrics_.value(QStringLiteral(
"unverified")).toInt())
679 .arg(statusMetrics_.value(QStringLiteral(
"verified")).toInt())
680 .arg(statusMetrics_.value(QStringLiteral(
"completed")).toInt());
684 const QString trimmed = key.trimmed();
685 if (trimmed.isEmpty()) {
688 sectionExpanded_.insert(trimmed, !isTransactionSectionExpanded(trimmed));
692bool AnnualViewModel::isTransactionSectionExpanded(
const QString& key)
const {
693 return sectionExpanded_.value(key.trimmed(),
true).toBool();
696void AnnualViewModel::rebuildAnnualResultState() {
698 const int currentRevision = workspaceRevision();
699 if (lastPreviewAnnualId_ == currentAnnualId && lastPreviewYear_ == year_ &&
700 lastPreviewAnalysisIds_ == analysisIds_ &&
701 lastPreviewRevision_ == currentRevision) {
704 lastPreviewAnnualId_ = currentAnnualId;
705 lastPreviewYear_ = year_;
706 lastPreviewAnalysisIds_ = analysisIds_;
707 lastPreviewRevision_ = currentRevision;
708 if (!annualWorkflow_) {
709 applyAnnualResult({});
712 applyAnnualResult(annualWorkflow_->computeAnnualPreview(currentAnnualId,
713 analysisIds_, year_));
716void AnnualViewModel::applyAnnualResult(
const QVariantMap& result) {
717 const QVariantMap stats = result.value(QStringLiteral(
"stats")).toMap();
718 verificationIssues_ =
719 QVariantMap{{QStringLiteral(
"missingFromYear"),
720 stats.value(QStringLiteral(
"missingFromYear"), 0)},
721 {QStringLiteral(
"mixedInAnnual"),
722 stats.value(QStringLiteral(
"mixedYear"), 0)},
723 {QStringLiteral(
"duplicateCount"),
724 stats.value(QStringLiteral(
"duplicateCount"), 0)},
725 {QStringLiteral(
"missingLive"),
726 stats.value(QStringLiteral(
"missingLive"), 0)}};
727 statusMetrics_ = QVariantMap{
728 {QStringLiteral(
"neutral"), stats.value(QStringLiteral(
"neutral"), 0)},
729 {QStringLiteral(
"unverified"),
730 stats.value(QStringLiteral(
"unverified"), 0)},
731 {QStringLiteral(
"verified"), stats.value(QStringLiteral(
"verified"), 0)},
732 {QStringLiteral(
"completed"),
733 stats.value(QStringLiteral(
"completed"), 0)}};
735 annualTransactions_ = rowsFromResultBucket(
736 result.value(QStringLiteral(
"transactions")).toList());
737 annualTransactionGroups_ = QVariantMap{
738 {qstr(kDeduplicated),
739 rowsFromResultBucket(result.value(qstr(kDeduplicated)).toList())},
741 rowsFromResultBucket(result.value(qstr(kSimilar)).toList())},
743 rowsFromResultBucket(result.value(qstr(kDivergent)).toList())},
744 {qstr(kWorkspaceOnly),
745 rowsFromResultBucket(result.value(qstr(kWorkspaceOnly)).toList())},
747 rowsFromResultBucket(result.value(qstr(kMissingLive)).toList())}};
750QVariantMap AnnualViewModel::emptyIssues()
const {
751 return {{QStringLiteral(
"missingFromYear"), 0},
752 {QStringLiteral(
"mixedInAnnual"), 0},
753 {QStringLiteral(
"duplicateCount"), 0},
754 {QStringLiteral(
"missingLive"), 0}};
757QVariantMap AnnualViewModel::emptyStatusMetrics()
const {
758 return {{QStringLiteral(
"neutral"), 0},
759 {QStringLiteral(
"unverified"), 0},
760 {QStringLiteral(
"verified"), 0},
761 {QStringLiteral(
"completed"), 0}};
765AnnualViewModel::rowsFromResultBucket(
const QVariantList& rows)
const {
767 out.reserve(rows.size());
768 for (
const QVariant& value : rows) {
769 out.push_back(transactionRow(value.toMap()));
774QVariantMap AnnualViewModel::transactionRow(
const QVariantMap& source)
const {
775 QVariantMap row = source;
776 const QVariantList names =
777 source.value(QStringLiteral(
"sourceAnalysisNames")).toList();
778 QStringList nameStrings;
779 nameStrings.reserve(names.size());
780 for (
const QVariant& value : names) {
781 const QString text = value.toString();
782 if (!text.isEmpty()) {
783 nameStrings.push_back(text);
786 const int status = source.value(QStringLiteral(
"status")).toInt();
787 row.insert(QStringLiteral(
"contractType"),
788 source.value(QStringLiteral(
"contractType")).toString());
789 row.insert(QStringLiteral(
"amountText"),
790 amountText(source.value(QStringLiteral(
"amount"))));
791 row.insert(QStringLiteral(
"sourceNamesText"),
792 nameStrings.join(QStringLiteral(
", ")));
793 row.insert(QStringLiteral(
"allocatableText"),
794 source.value(QStringLiteral(
"allocatable")).toBool()
796 : tr(
"Non-allocatable"));
797 row.insert(QStringLiteral(
"contractTypeLabel"),
798 nonEmptyString(source, QStringLiteral(
"contractType"),
799 tr(
"No type assigned")));
800 row.insert(QStringLiteral(
"statusText"),
801 nonEmptyString(source, QStringLiteral(
"statusText"),
803 row.insert(QStringLiteral(
"statusTone"),
804 status == 3 ? QStringLiteral(
"success")
805 : status == 2 ? QStringLiteral(
"info")
806 : status == 1 ? QStringLiteral(
"warning")
807 : QStringLiteral(
"primary"));
812AnnualViewModel::transactionSection(
const QString& key,
const QString& title,
813 const QVariantList& rows)
const {
814 return {{QStringLiteral(
"key"), key},
815 {QStringLiteral(
"title"), title},
816 {QStringLiteral(
"rows"), rows},
817 {QStringLiteral(
"expanded"), isTransactionSectionExpanded(key)},
818 {QStringLiteral(
"visible"), !rows.isEmpty()}};
Declares the QML API for the Annual view.
Declares the UI workflow for annual result computation.
Declarations for the UI PayloadKeys component.
Provides translated C++ UI strings for shell, workflow, adapter and view model code.
Declarations for the UI Trace component.
Declares the workspace selection state synchronized with the store.
Declares read-only workspace selectors for QML payloads.
Declares the UI workspace snapshot store.
void setYear(int value)
Updates the selected annual year.
void setAnnualWorkflow(AnnualWorkflow *value)
Sets the workflow used for annual computation.
Q_INVOKABLE void submitCreate()
Creates an annual through workspace.
Q_INVOKABLE void submitUpdate()
Updates the selected annual through workspace.
Q_INVOKABLE void setAnalysisExportFormat(const QString &id, const QString &exportFormat)
Updates export format for an assigned analysis.
Q_INVOKABLE void navigate(int delta)
Navigates between annual rows.
Q_INVOKABLE void stepYear(int delta)
Steps the selected year by a relative delta.
QVariantList analysisIds() const
Returns ids of analyses assigned to the annual.
Q_INVOKABLE void selectAnnual(const QString &id)
Selects an annual row.
QVariantList verificationRows
Q_INVOKABLE void addAvailableAnalysisAtIndex(int index)
Adds an available analysis by row index.
Q_INVOKABLE void removeAnalysis(const QString &id)
Removes an analysis from the annual.
Q_INVOKABLE void toggleTransactionSection(const QString &key)
Toggles expansion state for a transaction section.
AnnualViewModel(QObject *parent=nullptr)
Creates the annual view model.
void setName(const QString &value)
Updates the annual name field.
Q_INVOKABLE void refreshFromSelection()
Reloads create or edit state from the workspace selection.
Q_INVOKABLE void toggleContent()
Toggles between annual analysis assignment and transaction preview.
QVariantList transactionSections
void setWorkspaceRoles(WorkspaceStore *store, WorkspaceCommands *commands, WorkspaceSelection *selection, WorkspaceSelectors *selectors)
Sets workspace roles used for annual rows, selection and mutations.
Q_INVOKABLE void deleteCurrent()
Deletes the selected annual through workspace.
Q_INVOKABLE void resetCreateState()
Resets the create form to defaults.
QString statusSummaryText
Coordinates annual computation for UI states.
Maps UI intent to core workspace writer commands.
Tracks the currently selected workspace entities.
void selectedAnnualIdChanged()
Emitted when the selected annual id changed.
Projects workspace store data into QML-facing read models.
int dataRevision() const noexcept
Returns the current workspace data revision.
Owns the current UI-side workspace snapshot and revision lifecycle.
void dataRevisionChanged()
void traceViewModel(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a view model trace event.
const auto kIncludeCalcAdjustments
QVariantList options(const QString &neutral, const QString &unverified, const QString &verified, const QString &completed)
Builds transaction status options.
QString neutral()
Label for neutral transaction status.
Declares UI-side workspace command submission.