FOSSredder 0.5.0
Loading...
Searching...
No Matches
BookingViewModel.cpp
Go to the documentation of this file.
1
5
7
8#include <algorithm>
9
10#include "ui/i18n/Text.h"
17
18namespace ui {
19
20namespace {
21
22QString stringValue(const QVariant& value) {
23 const QVariantMap map = value.toMap();
24 if (!map.isEmpty()) {
25 const QString aliasValue = map.value(QStringLiteral("value")).toString();
26 if (!aliasValue.isEmpty()) {
27 return aliasValue;
28 }
29 const QString aliasSource = map.value(QStringLiteral("source")).toString();
30 if (!aliasSource.isEmpty()) {
31 return aliasSource;
32 }
33 const QString id = map.value(QStringLiteral("id")).toString();
34 if (!id.isEmpty()) {
35 return id;
36 }
37 }
38 return value.toString();
39}
40
41QVariantList normalizeStringValues(const QVariantList& values) {
42 QVariantList out;
43 out.reserve(values.size());
44 for (const auto& value : values) {
45 out.push_back(stringValue(value));
46 }
47 return out;
48}
49
50QString rowIdAt(const QVariantList& rows, int index,
51 const QString& idKey = QStringLiteral("id")) {
52 if (index < 0 || index >= rows.size()) {
53 return {};
54 }
55 return rows.at(index).toMap().value(idKey).toString();
56}
57
58int indexOfId(const QVariantList& rows, const QString& id,
59 const QString& idKey = QStringLiteral("id")) {
60 if (id.isEmpty()) {
61 return -1;
62 }
63 for (int i = 0; i < rows.size(); ++i) {
64 if (rows.at(i).toMap().value(idKey).toString() == id) {
65 return i;
66 }
67 }
68 return -1;
69}
70
71int indexOfKeyValue(const QVariantList& rows, const QString& key,
72 const QVariant& value) {
73 for (int i = 0; i < rows.size(); ++i) {
74 if (rows.at(i).toMap().value(key) == value) {
75 return i;
76 }
77 }
78 return -1;
79}
80
81QVariantMap rowById(const QVariantList& rows, const QString& id,
82 const QString& idKey = QStringLiteral("id")) {
83 const int index = indexOfId(rows, id.trimmed(), idKey);
84 return index >= 0 ? rows.at(index).toMap() : QVariantMap{};
85}
86
87QString deleteNextSelectionId(const QVariantList& rows,
88 const QString& removedId, int defaultIndex = 0,
89 const QString& idKey = QStringLiteral("id")) {
90 if (rows.isEmpty()) {
91 return {};
92 }
93 const int removedIndex = indexOfId(rows, removedId, idKey);
94 const int nextIndex = removedIndex >= 0 ? removedIndex + 1 : defaultIndex;
95 const int wrapped = nextIndex % rows.size();
96 return rowIdAt(rows, wrapped < 0 ? wrapped + rows.size() : wrapped, idKey);
97}
98
99int wrappedIndex(int index, int count) {
100 if (count <= 0) {
101 return -1;
102 }
103 const int normalized = index % count;
104 return normalized < 0 ? normalized + count : normalized;
105}
106
107QVariantList rowIds(const QVariantList& rows,
108 const QString& idKey = QStringLiteral("id")) {
109 QVariantList out;
110 out.reserve(rows.size());
111 for (const auto& rowValue : rows) {
112 const QString id = rowValue.toMap().value(idKey).toString();
113 if (!id.isEmpty()) {
114 out.push_back(id);
115 }
116 }
117 return out;
118}
119
120bool rowHasId(const QVariantList& rows, const QString& id,
121 const QString& idKey = QStringLiteral("id")) {
122 return indexOfId(rows, id.trimmed(), idKey) >= 0;
123}
124
125QVariantList pruneAndAppendMissingIds(const QVariantList& preferredIds,
126 const QVariantList& availableIds) {
127 QVariantList out;
128 for (const auto& id : preferredIds) {
129 if (availableIds.contains(id) && !out.contains(id)) {
130 out.push_back(id);
131 }
132 }
133 for (const auto& id : availableIds) {
134 if (!out.contains(id)) {
135 out.push_back(id);
136 }
137 }
138 return out;
139}
140
141QVariantList orderRowsByIds(const QVariantList& rows,
142 const QVariantList& orderIds,
143 const QString& idKey = QStringLiteral("id")) {
144 QVariantList out;
145 out.reserve(rows.size());
146 for (const auto& orderId : orderIds) {
147 for (const auto& rowValue : rows) {
148 if (rowValue.toMap().value(idKey).toString() == orderId.toString() &&
149 !out.contains(rowValue)) {
150 out.push_back(rowValue);
151 break;
152 }
153 }
154 }
155 for (const auto& rowValue : rows) {
156 if (!out.contains(rowValue)) {
157 out.push_back(rowValue);
158 }
159 }
160 return out;
161}
162
163QVariantList orderWithInsertedId(const QVariantList& currentOrder,
164 const QVariantList& availableIds,
165 const QString& insertedId,
166 int insertAfterIndex) {
167 QVariantList out = pruneAndAppendMissingIds(currentOrder, availableIds);
168 if (!insertedId.isEmpty() && !out.contains(insertedId)) {
169 const int insertIndex =
170 std::clamp(insertAfterIndex + 1, 0, static_cast<int>(out.size()));
171 out.insert(insertIndex, insertedId);
172 }
173 return out;
174}
175
176QVariantMap orderedSelectionState(const QVariantList& rows,
177 const QVariantList& preferredOrder,
178 int currentIndex, const QString& selectedId,
179 const QString& idKey = QStringLiteral("id")) {
180 const QVariantList orderIds =
181 pruneAndAppendMissingIds(preferredOrder, rowIds(rows, idKey));
182 const QVariantList orderedRows = orderRowsByIds(rows, orderIds, idKey);
183 if (orderedRows.isEmpty()) {
184 return {{QStringLiteral("rows"), orderedRows},
185 {QStringLiteral("orderIds"), orderIds},
186 {QStringLiteral("index"), -1},
187 {QStringLiteral("id"), QString()}};
188 }
189 const int selectedIndex = indexOfId(orderedRows, selectedId, idKey);
190 const int lastIndex = static_cast<int>(orderedRows.size()) - 1;
191 const int resolvedIndex = selectedIndex >= 0
192 ? selectedIndex
193 : std::clamp(currentIndex, 0, lastIndex);
194 const QString resolvedId = selectedIndex >= 0
195 ? selectedId
196 : rowIdAt(orderedRows, resolvedIndex, idKey);
197 return {{QStringLiteral("rows"), orderedRows},
198 {QStringLiteral("orderIds"), orderIds},
199 {QStringLiteral("index"), resolvedIndex},
200 {QStringLiteral("id"), resolvedId}};
201}
202
203QVariantMap
204navigateSelectionState(const QVariantList& rows, int currentIndex,
205 const QString& selectedId, int delta,
206 int defaultIndex = 0,
207 const QString& idKey = QStringLiteral("id")) {
208 const int selectedIndex = indexOfId(rows, selectedId, idKey);
209 const int baseIndex = selectedIndex >= 0 ? selectedIndex : defaultIndex;
210 const int nextIndex = wrappedIndex(baseIndex + delta, rows.size());
211 return {{QStringLiteral("index"), nextIndex},
212 {QStringLiteral("id"), rowIdAt(rows, nextIndex, idKey)}};
213}
214
215QVariantMap
216deleteReselectionState(const QVariantList& rows,
217 const QVariantList& preferredOrder, int currentIndex,
218 const QString& removedId,
219 const QString& idKey = QStringLiteral("id")) {
220 QVariantList remainingRows;
221 for (const auto& rowValue : rows) {
222 if (rowValue.toMap().value(idKey).toString() != removedId) {
223 remainingRows.push_back(rowValue);
224 }
225 }
226 QVariantList remainingOrder;
227 for (const auto& id : preferredOrder) {
228 if (id.toString() != removedId) {
229 remainingOrder.push_back(id);
230 }
231 }
232 return orderedSelectionState(remainingRows, remainingOrder, currentIndex, {},
233 idKey);
234}
235
236QString rememberedOrFirstRowId(const QVariantList& rows,
237 const QVariantMap& rememberedIds,
238 const QString& ownerId,
239 const QString& idKey = QStringLiteral("id")) {
240 const QString remembered =
241 rememberedIds.value(ownerId.trimmed()).toString().trimmed();
242 if (rowHasId(rows, remembered, idKey)) {
243 return remembered;
244 }
245 return rows.isEmpty() ? QString() : rowIdAt(rows, 0, idKey);
246}
247
248bool containsTrimmed(const QVariantList& values, const QString& id) {
249 const QString nextId = id.trimmed();
250 if (nextId.isEmpty()) {
251 return false;
252 }
253 for (const auto& value : values) {
254 if (stringValue(value).trimmed() == nextId) {
255 return true;
256 }
257 }
258 return false;
259}
260
261QVariantList addUniqueTrimmed(const QVariantList& values,
262 const QString& value) {
263 const QString next = value.trimmed();
264 QVariantList out = normalizeStringValues(values);
265 if (next.isEmpty() || containsTrimmed(out, next)) {
266 return out;
267 }
268 out.push_back(next);
269 return out;
270}
271
272QVariantList removeTrimmed(const QVariantList& values, const QString& value) {
273 const QString target = value.trimmed();
274 if (target.isEmpty()) {
275 return values;
276 }
277 QVariantList out;
278 out.reserve(values.size());
279 for (const auto& entry : values) {
280 const QString current = stringValue(entry).trimmed();
281 if (current != target) {
282 out.push_back(current);
283 }
284 }
285 return out;
286}
287
288bool setSelectedId(QVariantList& selectedIds, const QString& id,
289 bool selected) {
290 const QString normalizedId = id.trimmed();
291 if (normalizedId.isEmpty()) {
292 return false;
293 }
294 const QVariantList next = selected
295 ? addUniqueTrimmed(selectedIds, normalizedId)
296 : removeTrimmed(selectedIds, normalizedId);
297 if (next == selectedIds) {
298 return false;
299 }
300 selectedIds = next;
301 return true;
302}
303
304QVariantMap emptyTransactionFormState() {
305 QVariantMap tx;
306 tx.insert(QStringLiteral("id"), QString());
307 tx.insert(QStringLiteral("name"), QString());
308 tx.insert(QStringLiteral("bookingDate"), QString());
309 tx.insert(QStringLiteral("valuta"), QString());
310 tx.insert(QStringLiteral("amount"), 0.0);
311 tx.insert(QStringLiteral("description"), QString());
312 tx.insert(QStringLiteral("status"), 0);
313 tx.insert(QStringLiteral("actorId"), QString());
314 tx.insert(QStringLiteral("propertyIds"), QVariantList());
315 tx.insert(QStringLiteral("allocatable"), false);
316 tx.insert(QStringLiteral("contractId"), QString());
317 tx.insert(QStringLiteral("statementId"), QString());
318 return tx;
319}
320
321QVariantMap normalizeTransactionFormState(const QVariantMap& tx) {
322 QVariantMap out = emptyTransactionFormState();
323 for (auto it = tx.constBegin(); it != tx.constEnd(); ++it) {
324 out.insert(it.key(), it.value());
325 }
326 out.insert(QStringLiteral("status"),
327 out.value(QStringLiteral("status")).toInt());
328 out.insert(
329 QStringLiteral("propertyIds"),
330 normalizeStringValues(out.value(QStringLiteral("propertyIds")).toList()));
331 out.insert(QStringLiteral("actorId"),
332 out.value(QStringLiteral("actorId")).toString());
333 out.insert(QStringLiteral("contractId"),
334 out.value(QStringLiteral("contractId")).toString());
335 out.insert(QStringLiteral("name"),
336 out.value(QStringLiteral("name")).toString());
337 out.insert(QStringLiteral("bookingDate"),
338 out.value(QStringLiteral("bookingDate")).toString());
339 out.insert(QStringLiteral("valuta"),
340 out.value(QStringLiteral("valuta")).toString());
341 out.insert(QStringLiteral("description"),
342 out.value(QStringLiteral("description")).toString());
343 out.insert(QStringLiteral("statementId"),
344 out.value(QStringLiteral("statementId")).toString());
345 out.insert(QStringLiteral("allocatable"),
346 out.value(QStringLiteral("allocatable")).toBool());
347 return out;
348}
349
350QVariantList normalizeTransactionFormStates(const QVariantList& values) {
351 QVariantList out;
352 out.reserve(values.size());
353 for (const auto& value : values) {
354 out.push_back(normalizeTransactionFormState(value.toMap()));
355 }
356 return out;
357}
358
359bool transactionFormStateHasContent(const QVariantMap& tx) {
360 const QVariantMap normalized = normalizeTransactionFormState(tx);
361 const QVariant amount = normalized.value(QStringLiteral("amount"));
362 const bool hasAmount = !amount.toString().trimmed().isEmpty();
363 return !normalized.value(QStringLiteral("name")).toString().isEmpty() ||
364 !normalized.value(QStringLiteral("bookingDate"))
365 .toString()
366 .isEmpty() ||
367 !normalized.value(QStringLiteral("valuta")).toString().isEmpty() ||
368 hasAmount ||
369 !normalized.value(QStringLiteral("actorId")).toString().isEmpty() ||
370 !normalized.value(QStringLiteral("contractId")).toString().isEmpty() ||
371 !normalized.value(QStringLiteral("propertyIds")).toList().isEmpty() ||
372 normalized.value(QStringLiteral("allocatable")).toBool() ||
373 normalized.value(QStringLiteral("status")).toInt() != 0;
374}
375
376QVariantMap createFormStateList(const QVariantList& states, int currentIndex,
377 const QVariantMap& emptyState) {
378 QVariantList normalizedStates = normalizeTransactionFormStates(states);
379 if (normalizedStates.isEmpty()) {
380 normalizedStates.push_back(normalizeTransactionFormState(emptyState));
381 }
382 const int lastIndex = static_cast<int>(normalizedStates.size()) - 1;
383 const int index = std::clamp(currentIndex, 0, lastIndex);
384 return {{QStringLiteral("states"), normalizedStates},
385 {QStringLiteral("index"), index}};
386}
387
388QVariantMap insertFormStateAfterCurrent(const QVariantList& states,
389 int currentIndex,
390 const QVariantMap& emptyState) {
391 const QVariantMap base =
392 createFormStateList(states, currentIndex, emptyState);
393 QVariantList normalizedStates = base.value(QStringLiteral("states")).toList();
394 const int index = base.value(QStringLiteral("index")).toInt();
395 const int insertIndex =
396 std::clamp(index + 1, 0, static_cast<int>(normalizedStates.size()));
397 normalizedStates.insert(insertIndex,
398 normalizeTransactionFormState(emptyState));
399 return {{QStringLiteral("states"), normalizedStates},
400 {QStringLiteral("index"), insertIndex}};
401}
402
403QVariantMap removeFormStateAt(const QVariantList& states, int currentIndex,
404 const QVariantMap& emptyState) {
405 const QVariantMap base =
406 createFormStateList(states, currentIndex, emptyState);
407 QVariantList normalizedStates = base.value(QStringLiteral("states")).toList();
408 int index = base.value(QStringLiteral("index")).toInt();
409 if (index < 0 || index >= normalizedStates.size()) {
410 index = 0;
411 }
412 normalizedStates.removeAt(index);
413 if (normalizedStates.isEmpty()) {
414 normalizedStates.push_back(normalizeTransactionFormState(emptyState));
415 index = 0;
416 } else {
417 index = std::clamp(index, 0, static_cast<int>(normalizedStates.size()) - 1);
418 }
419 return {{QStringLiteral("states"), normalizedStates},
420 {QStringLiteral("index"), index}};
421}
422
423QVariantMap currentFormState(const QVariantList& states, int currentIndex,
424 const QVariantMap& emptyState) {
425 const QVariantMap base =
426 createFormStateList(states, currentIndex, emptyState);
427 const QVariantList normalizedStates =
428 base.value(QStringLiteral("states")).toList();
429 const int index = base.value(QStringLiteral("index")).toInt();
430 return {{QStringLiteral("states"), normalizedStates},
431 {QStringLiteral("index"), index},
432 {QStringLiteral("state"),
433 normalizeTransactionFormState(normalizedStates.at(index).toMap())}};
434}
435
436QVariantMap setCurrentRawFormState(const QVariantList& states, int currentIndex,
437 const QVariantMap& formState,
438 const QVariantMap& emptyState) {
439 QVariantMap state = currentFormState(states, currentIndex, emptyState);
440 QVariantList nextStates = state.value(QStringLiteral("states")).toList();
441 const int index = state.value(QStringLiteral("index")).toInt();
442 if (index >= 0 && index < nextStates.size()) {
443 nextStates[index] = formState;
444 }
445 state.insert(QStringLiteral("states"), nextStates);
446 state.insert(QStringLiteral("state"), formState);
447 return state;
448}
449
450bool bookingEditStateChanged(const QString& savedStatementName,
451 const QVariantMap& savedTransactionState,
452 const QString& currentStatementName,
453 const QVariantMap& transaction) {
454 return savedStatementName != currentStatementName ||
455 savedTransactionState != normalizeTransactionFormState(transaction);
456}
457
458} // namespace
460 WorkspaceCommands* commands,
461 WorkspaceSelection* selection,
462 WorkspaceSelectors* selectors,
463 QObject* parent)
464 : QObject(parent), store_(store), commands_(commands),
465 selection_(selection), selectors_(selectors) {
466 createTransactionStates_ = QVariantList{emptyTransaction()};
467 currentTransactionFormState_ = emptyTransaction();
468 bindSignals();
469 if (isCreateMode()) {
471 } else {
472 syncEditState();
473 }
474}
475
477 return selectedStatementId().isEmpty();
478}
479
481 return isCreateMode() ? createStatementName_ : editStatementName_;
482}
483
484void BookingViewModel::setStatementName(const QString& value) {
485 if (isCreateMode()) {
486 if (createStatementName_ == value) {
487 return;
488 }
489 createStatementName_ = value;
490 } else {
491 if (editStatementName_ == value) {
492 return;
493 }
494 editStatementName_ = value;
495 }
496 emit changed();
497}
498
499void BookingViewModel::bindSignals() {
500 if (!store_ || !selection_) {
501 return;
502 }
503 QObject::connect(store_, &WorkspaceStore::dataRevisionChanged, this,
504 [this]() {
505 if (isCreateMode()) {
507 } else {
508 syncEditState();
509 }
510 });
511 QObject::connect(selection_, &WorkspaceSelection::selectedStatementIdChanged,
512 this, [this]() {
513 if (isCreateMode()) {
515 } else {
516 editTransactionOrderIds_.clear();
517 editTransactionIndex_ = 0;
518 syncEditState();
519 }
520 });
521 QObject::connect(selection_,
523 [this]() {
524 if (isCreateMode()) {
526 } else {
527 rememberSelectedTransaction();
528 syncEditState();
529 }
530 });
531}
532
534 return selectors_ ? selectors_->statementRowsWithTransactions()
535 : QVariantList();
536}
537
539 return selection_ ? selection_->selectedStatementId() : QString();
540}
541
543 return selection_ ? selection_->selectedTransactionId() : QString();
544}
545
547 return !statementRows().isEmpty();
548}
549
550QVariantList
551BookingViewModel::statementTransactionRows(const QString& statementId) const {
552 return selectors_ ? selectors_->statementTransactionRows(statementId)
553 : QVariantList();
554}
555
556void BookingViewModel::selectStatement(const QString& statementId) {
557 if (!selection_) {
558 return;
559 }
560 selection_->selectTransaction(statementId, QString());
561}
562
563void BookingViewModel::selectTransaction(const QString& statementId,
564 const QString& transactionId) {
565 if (!selection_) {
566 return;
567 }
568 selection_->selectTransaction(statementId, transactionId);
569}
570
572 if (isCreateMode()) {
573 return createTransactionStates_.isEmpty()
574 ? tr("No transactions")
575 : tr("Transaction %1 / %2")
576 .arg(createTransactionIndex_ + 1)
577 .arg(createTransactionStates_.size());
578 }
579
580 const QVariantMap state = editTransactionState();
581 const QVariantList rows = state.value(QStringLiteral("rows")).toList();
582 if (rows.isEmpty()) {
583 return tr("No transactions");
584 }
585 const int index = state.value(QStringLiteral("index")).toInt();
586 return tr("Transaction %1 / %2").arg(index + 1).arg(rows.size());
587}
588
590 return isCreateMode() ? createTransactionStates_.size() > 1
591 : editTransactionState()
592 .value(QStringLiteral("rows"))
593 .toList()
594 .size() > 1;
595}
596
598 return isCreateMode() || !selectedStatementId().isEmpty();
599}
600
602 if (isCreateMode()) {
603 return createTransactionStates_.size() > 1;
604 }
605 return hasMultipleTransactions() &&
606 !editTransactionData_.value(QStringLiteral("id")).toString().isEmpty();
607}
608
609QVariantMap BookingViewModel::emptyTransaction() const {
610 return emptyTransactionFormState();
611}
612
613QVariantMap
614BookingViewModel::normalizeTransaction(const QVariantMap& tx) const {
615 return normalizeTransactionFormState(tx);
616}
617
618QVariantMap BookingViewModel::editTransactionState() const {
619 if (!selectors_ || selectedStatementId().isEmpty()) {
620 return {{QStringLiteral("rows"), QVariantList()},
621 {QStringLiteral("orderIds"), QVariantList()},
622 {QStringLiteral("index"), -1},
623 {QStringLiteral("id"), QString()}};
624 }
625 const QVariantList rows = statementTransactionRows(selectedStatementId());
626 const QVariantList preferred = editTransactionOrderIds_.isEmpty()
627 ? rowIds(rows)
628 : editTransactionOrderIds_;
629 return orderedSelectionState(rows, preferred, editTransactionIndex_,
630 selectedTransactionId(), QStringLiteral("id"));
631}
632
633QVariantMap BookingViewModel::transactionById(const QString& txId) const {
634 if (!selectors_ || txId.isEmpty()) {
635 return emptyTransaction();
636 }
637 QVariantMap transaction = selectors_->transactionRowById(txId);
638 if (transaction.isEmpty()) {
639 return emptyTransaction();
640 }
642 payload::mapper::toVariantList(transaction.value(
644 return normalizeTransaction(transaction);
645}
646
647QVariantMap
648BookingViewModel::normalizedTransactionState(const QVariantMap& data) const {
649 return normalizeTransactionFormState(data);
650}
651
652QVariantMap BookingViewModel::transactionData() const {
653 return isCreateMode() ? currentTransactionFormState_ : editTransactionData_;
654}
655
656void BookingViewModel::setTransactionData(const QVariantMap& value) {
657 if (isCreateMode()) {
658 const QVariantMap formState = value.isEmpty() ? emptyTransaction() : value;
659 const QVariantMap state = setCurrentRawFormState(
660 createTransactionStates_, createTransactionIndex_, formState,
661 emptyTransaction());
662 createTransactionStates_ = state.value(QStringLiteral("states")).toList();
663 createTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
664 syncCurrentCreateTransaction();
665 } else {
666 if (editTransactionData_ == value) {
667 return;
668 }
669 editTransactionData_ = value.isEmpty() ? emptyTransaction() : value;
670 }
671 emit changed();
672}
673
674QVariant BookingViewModel::transactionField(const QString& key) const {
675 return transactionData().value(key);
676}
677
678void BookingViewModel::setTransactionField(const QString& key,
679 const QVariant& value) {
680 QVariantMap next = transactionData();
681 next.insert(key, value);
682 setTransactionData(next);
683}
684
685void BookingViewModel::applyTransactionFormChange(const QVariantMap& changes) {
686 if (!selectors_) {
687 return;
688 }
689 setTransactionData(selectors_->transactionFormWithCatalogSelection(
690 transactionData(), changes));
691}
692
694 return transactionField(QStringLiteral("name")).toString();
695}
696
697void BookingViewModel::setTransactionName(const QString& value) {
698 setTransactionField(QStringLiteral("name"), value);
699}
700
702 return transactionField(QStringLiteral("bookingDate")).toString();
703}
704
706 setTransactionField(QStringLiteral("bookingDate"), value);
707}
708
710 return transactionField(QStringLiteral("valuta")).toString();
711}
712
713void BookingViewModel::setTransactionValuta(const QString& value) {
714 setTransactionField(QStringLiteral("valuta"), value);
715}
716
718 const QVariant amount = transactionField(QStringLiteral("amount"));
719 return amount.isValid() ? amount.toString() : QString();
720}
721
723 setTransactionField(QStringLiteral("amount"), value);
724}
725
727 return transactionField(QStringLiteral("allocatable")).toBool();
728}
729
731 setTransactionField(QStringLiteral("allocatable"), value);
732}
733
734void BookingViewModel::captureEditState() {
735 savedEditStatementName_ = editStatementName_;
736 savedEditTransactionState_ = normalizedTransactionState(editTransactionData_);
737}
738
739void BookingViewModel::syncCurrentCreateTransaction() {
740 if (!selectors_) {
741 createTransactionStates_ = QVariantList{emptyTransaction()};
742 createTransactionIndex_ = 0;
743 currentTransactionFormState_ = emptyTransaction();
744 return;
745 }
746 const QVariantMap state = currentFormState(
747 createTransactionStates_, createTransactionIndex_, emptyTransaction());
748 createTransactionStates_ = state.value(QStringLiteral("states")).toList();
749 createTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
750 currentTransactionFormState_ = state.value(QStringLiteral("state")).toMap();
751}
752
754 createStatementName_.clear();
755 if (selectors_) {
756 const QVariantMap state =
757 createFormStateList(QVariantList(), 0, emptyTransaction());
758 createTransactionStates_ = state.value(QStringLiteral("states")).toList();
759 createTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
760 currentTransactionFormState_ = state.value(QStringLiteral("state")).toMap();
761 } else {
762 createTransactionStates_ = QVariantList{emptyTransaction()};
763 createTransactionIndex_ = 0;
764 currentTransactionFormState_ = emptyTransaction();
765 }
766 emit changed();
767}
768
770 if (!commands_ || !selection_) {
771 return;
772 }
773 if (isCreateMode()) {
774 const QVariantMap state = insertFormStateAfterCurrent(
775 createTransactionStates_, createTransactionIndex_, emptyTransaction());
776 createTransactionStates_ = state.value(QStringLiteral("states")).toList();
777 createTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
778 syncCurrentCreateTransaction();
779 emit changed();
780 return;
781 }
782
783 if (selectedStatementId().isEmpty()) {
784 return;
785 }
786 const QVariantMap state = editTransactionState();
787 editTransactionOrderIds_ = state.value(QStringLiteral("orderIds")).toList();
788 const QVariantList rows = state.value(QStringLiteral("rows")).toList();
789 int insertAfterIndex =
790 state.value(QStringLiteral("index"), editTransactionIndex_).toInt();
791 if (insertAfterIndex < 0 || insertAfterIndex >= rows.size()) {
792 insertAfterIndex = rows.size() - 1;
793 }
794 const QVariantMap current = normalizeTransaction(editTransactionData_);
795 const QString newId = commands_->insertTransactionAfter(
796 current.value(QStringLiteral("id")).toString(), QString(),
797 current.value(QStringLiteral("bookingDate")).toString(),
798 current.value(QStringLiteral("valuta")).toString(), 0.0,
799 selectedStatementId(), 0, QString(), QString(), false, QStringList());
800 if (newId.isEmpty()) {
801 return;
802 }
803 const QVariantList updatedRows =
804 statementTransactionRows(selectedStatementId());
805 const QVariantList updatedIds = rowIds(updatedRows);
806 editTransactionOrderIds_ = orderWithInsertedId(
807 editTransactionOrderIds_, updatedIds, newId, insertAfterIndex);
808 selection_->selectTransaction(selectedStatementId(), newId);
809 syncEditState();
810}
811
813 if (!commands_ || !selection_) {
814 return;
815 }
816 if (isCreateMode()) {
817 const QVariantMap state = removeFormStateAt(
818 createTransactionStates_, createTransactionIndex_, emptyTransaction());
819 createTransactionStates_ = state.value(QStringLiteral("states")).toList();
820 createTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
821 syncCurrentCreateTransaction();
822 emit changed();
823 return;
824 }
825 const QString deletedId =
826 editTransactionData_.value(QStringLiteral("id")).toString();
827 if (deletedId.isEmpty() || !hasMultipleTransactions()) {
828 return;
829 }
830 const QVariantMap selectionState = editTransactionState();
831 editTransactionOrderIds_ =
832 selectionState.value(QStringLiteral("orderIds")).toList();
833 commands_->deleteTransaction(deletedId);
834 const QVariantMap reselectionState = deleteReselectionState(
835 statementTransactionRows(selectedStatementId()), editTransactionOrderIds_,
836 editTransactionIndex_, deletedId, QStringLiteral("id"));
837 editTransactionOrderIds_ =
838 reselectionState.value(QStringLiteral("orderIds")).toList();
839 editTransactionIndex_ =
840 reselectionState.value(QStringLiteral("index")).toInt();
841 const QString txId = reselectionState.value(QStringLiteral("id")).toString();
842 selection_->selectTransaction(selectedStatementId(), txId);
843 rememberSelectedTransaction();
844 editTransactionData_ = transactionById(txId);
845 emit changed();
846}
847
848void BookingViewModel::rememberSelectedTransaction() {
849 if (!selection_ || selectedStatementId().isEmpty() ||
850 selectedTransactionId().isEmpty()) {
851 return;
852 }
853 lastTransactionIdByStatementId_.insert(selectedStatementId(),
855}
856
857QString
858BookingViewModel::transactionIdForStatement(const QString& statementId) const {
859 return selectors_
860 ? rememberedOrFirstRowId(statementTransactionRows(statementId),
861 lastTransactionIdByStatementId_,
862 statementId, QStringLiteral("id"))
863 : QString();
864}
865
866bool BookingViewModel::ensureSelectedTransactionForStatement() {
867 if (isCreateMode() || !selection_ || selectedStatementId().isEmpty()) {
868 return true;
869 }
870 const QString statementId = selectedStatementId();
871 const QString currentTxId = selectedTransactionId();
872 if (rowHasId(statementTransactionRows(statementId), currentTxId,
873 QStringLiteral("id"))) {
874 return true;
875 }
876 const QString nextTxId = transactionIdForStatement(statementId);
877 if (currentTxId == nextTxId) {
878 return true;
879 }
880 selection_->selectTransaction(statementId, nextTxId);
881 return false;
882}
883
884void BookingViewModel::syncEditState() {
885 if (isCreateMode()) {
886 return;
887 }
888 if (!ensureSelectedTransactionForStatement()) {
889 return;
890 }
891 editStatementName_ = rowById(statementRows(), selectedStatementId())
892 .value(QStringLiteral("name"))
893 .toString();
894 const QVariantMap state = editTransactionState();
895 const QVariantList rows = state.value(QStringLiteral("rows")).toList();
896 editTransactionOrderIds_ = state.value(QStringLiteral("orderIds")).toList();
897 if (rows.isEmpty()) {
898 editTransactionIndex_ = -1;
899 editTransactionData_ = emptyTransaction();
900 emit changed();
901 return;
902 }
903 editTransactionIndex_ = state.value(QStringLiteral("index")).toInt();
904 const QString txId = state.value(QStringLiteral("id")).toString();
905 editTransactionData_ = transactionById(txId);
906 if (selection_)
907 selection_->selectTransaction(selectedStatementId(), txId);
908 rememberSelectedTransaction();
909 captureEditState();
910 emit changed();
911}
912
914 if (!selection_) {
915 return;
916 }
917 const QVariantList rows = statementRows();
918 if (rows.isEmpty()) {
919 return;
920 }
921 const QString currentId = isCreateMode() ? QString() : selectedStatementId();
922 const int currentIndex = indexOfId(rows, currentId);
923 if (currentIndex == 0) {
924 rememberSelectedTransaction();
925 selection_->selectTransaction(QString(), QString());
926 editTransactionOrderIds_.clear();
927 editTransactionIndex_ = -1;
928 emit changed();
929 return;
930 }
931 const int nextIndex = currentIndex < 0 ? rows.size() - 1 : currentIndex - 1;
932 const QString nextId =
933 rows.value(nextIndex).toMap().value(QStringLiteral("id")).toString();
934 rememberSelectedTransaction();
935 selection_->selectTransaction(nextId, transactionIdForStatement(nextId));
936 editTransactionOrderIds_.clear();
937 editTransactionIndex_ = 0;
938 emit changed();
939}
940
942 if (!selection_) {
943 return;
944 }
945 const QVariantList rows = statementRows();
946 if (rows.isEmpty()) {
947 return;
948 }
949 const QString currentId = isCreateMode() ? QString() : selectedStatementId();
950 const int currentIndex = indexOfId(rows, currentId);
951 if (currentIndex == rows.size() - 1) {
952 rememberSelectedTransaction();
953 selection_->selectTransaction(QString(), QString());
954 editTransactionOrderIds_.clear();
955 editTransactionIndex_ = -1;
956 emit changed();
957 return;
958 }
959 const int nextIndex = currentIndex < 0 ? 0 : currentIndex + 1;
960 const QString nextId =
961 rows.value(nextIndex).toMap().value(QStringLiteral("id")).toString();
962 rememberSelectedTransaction();
963 selection_->selectTransaction(nextId, transactionIdForStatement(nextId));
964 editTransactionOrderIds_.clear();
965 editTransactionIndex_ = 0;
966 emit changed();
967}
968
970 if (!selection_) {
971 return;
972 }
973 if (isCreateMode()) {
974 if (createTransactionStates_.isEmpty()) {
975 return;
976 }
977 createTransactionIndex_ = wrappedIndex(createTransactionIndex_ - 1,
978 createTransactionStates_.size());
979 syncCurrentCreateTransaction();
980 emit changed();
981 return;
982 }
983 const QVariantMap state = editTransactionState();
984 const QVariantList rows = state.value(QStringLiteral("rows")).toList();
985 editTransactionOrderIds_ = state.value(QStringLiteral("orderIds")).toList();
986 if (rows.isEmpty()) {
987 return;
988 }
989 const QVariantMap next =
990 navigateSelectionState(rows, state.value(QStringLiteral("index")).toInt(),
991 state.value(QStringLiteral("id")).toString(), -1,
992 0, QStringLiteral("id"));
993 editTransactionIndex_ = next.value(QStringLiteral("index")).toInt();
994 const QString txId = next.value(QStringLiteral("id")).toString();
995 editTransactionData_ = transactionById(txId);
996 selection_->selectTransaction(selectedStatementId(), txId);
997 rememberSelectedTransaction();
998 emit changed();
999}
1000
1002 if (!selection_) {
1003 return;
1004 }
1005 if (isCreateMode()) {
1006 if (createTransactionStates_.isEmpty()) {
1007 return;
1008 }
1009 createTransactionIndex_ = wrappedIndex(createTransactionIndex_ + 1,
1010 createTransactionStates_.size());
1011 syncCurrentCreateTransaction();
1012 emit changed();
1013 return;
1014 }
1015 const QVariantMap state = editTransactionState();
1016 const QVariantList rows = state.value(QStringLiteral("rows")).toList();
1017 editTransactionOrderIds_ = state.value(QStringLiteral("orderIds")).toList();
1018 if (rows.isEmpty()) {
1019 return;
1020 }
1021 const QVariantMap next = navigateSelectionState(
1022 rows, state.value(QStringLiteral("index")).toInt(),
1023 state.value(QStringLiteral("id")).toString(), 1, 0, QStringLiteral("id"));
1024 editTransactionIndex_ = next.value(QStringLiteral("index")).toInt();
1025 const QString txId = next.value(QStringLiteral("id")).toString();
1026 editTransactionData_ = transactionById(txId);
1027 selection_->selectTransaction(selectedStatementId(), txId);
1028 rememberSelectedTransaction();
1029 emit changed();
1030}
1031
1033 return !createStatementName_.trimmed().isEmpty();
1034}
1035
1037 if (isCreateMode() || !selection_) {
1038 return false;
1039 }
1040 return bookingEditStateChanged(savedEditStatementName_,
1041 savedEditTransactionState_, editStatementName_,
1042 editTransactionData_);
1043}
1044
1046 if (!commands_ || !selectors_ || !selection_ || !canCreate()) {
1047 return QString();
1048 }
1050 "BookingViewModel::submit", "Booking create submitted",
1051 {{observability::context::kName, createStatementName_.toStdString()},
1052 {"transactionCount", std::to_string(createTransactionStates_.size())}});
1053 QVariantList transactionStates;
1054 for (const QVariant& stateValue : createTransactionStates_) {
1055 const QVariantMap rawTx = stateValue.toMap();
1056 const QVariantMap tx = normalizeTransaction(rawTx);
1057 if (!transactionFormStateHasContent(tx)) {
1058 continue;
1059 }
1060 transactionStates.push_back(rawTx);
1061 }
1062 const QString statementId = commands_->addStatementWithTransactions(
1063 createStatementName_, transactionStates);
1064 if (statementId.isEmpty()) {
1065 return QString();
1066 }
1068 selection_->selectTransaction(statementId,
1069 transactionIdForStatement(statementId));
1070 return statementId;
1071}
1072
1074 if (isCreateMode() || !commands_ || !selectors_ || !selection_ ||
1075 selectedStatementId().isEmpty()) {
1076 return;
1077 }
1079 "BookingViewModel::updateCurrent", "Booking update submitted",
1080 {{"statementId", selectedStatementId().toStdString()},
1081 {"transactionId", selectedTransactionId().toStdString()}});
1082 const QString statementId = selectedStatementId();
1083 const bool statementChanged = savedEditStatementName_ != editStatementName_;
1084 const QString statementName = editStatementName_;
1085 const QVariantMap transactionData = editTransactionData_;
1086 const QString txId = transactionData.value(QStringLiteral("id")).toString();
1087 const bool transactionChanged =
1088 savedEditTransactionState_ != normalizedTransactionState(transactionData);
1089 const QVariantMap normalizedTx = normalizeTransaction(transactionData);
1090 if (transactionChanged && !txId.isEmpty()) {
1091 commands_->updateTransaction(
1092 txId, transactionData.value(QStringLiteral("name")).toString(),
1093 transactionData.value(QStringLiteral("bookingDate")).toString(),
1094 transactionData.value(QStringLiteral("valuta")).toString(),
1095 transactionData.value(QStringLiteral("amount")), statementId,
1096 normalizedTx.value(payload::keys::common::kStatus).toInt(),
1097 transactionData.value(payload::keys::transaction::kActorId).toString(),
1098 transactionData.value(payload::keys::transaction::kContractId)
1099 .toString(),
1100 transactionData.value(QStringLiteral("allocatable")).toBool(),
1102 transactionData.value(payload::keys::transaction::kPropertyIds)
1103 .toList()));
1104 }
1105 if (statementChanged) {
1106 commands_->updateStatement(statementId, statementName);
1107 }
1108 syncEditState();
1109}
1110
1112 if (isCreateMode() || !commands_ || !selection_) {
1113 return;
1114 }
1116 "BookingViewModel::deleteCurrentStatement", "Statement delete submitted",
1117 {{"statementId", selectedStatementId().toStdString()}});
1118 const QString removedId = selectedStatementId();
1119 commands_->deleteStatement(removedId);
1120 const QString nextId = deleteNextSelectionId(statementRows(), removedId, 0,
1121 QStringLiteral("id"));
1122 selection_->selectTransaction(
1123 nextId, nextId.isEmpty() ? QString() : transactionIdForStatement(nextId));
1124 emit changed();
1125}
1126
1128 if (!selectors_) {
1129 return 0;
1130 }
1131 const QVariantMap tx = normalizeTransaction(transactionData());
1132 const int index =
1136 return index >= 0 ? index : 0;
1137}
1138
1140 const QVariantList options = transactionStatusOptions();
1141 const QVariantMap option = options.value(index).toMap();
1142 setTransactionField(payload::keys::common::kStatus,
1143 option.value(payload::keys::common::kValue,
1145}
1146
1148 return selectors_ ? selectors_->propertyRows() : QVariantList();
1149}
1150
1152 return normalizeTransaction(transactionData())
1154 .toList();
1155}
1156
1164
1166 return selectors_ ? selectors_->actorDropdownRows() : QVariantList();
1167}
1168
1170 return selectors_ ? selectors_->contractDropdownRows() : QVariantList();
1171}
1172
1173int BookingViewModel::selectedIndexFor(const QVariantList& rows,
1174 const QString& id) const {
1175 if (!selectors_) {
1176 return 0;
1177 }
1178 const int index = indexOfId(rows, id);
1179 return index >= 0 ? index : 0;
1180}
1181
1183 return selectedIndexFor(
1185 transactionField(payload::keys::transaction::kActorId).toString());
1186}
1187
1189 return selectedIndexFor(
1191 transactionField(payload::keys::transaction::kContractId).toString());
1192}
1193
1194void BookingViewModel::setPropertySelected(const QString& propertyId,
1195 bool selected) {
1196 if (!selectors_) {
1197 return;
1198 }
1199 QVariantList propertyIds =
1200 normalizeTransaction(transactionData())
1202 .toList();
1203 if (!setSelectedId(propertyIds, propertyId, selected)) {
1204 return;
1205 }
1206 applyTransactionFormChange(
1208}
1209
1211 const QVariantList rows = actorDisplayRows();
1212 const QString id =
1213 rows.value(index).toMap().value(payload::keys::common::kId).toString();
1214 applyTransactionFormChange({{payload::keys::transaction::kActorId, id}});
1215}
1216
1218 const QVariantList rows = contractDisplayRows();
1219 const QString id =
1220 rows.value(index).toMap().value(payload::keys::common::kId).toString();
1221 applyTransactionFormChange({{payload::keys::transaction::kContractId, id}});
1222}
1223
1224} // namespace ui
Declares the QML API for the Booking view.
Declarations for the UI payload mapper helpers.
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.
Q_INVOKABLE void setPropertySelected(const QString &propertyId, bool selected)
Updates whether a property is selected for the current transaction.
QVariantList selectedPropertyIds
void setTransactionAllocatable(bool value)
Updates the current transaction allocatable flag.
BookingViewModel(WorkspaceStore *store, WorkspaceCommands *commands, WorkspaceSelection *selection, WorkspaceSelectors *selectors, QObject *parent=nullptr)
Creates the booking view model backed by workspace roles.
Q_INVOKABLE QString submit()
Creates a statement and its transaction form states through workspace.
Q_INVOKABLE void updateCurrent()
Updates the selected statement and transaction through workspace.
QVariantList actorDisplayRows
Q_INVOKABLE void nextStatement()
Selects the next statement or the create form.
Q_INVOKABLE void addTransactionAfterCurrent()
Adds a transaction after the current transaction.
Q_INVOKABLE void previousTransaction()
Selects the previous transaction in the current statement.
Q_INVOKABLE void selectActorIndex(int index)
Selects an actor from the actor dropdown.
Q_INVOKABLE void deleteCurrentTransaction()
Deletes the current transaction when at least one remains.
void setTransactionName(const QString &value)
Updates the current transaction name field.
Q_INVOKABLE void selectStatement(const QString &statementId)
Selects a statement from the sidebar.
void setTransactionStatusIndex(int index)
Updates the current transaction status from an option index.
Q_INVOKABLE void resetCreateState()
Clears the create form and creates a single empty transaction state.
Q_INVOKABLE void selectContractIndex(int index)
Selects a contract from the contract dropdown.
void setTransactionAmountText(const QString &value)
Updates the current amount field.
Q_INVOKABLE void previousStatement()
Selects the previous statement or the create form.
void setTransactionBookingDate(const QString &value)
Updates the current transaction booking date field.
QVariantList transactionStatusOptions
QVariantList contractDisplayRows
Q_INVOKABLE void nextTransaction()
Selects the next transaction in the current statement.
Q_INVOKABLE void deleteCurrentStatement()
Deletes the selected statement through workspace.
void setStatementName(const QString &value)
Updates the statement name shown in the form.
void setTransactionValuta(const QString &value)
Updates the current transaction valuta field.
Q_INVOKABLE void selectTransaction(const QString &statementId, const QString &transactionId)
Selects a transaction from the sidebar.
Maps UI intent to core workspace writer commands.
Tracks the currently selected workspace entities.
void selectedStatementIdChanged()
Emitted when the selected statement id changed.
void selectedTransactionIdChanged()
Emitted when the selected transaction id changed.
Projects workspace store data into QML-facing read models.
QVariantList statementTransactionRows(const QString &statementId) const
Returns transactions belonging to a statement.
Owns the current UI-side workspace snapshot and revision lifecycle.
void dataRevisionChanged()
constexpr auto kName
Definition Trace.h:33
void traceViewModel(const char *origin, std::string message, core::errors::ErrorContext context={})
Emits a view model trace event.
Definition Trace.h:97
QVariantList toVariantList(const QVariant &value)
Converts a QVariant value to a QVariantList.
QString stringValue(const QVariantMap &map, const QString &key)
Reads a string value from a map.
QStringList toQStringList(const std::vector< std::string > &values)
Converts standard strings to a Qt string list.
QVariantList options(const QString &neutral, const QString &unverified, const QString &verified, const QString &completed)
Builds transaction status options.
QString verified()
Label for verified transaction status.
Definition Text.h:462
QString neutral()
Label for neutral transaction status.
Definition Text.h:454
QString unverified()
Label for unverified transaction status.
Definition Text.h:458
QString completed()
Label for completed transaction status.
Definition Text.h:466
Declares UI-side workspace command submission.