Qt account screen: raw reference analysis
Table of Contents
- 1. Standard CRUD Entity Detail Dialog — Exact Specification (account as reference)
- 2. Source key (paths relative to repo root)
- 3. DIALOG STRUCTURE
- 4. FORM FIELDS (account, in exact order)
- 4.1. General tab → group box
Account Information(QFormLayout name"formLayout"=) - 4.2. General tab → group box
Photo(ACC.ui:141-146) - 4.3. Security tab → group box
Password(QFormLayout name"formLayout_2"=) - 4.4. Login Status tab → group box
Login Status (Read-only)(QFormLayout name"formLayout_4"=) - 4.5. Validation (client-side)
- 4.1. General tab → group box
- 5. TABBED / GROUPED SECTIONS
- 6. CHILD ENTITY TABLES
- 7. SAVE FLOW
- 8. DELETE FLOW
- 9. AUDIT FIELDS
- 10. HISTORY DIALOG
- 11. VALIDATION AND ERROR PRESENTATION
- 12. WHAT VARIES PER ENTITY
- 12.1. 10.1 Model keys the implementer must supply (example:
MODEL:317-346) - 12.2. 10.2
*** Detail fieldstable columns (one row per field, in display order) - 12.3. 10.3 What is inherited / shared (never re-implemented per entity)
- 12.4. 10.4 Per-entity deltas the implementer actually writes
- 12.5. 10.5 Account-specific deviations from the generated standard (important for a faithful React port)
- 12.1. 10.1 Model keys the implementer must supply (example:
- 13. Quick reference — exact button/action labels
Note (historical). The Qt client that this analysis describes was removed in PR #2106. Nothing here describes code that still exists in the tree. Read it only for the behaviour it records, not as a description of the current codebase.
This is the working analysis behind entity_specification.org, kept because it
carries the exact file and line citations that the specification deliberately
leaves out. Read the specification for the rules; come here to see where a rule
came from, or to check one against the Qt source.
It was produced by reading the C++ checkout and is a description of what is there, defects included. The specification is the one that decides what to build.
1. Standard CRUD Entity Detail Dialog — Exact Specification (account as reference)
Source tree: /mnt/development/OreStudio/ores_dev_festive_dijkstra (read-only; nothing modified).
2. Source key (paths relative to repo root)
| Token | Path |
|---|---|
DDB.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/DetailDialogBase.hpp / projects/ores.qt/api/src/DetailDialogBase.cpp |
CRD.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/ChangeReasonDialog.hpp / .../src/ChangeReasonDialog.cpp |
CRC.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/ChangeReasonCache.hpp / .../src/ChangeReasonCache.cpp |
ACC.hpp/.cpp/.ui |
projects/ores.qt/iam/include/ores.qt/AccountDetailDialog.hpp / .../src/AccountDetailDialog.cpp / .../ui/AccountDetailDialog.ui |
ACH.hpp/.cpp/.ui |
projects/ores.qt/iam/{include/ores.qt/AccountHistoryDialog.hpp, src/AccountHistoryDialog.cpp, ui/AccountHistoryDialog.ui} |
HDB.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/HistoryDialogBase.hpp / .../src/HistoryDialogBase.cpp |
HD.hpp/.cpp/.ui |
projects/ores.qt/api/include/ores.qt/HistoryDialog.hpp / .../src/HistoryDialog.cpp / .../ui/HistoryDialog.ui |
CETW.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/ChildEntityTableWidget.hpp / .../src/ChildEntityTableWidget.cpp |
ACET.hpp/.cpp |
projects/ores.qt/iam/include/ores.qt/AccountChildEntityTables.hpp / .../src/AccountChildEntityTables.cpp |
PW.hpp/.cpp/.ui |
projects/ores.qt/api/{include/ores.qt/ProvenanceWidget.hpp, src/ProvenanceWidget.cpp, ui/ProvenanceWidget.ui} |
ACID.hpp/.cpp/.ui |
projects/ores.qt/iam/{include/ores.qt/AccountContactInformationDetailDialog.hpp, src/AccountContactInformationDetailDialog.cpp, ui/AccountContactInformationDetailDialog.ui} |
T-cpp |
projects/ores.codegen/library/templates/cpp_qt_detail_dialog.cpp.mustache |
T-hpp |
projects/ores.codegen/library/templates/cpp_qt_detail_dialog.hpp.mustache |
T-ui |
projects/ores.codegen/library/templates/qt_detail_dialog_ui.mustache |
MODEL |
projects/ores.iam/modeling/ores.iam.account_contact_information.org (example codegen-bound entity) |
CTRL |
projects/ores.qt/iam/src/AccountController.cpp |
MDIW |
projects/ores.qt/iam/src/AccountMdiWindow.cpp |
ACCT.hpp |
projects/ores.iam/api/include/ores.iam.api/domain/account.hpp |
ACCTP.hpp |
projects/ores.iam/api/include/ores.iam.api/messaging/account_protocol.hpp |
CRDOM.hpp |
projects/ores.dq/api/include/ores.dq.api/domain/change_reason.hpp |
CRCONST.hpp |
projects/ores.dq/api/include/ores.dq.api/domain/change_reason_constants.hpp |
MBH.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/MessageBoxHelper.hpp / .../src/MessageBoxHelper.cpp |
PMI.hpp/.cpp |
projects/ores.qt/api/include/ores.qt/PasswordMatchIndicator.hpp / .../src/PasswordMatchIndicator.cpp |
WU.hpp |
projects/ores.qt/api/include/ores.qt/WidgetUtils.hpp |
Base classes in the inheritance chain: ores::qt::DetailDialogBase : public QWidget (DDB.hpp:84); ores::qt::HistoryDialogBase : public QWidget (HDB.hpp:75); AccountDetailDialog final : public DetailDialogBase (ACC.hpp:58); AccountHistoryDialog final : public HistoryDialogBase (ACH.hpp:40). The generic HistoryDialog final : public HistoryDialogBase (HD.hpp:53). The dialog is not a QDialog; it is a QWidget hosted inside a DetachableMdiSubWindow (CTRL:326-328, 457-459). Child CRUD dialogs are additionally wrapped in a throwaway QDialog wrapper to get modal exec() (ACET.cpp:140-166, 203-225).
3. DIALOG STRUCTURE
3.1. 1.1 Root shell (account)
| Element | Exact value | Citation |
|---|---|---|
| Top-level widget | QWidget name"AccountDetailDialog"= |
ACC.ui:4 |
windowTitle |
Account Details |
ACC.ui:25-27 |
geometry |
width 600, height 700 | ACC.ui:5-12 |
minimumSize |
600 × 500 | ACC.ui:13-18 |
sizePolicy |
Preferred=/=Preferred, stretch 0/0 |
ACC.ui:19-24 |
| Root layout | QVBoxLayout name"verticalLayout"= |
ACC.ui:28 |
| Child 0 | QTabWidget name"tabWidget", =currentIndex=0 |
ACC.ui:30-31 |
| Child 1 | QHBoxLayout name"buttonLayout"= |
ACC.ui:389 |
Generated standard shell is identical (T-ui:3-31), except generated dialogs are 600×500 and window title {{entity_title}} Details (T-ui:10-11, 26-28).
3.2. 1.2 Runtime-inserted toolbar (hidden by default)
The dialog builds its own top QToolBar and inserts it as layout widget 0 (ACC.cpp:89-105):
toolBar_ = new QToolBar(this);
toolBar_->setMovable(false); toolBar_->setFloatable(false);
revertAction_ = new QAction("Revert to this version", this);
revertAction_->setToolTip("Revert account to this historical version");
mainLayout->insertWidget(0, toolBar_);
toolBar_->setVisible(false);
ACC.cpp:89-105. It becomes visible only in read-only/historical mode (ACC.cpp:1093). Generated dialogs with has_version_navigation additionally get First, Previous, Next, Last actions and a Revert action, all setVisible(false) until history mode (T-cpp:152-205).
3.3. 1.3 Button box
Order in the layout is: horizontal spacer → Delete → Close → Save (ACC.ui:390-423). Labels are exactly Delete, Close, Save; Save has default=true (ACC.ui:419-421). All three are QPushButton (ACC.ui:401,408,415). Icons are assigned at runtime:
- Save ←
Icon::Save(ACC.cpp:108-109) - Delete ←
Icon::Delete(ACC.cpp:111-112) - Close ←
Icon::Dismiss(ACC.cpp:113-114)
Signals: Save→=onSaveClicked=, Delete→=onDeleteClicked=, Close→=onCloseClicked= (ACC.cpp:115-117). The generated standard is byte-identical in structure (T-ui:221-261) and the same icon wiring (T-cpp:126-135; AccountContactInformation: ACID.cpp:88-98).
3.4. 1.4 Add mode vs edit mode
Mode is selected by isAddMode_ = account.username.empty() (ACC.cpp:262-266). setCreateMode() (ACC.cpp:324-360) applies:
| Aspect | Create (add) | Edit |
|---|---|---|
| Title / container | New Account (CTRL:461-462) |
Account: %1 (CTRL:464-466) |
usernameEdit |
editable (setReadOnly(false)) |
read-only (setReadOnly(true)) — ACC.cpp:328 |
accountTypeCombo |
enabled, index 0 = "User" (ACC.cpp:349-351) |
disabled (ACC.cpp:349) |
| Security tab (index 1) | enabled (ACC.cpp:336-337) |
disabled |
| Login Status tab (index 2) | disabled (ACC.cpp:338-339) |
enabled |
| Roles tab (index 3) | enabled (ACC.cpp:340-341) |
enabled |
| Parties tab (index 4) | enabled (pre-select allowed) (ACC.cpp:342-345) |
enabled |
| Provenance tab | disabled (setProvenanceEnabled(!createMode), ACC.cpp:346) |
enabled |
deleteButton |
disabled but visible (updateSaveResetButtonState sets setEnabled(!isAddMode_), ACC.cpp:1151; the generated template instead hides it: setVisible(!createMode), T-cpp:543) |
enabled |
| Password fields | cleared on setAccount; validated on save (ACC.cpp:292-294) |
hidden tab, no validation (ACC.cpp:494-496) |
| Default account type | user (ACC.cpp:351) |
from record (ACC.cpp:281-283) |
Hidden fields in all modes: the Admin row is invisible — isAdminCheckBox text Grant administrative privileges and labelIsAdmin text Admin: both carry visible=false in the .ui (ACC.ui:94-112) and are re-hidden in the constructor (ACC.cpp:138-140). Comment: "Admin row hidden — privileges managed via RBAC role assignments".
Read-only / historical mode (setReadOnly(readOnly, versionNumber), ACC.cpp:1081-1119): saveButton & deleteButton setVisible(!readOnly), toolbar setVisible(readOnly); Security tab disabled; roles/parties widgets setReadOnly(readOnly) and setVisible(versionNumber = 0)= (so hidden for a numbered historical version); child tables read-only; setFieldsReadOnly sets usernameEdit=/=emailEdit read-only and disables the type combo. Controller sets container title Account: %1 (Version %2 - Read Only) (CTRL:520-524). Note: fullNameEdit and jobTitleEdit are not put read-only by setFieldsReadOnly (ACC.cpp:1121-1129).
3.5. 1.5 Close behaviour
onCloseClicked() (DDB.cpp:121-132):
if (hasUnsavedChanges()) {
MessageBoxHelper::question(this, "Unsaved Changes",
"You have unsaved changes. Close anyway?", Yes|No);
if (reply != Yes) return;
}
closeConfirmed_ = true; requestClose();
hasUnsavedChanges() for account returns isDirty_ (ACC.hpp:159-161). requestClose() emits closeRequested (DDB.hpp:196-198), which the controller connects to the sub-window's close slot (connect_dialog_close, CTRL:475).
4. FORM FIELDS (account, in exact order)
The dialog has no date pickers, spin boxes, or free-text areas among entity fields (the only QPlainTextEdit is the read-only provenance commentary). Every editable field is a QLineEdit or QComboBox.
4.1. General tab → group box Account Information (QFormLayout name"formLayout"=)
| Row | Label text (exact) | Widget | Control type | Placeholder | Edit-mode state |
|---|---|---|---|---|---|
| 0 | Username: |
usernameEdit |
QLineEdit | Enter username |
read-only (ACC.cpp:328) |
| 1 | Full Name: |
fullNameEdit |
QLineEdit | Enter full name |
editable |
| 2 | Email: |
emailEdit |
QLineEdit | Enter email address |
editable (but set read-only in historical mode, ACC.cpp:1123) |
| 3 | Type: |
accountTypeCombo |
QComboBox | — | disabled (ACC.cpp:349) |
| 4 | Admin: |
isAdminCheckBox text Grant administrative privileges |
QCheckBox | — | hidden in all modes (ACC.ui:94-112, ACC.cpp:138-140) |
| 5 | Job Title: |
jobTitleEdit |
QLineEdit | e.g. Head of Desk, Senior Trader |
editable |
| 6 | Reports To: |
reportsToCombo |
QComboBox | — | editable |
Citations: ACC.ui:36-137 (Account Information group and rows); ACC.cpp:143-146 (type combo values/labels).
accountTypeCombo items: label / data = User=/=user, Service=/=service, Algorithm=/=algorithm, LLM=/=llm (ACC.cpp:143-146). Changing it also pushes the type to the parties widget (ACC.cpp:151-155).
reportsToCombo is populated asynchronously from get_accounts_request (limit 10 000), first item (none) with empty data, display name = full_name else username; self excluded by username; value = account UUID string (ACC.cpp:374-422). It is not repopulated per-account in setAccount (arg is ignored) and defaults to selection 0.
4.2. General tab → group box Photo (ACC.ui:141-146)
Title Photo, empty QVBoxLayout name"photoGroupLayout". At runtime =initFlagButton(ui_->photoGroup->layout()) inserts a centred 52×52 flat QPushButton (icon 48×48, pointing-hand cursor, tooltip Click to select flag) (ACC.cpp:72, DDB.cpp:150-173). This edits the account's image_id profile picture. Pending state restyles the button border orange and tooltip Flag changed (unsaved) (DDB.cpp:40-46, 212-214). Entities with no image never call this.
4.3. Security tab → group box Password (QFormLayout name"formLayout_2"=)
| Row | Label text | Widget | Type | Placeholder |
|---|---|---|---|---|
| 0 | Password: |
passwordEdit |
QLineEdit, echoMode=Password |
Enter password |
| 1 | Confirm Password: |
confirmPasswordEdit |
QLineEdit, echoMode=Password |
Confirm password |
| 2 (col 1) | — | showPasswordCheck text Show passwords |
QCheckBox | — |
Citations: ACC.ui:166-214. showPasswordCheck toggles echo mode of both fields between QLineEdit::Normal and QLineEdit::Password (ACC.cpp:132-136). Inline match help: PasswordMatchIndicator::connectFields(passwordEdit, confirmPasswordEdit) paints the confirm field border green #4CAF50 on match, red #cc0000 on mismatch, default when empty (ACC.cpp:129, PMI.hpp:37-60).
4.4. Login Status tab → group box Login Status (Read-only) (QFormLayout name"formLayout_4"=)
| Row | Label | Widget | Type |
|---|---|---|---|
| 0 | Online: |
onlineBadge |
QLabel (badge) |
| 1 | Locked: |
lockedBadge |
QLabel (badge) |
| 2 | Failed Logins: |
failedLoginsEdit |
QLineEdit readOnly=true |
| 3 | Last Login: |
lastLoginEdit |
QLineEdit readOnly=true |
| 4 | Last IP: |
lastIpEdit |
QLineEdit readOnly=true |
| 5 | Last Attempt IP: |
lastAttemptIpEdit |
QLineEdit readOnly=true |
Citations: ACC.ui:231-347. Populated by setLoginInfo (ACC.cpp:447-487): badges via BadgeLabelUtils::apply(..., "account_online", ...) and "account_locked"; last login Qt::ISODate or the literal Never when epoch ≤ 0; missing info shows N/A in all five. IPs from li.last_ip.to_string().
4.5. Validation (client-side)
usernameEditmust be non-empty after trim, else warningValidation Error/Username is required.(ACC.cpp:535-539).- Create mode only password rules (
ACC.cpp:542-566): non-empty →Password is required for new accounts.; equality →Passwords do not match.; length ≥ 12 →Password must be at least 12 characters long.(validatePassword()helper:ACC.cpp:493-515). - No email-format, no full-name, no job-title validation.
- Submit gating: Save is enabled only when something is dirty (
updateSaveResetButtonState,ACC.cpp:1131-1152).
5. TABBED / GROUPED SECTIONS
Tab order as built (.ui order, then runtime insertions):
| # | Tab title | Container | Contents | Editable in place |
|---|---|---|---|---|
| 0 | General |
QWidget name"generalTab"= |
Group Account Information (7 rows above) + Group Photo (flag button) + vertical spacer |
yes (per field rules) |
| 1 | Security |
securityTab |
Group Password (2 fields + show toggle) + spacer |
create mode only |
| 2 | Login Status |
loginStatusTab |
Group Login Status (Read-only) (2 badges + 4 read-only edits) + spacer |
never (read-only) |
| 3 | Roles |
rolesTab |
placeholder QLabel text Role assignments are managed via the Roles widget. centred; replaced at runtime by AccountRolesWidget |
yes |
| 4 | Parties |
partiesTab |
placeholder Party assignments are managed via the Parties widget.; replaced by AccountPartiesWidget |
yes |
| 5 | Contact Information |
dynamically inserted ChildEntityTableWidget |
child-entity table, inserted before the last tab | yes (Add/Delete/double-click edit) |
| 6 | Provenance |
provenanceTab |
embedded ores::qt::ProvenanceWidget |
never (read-only) |
Citations: tabs ACC.ui:32-385; placeholders ACC.ui:352-359, 367-374; replacement logic ACC.cpp:157-193; Contact Information insertion ACC.cpp:214-218 and ACET.cpp:61-65 (insert at count()-1 so Provenance stays last); Provenance widget ACC.ui:378-385.
Group headings are exact: Account Information, Photo, Password, Login Status (Read-only) plus the ProvenanceWidget's own group Record Provenance (PW.ui:15-18) whose six read-only rows are Version:, Modified By:, Performed By:, Recorded At:, Change Reason:, Commentary: (PW.ui:21-115; versionEdit, modifiedByEdit, performedByEdit, recordedAtEdit, changeReasonEdit are QLineEdit readOnly=true, commentaryEdit is QPlainTextEdit readOnly=true, 80–150 px tall).
5.1. Roles widget (AccountRolesWidget)
QGroupBox titled dynamically Assigned Roles (%1) where %1 is the effective count (AccountRolesWidget.cpp:41, 315), a QListWidget of assigned roles (single selection, alternating rows), and a row QComboBox roleCombo_ + two QToolButton=s: Add (tooltip =Assign selected role) and Delete/revoke (tooltip Revoke selected role) (AccountRolesWidget.cpp:42-88). Add/revoke only stage changes locally; un-staging is symmetric (clicking Add on a staged removal removes the removal) (AccountRolesWidget.cpp:209-254). load() fetches assigned roles + all roles asynchronously (AccountRolesWidget.cpp:110-202). Read-only disables both buttons (AccountRolesWidget.cpp:204-207, 320-327). Item tooltip = role description; combo lists names not already effective.
5.2. Parties widget (AccountPartiesWidget)
QGroupBox titled dynamically Assigned Parties (%1) (AccountPartiesWidget.cpp:44, 451-452), a QListWidget of assigned parties (single selection, alternating rows), a QComboBox partyCombo_ + Add (tooltip Add selected party) and Remove (tooltip Remove selected party) QToolButton=s, and below them a =Default Party: QLabel + defaultPartyCombo_ (AccountPartiesWidget.cpp:42-111). Party list items render "{full_name} ({party_type})"; the default party is prefixed ★ =, suffixed = — Default, indigo bold (#7C6CE8), with tooltip This is the account's default quick-login party. (AccountPartiesWidget.cpp:352-391). The default combo has item (none) + only already-persisted assigned parties, and its tooltip explains quick-login (AccountPartiesWidget.cpp:100-103, 457-492). Add/remove stage locally; the parent commits on Save (AccountPartiesWidget.cpp:289-346). addRequested for a tenant-admin account is unusual: confirmation Administrator Account / This is an administrator account. Assigning parties to administrator accounts is not typical and may not be appropriate.\n\nContinue? (AccountPartiesWidget.cpp:293-303). Read-only disables the default combo and buttons (AccountPartiesWidget.cpp:283-287, 494-501).
Both widgets emit statusMessage, errorMessage, roleListChanged=/=partyListChanged, and parties additionally dataLoaded=/=defaultPartyChanged; the parent wires these to its own signals and to updateSaveResetButtonState() (ACC.cpp:171-212).
6. CHILD ENTITY TABLES
6.1. 4.1 Generic widget — ChildEntityTableWidget
Entity-agnostic; it knows nothing about any service/domain (CETW.hpp:34-43). Constructor takes (columnHeaders, addLabel, deleteLabel, parent) (CETW.hpp:53-56). It builds:
QVBoxLayoutcontaining aQToolBarand aQTableWidget(CETW.cpp:34-57).- Toolbar icon size 16×16; Add action icon
Icon::Addlabel/tooltip =addLabel; Delete action iconIcon::Deletelabel/tooltip =deleteLabel(CETW.cpp:38-42). addRequested()emitted on Add;deleteRequested(int row)emitted on Delete with the table'scurrentRow()(CETW.cpp:43-46).- Table: column count/labels from
columnHeaders,horizontalHeader()->setStretchLastSection(true), selection behaviorSelectRows, selection modeSingleSelection, edit triggersNoEditTriggers, vertical header hidden (CETW.cpp:48-54). - API:
setRows(const std::vector<QStringList>&)replaces all rows andresizeColumnsToContents()(CETW.cpp:62-70);currentRow();table()for finer control (CETW.hpp:60-66).
There is no inline editing: the only edit path is a double-click, wired by the host (ACET.cpp:55-58).
6.2. 4.2 Account host — AccountChildEntityTables
A QObject that owns exactly one ChildEntityTableWidget for account contact information (ACET.hpp:39-48), constructed as:
new ChildEntityTableWidget({"Email", "Country", "Street", "City", "Phone"},
"Add Contact", "Delete Contact", dialogParent)
ACET.cpp:39-45. This yields a table with columns Email, Country, Street, City, Phone and toolbar tooltips Add Contact / Delete Contact.
attachTo(QTabWidget*)inserts it before the last tab under titleContact Information(ACET.cpp:61-65).reload(accountId, clientManager, username, imageCache, changeReasonCache)fans outget_account_contact_informations_by_account_id_request(limit 1000) and maps each record to the 5 cellsemail, country_code, street_line_1, city, phone(ACET.cpp:67-123). No-op if the id is nil, no client manager, or disconnected (ACET.cpp:77-79).setReadOnly(bool)sets a flag consulted by every mutating path (ACET.cpp:82-84).- Add: guarded by read-only; if the parent id is nil it warns
Save Required/Save the account first, then add contact information.; if disconnected warnsDisconnected/Cannot add contact while disconnected from server.(ACET.cpp:125-138). It opens a modalQDialogwrapper titledAdd Contact, hosts anAccountContactInformationDetailDialog, injects client manager / change-reason cache / image cache / username, callssetCreateMode(true), assigns a random UUID and the parentaccount_id, and reloads onQDialog::Accepted(ACET.cpp:140-166). - Delete: guarded; deletes by id via
delete_account_contact_information_request; on failure warnsDelete Failed; on success reloads (ACET.cpp:168-196). Note it does not prompt a change reason for the child delete. - Edit: double-click opens the same dialog wrapped in a
QDialogtitledContact Information Details,setCreateMode(false),setReadOnly(readOnly_ || !connected), and reloads on accept (ACET.cpp:198-225).
6.3. 4.3 Other per-entity hosts (pattern confirmation)
CounterpartyChildEntityTables and PartyChildEntityTables exist under projects/ores.qt/refdata/{include,src} and mirror this pattern (ACET.hpp:39-47 explicitly cites PartyChildEntityTables as the rationale). The account variant is "scoped to account's one composite child — accounts have no identifiers entity" (ACET.hpp:42-47).
6.4. 4.4 Generated/composite-child seam
The generated detail dialog reserves a seam for hosting such tables (and hierarchy trees) via model-declared :implements blocks; the generated .cpp constructor carries the comment:
// Composite child-entity tables seam: an :implements // 7E4A2C8D-9F1B-4E6A-8D3C-5B2A7E9F1C4D block constructs one QTableWidget // + QToolBar per embedded child entity ... wraps each in a tab, and inserts it // into this dialog's tab widget.
ACID.cpp:56-65 (same text in T-cpp:97-103).
7. SAVE FLOW
Entry point onSaveClicked() (ACC.cpp:517-964); save() is a public thin wrapper (ACC.cpp:489-491).
7.1. 5.0 Pre-flight (both modes)
- Connection guard: if
!clientManager_ || !clientManager_->isConnected()→ log warn,emit errorMessage("Not connected to server. Please login."), return (ACC.cpp:518-522). - Party-readiness guard: if
partiesWidget_ && !partiesWidget_->isDefaultPartyReady()→emit errorMessage("Still loading account data — please try again in a moment."), return (ACC.cpp:524-532). (Save is normally disabled in this window —ACC.cpp:1149-1150.) - Username required: trimmed empty →
MessageBoxHelper::warning(this, "Validation Error", "Username is required."), return (ACC.cpp:534-539). - Password rules (create mode only): the three checks/messages listed in §2 (
ACC.cpp:541-566).
7.2. 5.1 Create branch (isAddMode_ = true=) — ACC.cpp:570-724
- Party warning: if no pending party change and
!partiesWidget_->hasAvailableParties(), warnNo Parties Available/"No parties are defined in the system. The account will be created without a party assignment and will not be able to log in until a party is assigned."(non-blocking,ACC.cpp:573-581). Otherwise askNo Party Assigned/"No party has been assigned to this account.\n\nAccounts without a party assignment cannot log in to any party context.\n\nCreate the account without a party?"with Yes|No; No aborts (ACC.cpp:583-592). - Capture staged child adds:
partiesWidget_->pendingAdds(),rolesWidget_->pendingAdds()(ACC.cpp:595-601). - Copy form values to locals: username, password, email, account_type (
ACC.cpp:603-608). - Async
QtConcurrent::run(ACC.cpp:610-677):- Build
iam::messaging::save_account_request { principal=username, password, email, account_type, totp_secret"" }= (ACC.cpp:618-623; fields atACCTP.hpp:45-53). No change-reason fields exist on this request and none are prompted in create mode. process_authenticated_request; transport failure →{false, "Failed to communicate with server"};!success→{false, response.message}(ACC.cpp:625-634).- Parse
response.account_idUUID (ACC.cpp:636-638). - Commit each staged party via
save_account_party_request; failures produce"Account created but party assignment failed: " + …(ACC.cpp:640-657). - Commit each staged role via
assign_role_request; failures produce"Account created but role assignment failed: " + …(ACC.cpp:659-674). - Success →
{true, account_id_str}(ACC.cpp:676).
- Build
- Watcher (
QFutureWatcher<std::pair<bool,std::string>>,ACC.cpp:679-724):- Success:
isDirty_=false, emitisDirtyChanged(false), refresh buttons; parse UUID andemit accountCreated(account_id); reload parties/roles widgets;notifySaveSuccess(tr("Account '%1' created").arg(username))(ACC.cpp:690-714). - Failure: log error,
emit errorMessage("Failed to create account: %1"),MessageBoxHelper::critical(self, "Create Failed", message)(ACC.cpp:715-721).
- Success:
7.3. 5.2 Edit branch — ACC.cpp:725-963
- Compute what needs saving:
needsPartySave = partiesWidget_->hasPendingChanges(),needsRoleSave = rolesWidget_->hasPendingChanges(),needsAccountSave = isDirty_ || defaultBeingRemoved || partiesWidget_->hasPendingDefaultPartyChange()(ACC.cpp:728-764). (Default-party selection is always re-sent as part of the account update.) Change-reason prompt — only when party or role assignments changed:
if (needsPartySave || needsRoleSave) { const auto crSel = promptChangeReason( ChangeReasonDialog::OperationType::Amend, true, "common"); if (!crSel) return; // user cancelled changeReasonCode = crSel->reason_code; changeCommentary = crSel->commentary; }ACC.cpp:766-775. A pure account-field edit (email/full name/job title/reports-to) triggers no prompt and leaves the request'schange_reason_code=/=change_commentaryempty, even thoughupdate_account_requestcarries those fields (ACCTP.hpp:85-86).- Capture account_id, email, full name, job title, reports-to id, image id (pending flag if changed), pending role adds/removes, pending party adds/removes, change reason (
ACC.cpp:777-806). - Async
QtConcurrent::run(ACC.cpp:792-899):- If
needsAccountSave: buildupdate_account_request { account_id, email, full_name, default_party_id, job_title, reports_to_account_id, image_id }—change_reason_code=/=change_commentaryare not populated — and send; transport failure →"Failed to communicate with server";!success→response.message(ACC.cpp:810-831). - Commit party additions (
save_account_party_request, withchange_reason_code=/=change_commentary=/=modified_by""= set on the child and comment "server overwrites from session",ACC.cpp:833-851). - Commit party removals (
delete_account_party_requestby key,ACC.cpp:853-868). - Commit role additions (
assign_role_request,ACC.cpp:870-882) and removals (revoke_role_request,ACC.cpp:884-896). - Success →
{true, {}}(ACC.cpp:898).
- If
- Watcher (
ACC.cpp:901-960):- Success:
isDirty_=false;isDirtyChanged(false); if flag changed commit the staged image andresetFlagChanged(); rebasecurrentAccount_.default_party_idto the value actually sent and callpartiesWidget_->rebaseDefaultParty(finalDefaultPartyId); reload parties/roles if they changed; update buttons;emit accountUpdated(account_id);notifySaveSuccess(tr("Account updated"))(ACC.cpp:912-952). - Failure:
emit errorMessage("Failed to save: %1");MessageBoxHelper::critical(self, "Save Failed", message)(ACC.cpp:953-958).
- Success:
7.4. 5.3 Signals emitted and dialog fate
- Success signals:
accountCreated(uuid)/accountUpdated(uuid)/accountDeleted(uuid)(ACC.hpp:144-146), plusisDirtyChanged(bool)(ACC.hpp:147). notifySaveSuccess(message)emitsstatusMessage(message)thenrequestClose()(DDB.hpp:203-206) — so a successful save emits the status message and closes the dialog (the MDA sub-window isWA_DeleteOnClose,CTRL:458).- Failure:
errorMessage(QString)signal (DDB.hpp:169) + a modal critical box; dialog stays open. - Controller reacts to all three entity signals by marking the list window stale (
CTRL:430-454).
7.5. 5.4 The generic (codegen-bound) save flow
For comparison, the generated standard always prompts, including create, and writes the reason onto the entity before saving:
const auto crOpType = createMode_ ? Create : Amend; const auto crSel = promptChangeReason(crOpType, hasChanges_, createMode_ ? "system" : "common"); if (!crSel) return; entity_.change_reason_code = crSel->reason_code; entity_.change_commentary = crSel->commentary; ... save_<entity>_request request; request.data = entity_;
T-cpp:1356-1396; concrete instance in ACID.cpp:333-341, 358-361. Success emits <item>Saved(code) and notifySaveSuccess(tr("{{entity_title}} '%1' saved").arg(code)) (T-cpp:1404-1414; ACID.cpp:378-384).
8. DELETE FLOW
Account delete (onDeleteClicked, ACC.cpp:971-1049):
- Connection guard →
emit errorMessage("Not connected to server. Please login."), return (ACC.cpp:972-976). - If
isAddMode_→ log and return silently (ACC.cpp:978-981). Confirmation:
MessageBoxHelper::question(this, "Delete Account", QString("Are you sure you want to delete account '%1'?") .arg(QString::fromStdString(currentAccount_.username)), QMessageBox::Yes | QMessageBox::No);Yes|No only; anything but Yes returns (
ACC.cpp:985-995).- No change-reason prompt for account delete. (Contrast: the generated standard prompts
OperationType::Delete, false, "common"before the async delete —T-cpp:1446-1448,ACID.cpp:417-420. AndChangeReasonDialog's Delete title/button are specificallyDeletion Reason Required/Confirm Delete,CRD.cpp:70, 166.) - Async
delete_account_request { account_id }(ACC.cpp:997-1018; request shapeACCTP.hpp:100-104). Transport failure →"Failed to communicate with server". - Watcher (
ACC.cpp:1020-1048):- Success:
emit statusMessage("Successfully deleted account: %1"),emit accountDeleted(account_id), thenself->requestClose()(ACC.cpp:1032-1039). - Failure:
emit errorMessage("Failed to delete account: %1")+MessageBoxHelper::critical(self, "Delete Failed", message)(ACC.cpp:1040-1045).
- Success:
Generic template equivalent wording: title Delete {{entity_title}}, body Are you sure you want to delete {{entity_singular_words}} '%1'?, success {{entity_title}} '%1' deleted + entity-deleted signal + requestClose(), failure Delete Failed (T-cpp:1438-1440, 1518-1528; ACID.cpp:407-411, 455-465).
9. AUDIT FIELDS
9.1. 7.1 Domain audit fields (every versioned entity)
account carries (ACCT.hpp):
| Field | Type | Meaning | Citation |
|---|---|---|---|
version |
int | optimistic-locking / change-tracking version | ACCT.hpp:36-39 |
modified_by |
string | username who recorded this version | ACCT.hpp:51-54 |
change_reason_code |
string | soft FK to change_reasons | ACCT.hpp:56-61 |
change_commentary |
string | free-text explanation | ACCT.hpp:63-66 |
performed_by |
string | username of the account that performed the operation | ACCT.hpp:68-71 |
recorded_at |
time_point | timestamp recorded | ACCT.hpp:163-166 |
9.2. 7.2 What the user supplies
- Change reason code: chosen from a filtered, sorted list in
ChangeReasonDialog; mandatory in the sense that the dialog cannot be accepted without a selection, but it can be cancelled (in which case the whole save/delete aborts). - Commentary: mandatory only when the selected reason has
requires_commentary =true=; otherwise optional (CRD.cpp:182-228). modified_by: never user-editable. For account it is injected asmodifiedByUsername_.empty() ? "qt_user" : modifiedByUsername_ingetAccount()(ACC.cpp:369);setUsername()sets it from the controller (ACC.hpp:75,CTRL:398). For generated entities it is set inupdate<X>FromUi()(ACID.cpp:294).performed_by,recorded_at,versionare server-assigned and only displayed.
9.3. 7.3 Where reasons come from
ChangeReasonCacheis a shared, event-refreshed cache created once and injected viasetChangeReasonCache()(DDB.hpp:118-127;CTRL:399, 489). The controller receiveschangeReasonCache_=/=badgeCache_in its constructor and callssetImageCache(imageCache)(CTRL:55-62).- It loads all reasons + categories via
loadAll(), listens forchange_reason_changedevents, and exposesgetReasonsForNew/Amend/Delete(category_code)filtering byapplies_to_new/amend/deleteandcategory_code, sorted ascending bydisplay_order(CRC.hpp:99-117,ChangeReasonCache.cpp:240-290), plusgetReasonByCode,isValidReasonCode,allReasons,allCategories, and signalsloaded=/=loadError=/=refreshed(CRC.hpp:142-156). - Categories in use:
system(create),common(amend/delete) — constants"system"/"common"(CRCONST.hpp:83, 88). - Reason codes:
system.new_record,system.external_data_import,common.non_material_update,system.new(synthetic),system.test(synthetic) (CRCONST.hpp:36-61).common.non_material_updateis the special "touch" reason: allowed only when no field changed; every other reason is allowed only when a field changed (create ignores the toggle) (CRD.cpp:96-124,CRD.hpp:69-73).
9.4. 7.4 Failure to obtain a reason
promptChangeReason (DDB.cpp:88-119) returns std::nullopt and emits errorMessage with exact text:
- cache unset/not loaded →
"Change reasons not loaded. Please try again."(DDB.cpp:90-93); - no reasons for the category →
"No change reasons available. Please contact administrator."(DDB.cpp:109-112); - user cancel → silent
nullopt(DDB.cpp:115-116).
Its signature: promptChangeReason(OperationType opType, bool isDirty, std::string_view category = "system") returning std::optional<change_reason_selection{reason_code, commentary}> (DDB.hpp:269-302).
9.5. 7.5 Presentation — ChangeReasonDialog exact anatomy
| Aspect | Create | Amend | Delete |
|---|---|---|---|
| Title | New Record Reason |
Change Reason Required |
Deletion Reason Required |
| Header label | Please select a reason for creating this record: |
Please select a reason for this change: |
Please select a reason for this deletion: |
| Save-button text | Create |
Save |
Confirm Delete |
Citations: CRD.cpp:64-85, 159-167. Minimum width 450 (CRD.cpp:73). Form row label Reason: with a QComboBox (min width 300) whose display text and data are both reason.code (CRD.cpp:93-125). Below it a grey italic description label showing reason.description with stylesheet QLabel { color: #888888; font-style: italic; padding: 5px; } (CRD.cpp:129-134, 189-190). A group box titled Commentary contains a description label that flips between Commentary is required for this reason. and Commentary is optional for this reason., a QTextEdit (max height 100) with placeholder Enter explanation for this change..., and a required marker * Required (CRD.cpp:136-153, 192-200). Required marker colour is #cc6666 when empty, #66cc66 once satisfied (CRD.cpp:150, 220-228). Button box is QDialogButtonBox::Save | Cancel; Save is disabled while required commentary is empty, and validateAndAccept refuses accept() as a safety net (CRD.cpp:159-179, 209-242). Accessors: selectedReasonCode() → reasons_[index].code; commentary() → trimmed plain text (CRD.cpp:244-254).
9.6. 7.6 Provenance display (read-only audit panel)
DetailDialogBase::populateProvenance(version, modified_by, performed_by, recorded_at, change_reason_code, change_commentary) forwards to ProvenanceWidget::populate, which writes the six fields (DDB.cpp:68-79, PW.cpp:38-57). recorded_at is rendered through relative_time_helper::format() and left blank when the time_point is default-constructed (PW.cpp:48-53). The tab is disabled in create mode and for entities with no such tab (DDB.hpp:237-243, DDB.cpp:54-66). For account this is called from setAccount (ACC.cpp:285-290).
10. HISTORY DIALOG
Two coexisting implementations share HistoryDialogBase:
- Legacy, per-entity (what account uses):
AccountHistoryDialog. - Generic, entity-type-parameterised (the direction of travel):
ores::qt::HistoryDialog, constructed asHistoryDialog(entityType, entityId, clientManager, parent), documented as "the single, non-templated history dialog shared by every entity" (HD.hpp:36-52). There is nocpp_qt_history_dialog*mustache template any more; the older per-entity dialogs are hand/generated leftovers (many exist undercompute/,dq/,iam/,trading/,workspace/).
10.1. 8.1 How it is opened (account)
- List-window toolbar action
History, iconIcon::History, tooltipView account history; enabled only with a single row selected (MDIW:68, 126-130, 911). AccountMdiWindow::viewHistorySelected()resolves the selected account and emitsshowAccountHistory(username)(MDIW:866-882).AccountController::onShowAccountHistorycreates aDetachableMdiSubWindowtitledAccount History: %1, iconIcon::History, wraps the widget, connectsstatusChanged=/=errorOccurred=/=openVersionRequested=/=revertVersionRequested, then callsloadHistory()(CTRL:292-340).- The detail dialog itself has no History button.
10.2. 8.2 Legacy layout (account) — AccountHistoryDialog.ui
Root QWidget, 900×600, windowTitle "Account History" (ACH.ui:4-15). QVBoxLayout:
QLabel titleLabelbold 12pt, textAccount History(overwritten at runtime withAccount History: <username>) (ACH.ui:18-28;ACH.cpp:79-82).QSplittervertical (ACH.ui:31-33) containing:QTableWidget versionListWidget— min height 150, max 250, alternating rows,NoEditTriggers, single selection, select rows, columns exactlyVersion,Recorded At,Modified By(ACH.ui:35-69).- detail pane with a
QTabWidget:- Tab
Changes→QTableWidget changesTableWidget, columns exactlyField,Old Value,New Value,NoEditTriggers, alternating rows, single selection/rows (ACH.ui:89-126). - Tab
Full Details→ scroll area with aQFormLayoutof read-onlyQLabelrows:Username:,Email:,Administrator:(hidden at runtime),Version:,Modified By:,Recorded At:(ACH.ui:127-236;ACH.cpp:96-108).
- Tab
- Bottom row: spacer +
Closebutton (ACH.ui:244-262).
10.2.1. Sort order / versions
Versions are newest-first: index 0 is the newest (the title is built from history_.versions.front(), ACH.cpp:79-82; HistoryDialogBase docs "0 = newest", HDB.hpp:210-212). versionRow supplies column 0 = version number, then cells {relative_time_helper::format(recorded_at), modified_by} (ACH.cpp:72-77). Only column 0 gets the Icon::History icon; a legacy table also gets alternating rows, resize-to-contents headers, and row resize (HDB.cpp:455-478). The newest row is auto-selected (HDB.cpp:473-477).
10.2.2. Per-version displays
- Changes tab: diffs the selected version against index+1 (i.e. the previous version). Oldest version → a placeholder row
(Initial version)with-=/-; no diffs → =(No field changes)(HDB.cpp:565-595). Account's diff compares onlyUsernameandEmail(ACH.cpp:84-94). Changed cells are rendered as rich-text labels with GitHub-style line/span highlighting computed byores::diff::engine::compute(HDB.cpp:641-708). "Oldest version has nothing to diff against" isversion_index =historySize()-1= (HDB.cpp:582). - Full Details tab: username, email (Administrator row hidden), version number, modified_by, recorded-at relative time (
ACH.cpp:96-108). - Editable? No — every table is
NoEditTriggers, and the only mutating actions are toolbar Open/Revert. - Toolbar (created by
initializeHistoryUi, inserted at layout index 0): Reload (Icon::ArrowClockwise, tooltipReload history from server), separator, Open (Icon::Edit, tooltipOpen this version in read-only mode), Revert (Icon::ArrowRotateCounterclockwise, tooltipRevert to this version) (HDB.cpp:388-429). Double-clicking a version row triggers Open (HDB.cpp:315-317). Open is enabled with any selection; Revert only forindex > 0(reverting the newest is a no-op) (HDB.cpp:757-768). - Revert confirmation (exact): title
Revert, bodyAre you sure you want to revert '%1' from version %2 back to version %3?\n\nThis will create a new version with the data from version %3.(HDB.cpp:796-805). - Open emits the entity signal; account re-opens the detail dialog read-only via
onOpenAccountVersion(ACH.cpp:110-115;CTRL:479-532).revertToVersionAtre-stamps the selected data with the latest version number before emittingrevertVersionRequested(ACH.cpp:117-127). - Load:
get_account_history_request { username }viarunHistoryRequest; on!success→historyLoadFailed(message); else store history and callhistoryLoaded()(ACH.cpp:51-66).historyLoaded()repopulates the list, selects row 0, sets the title, updates action states, and emitsstatusChanged("Loaded %1 versions")(HDB.cpp:431-483). - Load failure (exact):
errorOccurred("Failed to load history: %1")+MessageBoxHelper::critical(this, "History Load Error", "Failed to load history:\n%1")(HDB.cpp:485-489). - Stale:
markAsStale()emitsstatusChanged("<code> has new versions - click Reload to see them")and pulses the Reload action gold (HDB.cpp:251-260;HDB.hpp:88-93).
10.3. 8.3 Generic layout — HistoryDialog.ui (compare mode)
900×600, windowTitle "History" (HD.ui:4-15). A horizontal QSplitter:
- Left
timelinePane(min 220 / max 320): bold 10ptHistory Timelinelabel +QListWidget versionTimeline(tooltipClick a version to preview it against its predecessor.) (HD.ui:22-62). - Right
diffPane: header row withCompare:label,compareFromCombo(tooltipThe older version.),→,compareToCombo(tooltipThe newer version.), spacer, and a segmented two-button pillAll Fields/Only Changes(HD.ui:64-147); below it aQTextBrowser diffBrowser(openExternalLinks=false) (HD.ui:141-147). - Bottom row: spacer +
Close(HD.ui:152-171).
Timeline cards are custom widgets: title v<version> · <relative time> (tooltip = full ISO-8601 UTC timestamp), then optional lines Modified By, Performed By, an amber reason-code badge, and an italic quoted commentary (HDB.cpp:179-236). HistoryDialog::versionRow supplies cells {relative time, modified_by, performed_by, change_reason_code, change_commentary, iso_timestamp} (HD.cpp:125-137). Provenance fields (recorded_at, modified_by, performed_by, change_reason_code, change_commentary) are excluded from the diff table and shown on the card instead (HD.cpp:99-114, 139-151). Selecting a row previews it against its predecessor; the two combos can pick any pair; choosing From older than To shows The <b>From</b> version must be older than the <b>To</b> version.; equal selection shows either nothing ((No field changes)) or all fields (HDB.cpp:514-563, 597-639). Diff rows render as an HTML table with headers Field / Value Diff and intra-line colour spans (HDB.cpp:710-755). Open=/=Revert emit generic (entityType, entityId, version) signals (HD.hpp:75-77, HD.cpp:191-201).
10.4. 8.4 Size hint
Every history dialog enforces a minimum of 900×600 via sizeHint() override (HDB.cpp:277-280).
11. VALIDATION AND ERROR PRESENTATION
There is no field-level inline error UI (no red asterisks/text under fields) in this codebase. Errors are presented as modal message boxes plus status/error signals:
| Class of problem | Presentation | Exact wording |
|---|---|---|
| Cross-field validation on Save (account) | MessageBoxHelper::warning title Validation Error |
Username is required. / Password is required for new accounts. / Passwords do not match. / Password must be at least 12 characters long. (ACC.cpp:537, 548-563) |
| Generic required-field validation | MessageBoxHelper::warning title Invalid Input |
Please fill in all required fields. (T-cpp:1347-1350; ACID.cpp:327-329) |
| Not connected | errorMessage signal (status area) |
Not connected to server. Please login. (ACC.cpp:520) |
| Save still loading | errorMessage signal |
Still loading account data — please try again in a moment. (ACC.cpp:530) |
| Change-reason cache missing/empty | errorMessage signal |
see §7.4 |
| Server rejects save | errorMessage signal and MessageBoxHelper::critical title Save Failed (account: Create Failed) with the server's message verbatim |
Failed to save: %1 / Failed to create account: %1 (ACC.cpp:717-720, 955-958) |
| Server rejects delete | errorMessage signal and critical title Delete Failed |
Failed to delete account: %1 (ACC.cpp:1042-1044) |
| Delete confirmation | MessageBoxHelper::question title Delete Account |
Are you sure you want to delete account '%1'? (ACC.cpp:986-990) |
| Unsaved-close confirmation | MessageBoxHelper::question title Unsaved Changes |
You have unsaved changes. Close anyway? (DDB.cpp:123-126) |
| History load failure | errorOccurred + critical title History Load Error |
Failed to load history: %1 / Failed to load history:\n%1 (HDB.cpp:486-488) |
| Revert confirmation | MessageBoxHelper::question title Revert |
Are you sure you want to revert '%1' from version %2 back to version %3?\n\nThis will create a new version with the data from version %3. (HDB.cpp:796-805) |
| Account-level revert confirmation | MessageBoxHelper::question title Revert Account |
Are you sure you want to revert '%1' to version %2?\n\nThis will create a new version with the data from version %2. (ACC.cpp:1064-1071) |
| Child delete failed | MessageBoxHelper::warning title Delete Failed |
server message (ACET.cpp:193) |
| Child add before save | warning title Save Required |
Save the account first, then add contact information. (ACET.cpp:129-131) |
| Child operation while disconnected | warning title Disconnected |
Cannot add contact while disconnected from server. / Cannot save account contact information while disconnected from server. / Cannot delete account contact information while disconnected from server. (ACET.cpp:135-137; ACID.cpp:319-324, 398-403) |
MessageBoxHelper (MBH.hpp:31-82, MBH.cpp:28-128) renders all boxes with the app's recoloured Fluent icons (Question 48×48, Warning, Error, Info) and gives Yes a Checkmark icon and No a Dismiss icon (MBH.cpp:39-66). question defaults to Yes | No (MBH.hpp:41-45). critical has an overload taking expandable detailedText (MBH.hpp:70-73) but the account dialog does not use it.
Conflict/duplicate/optimistic-locking errors: there is no dedicated conflict UI. The server's message is displayed verbatim in the critical box and in the errorMessage signal; nothing maps a conflict to stale-state handling. Server-side data-change notifications invoke DetailDialogBase::markAsStale() / HistoryDialogBase::markAsStale(), which for account mark the MDI parent stale and emit statusMessage("Account %1 has been modified on the server") (ACC.cpp:1154-1166; DDB.hpp:96-97).
Combo boxes are globally configured (max 10 visible items, bounded popup view) by WidgetUtils::setupComboBoxes(parent), called by every dialog after setupUi() (WU.hpp:133-138; ACC.cpp:69; CRD.cpp:62; PW.cpp:31; ACID.cpp:52).
12. WHAT VARIES PER ENTITY
The authoritative "what an implementer supplies" list is the codegen entity model's ** Qt section plus the *** Detail fields table. For a codegen-bound entity, everything below is data in the model .org file; the mustache templates produce the dialog. The account dialog is hand-authored and deviates (see §10.4).
12.1. 10.1 Model keys the implementer must supply (example: MODEL:317-346)
Required :…: properties under ** Qt:
| Property | Example (account_contact_information) |
|---|---|
domain_include |
ores.iam.api/domain/account_contact_information.hpp |
domain_class |
iam::domain::account_contact_information |
protocol_include |
ores.iam.api/messaging/account_contact_information_protocol.hpp |
collection_name |
account_contact_informations |
item_var |
accountContactInformation |
key_field |
email |
get_request_class / get_response_class / get_message_type |
iam::messaging::get_account_contact_informations_request … |
save_request_class / save_response_class / save_message_type |
…save_account_contact_information_request … |
save_request_item_field |
data (the member the entity is assigned to) |
delete_request_class / delete_response_class / delete_message_type |
…delete_account_contact_information_request … |
history_request_class / history_response_class / history_message_type |
…get_account_contact_information_history_request … |
has_explorer_api, has_export_macro |
true |
parent_entity_singular, parent_id_field, parent_id_field_camel |
account, account_id, accountId (parent-scoped child) |
settings_group, window_title, icon |
AccountContactInformationListWindow, Account Contact Information, PersonAccounts |
Plus header-level: #+entity_singular, #+entity_plural, #+entity_title, #+component, and the column properties (:type:, :nullable:, :primary_key:, :natural_key:, :cpp_type:) (MODEL:1-14, 31-215).
12.2. 10.2 *** Detail fields table columns (one row per field, in display order)
Columns (MODEL:356-357): field, label, widget, type, is_key, is_required, placeholder, flag_source, combo_allow_blank, combo_fetch, combo_domain_type, combo_fetch_include, combo_fetch_fn, combo_watcher_name, combo_code_field, combo_tooltip_field, combo_sort_field, combo_label, combo_setter_pascal, badge_key.
Widget type values drive which Qt widget the UI template emits (T-ui:57-177) and which signal the cpp template connects (T-cpp:315-371):
type |
Qt widget | Notes |
|---|---|---|
line_edit |
QLineEdit |
key fields get readOnly=true and connect to onCodeChanged (T-ui:57-72, T-cpp:316-320) |
text_edit |
QPlainTextEdit |
80–150 px tall (T-ui:74-96) |
static_combo |
QComboBox (or custom via combo_widget_class) |
insertPolicy=NoInsert; optional badge_key |
dynamic_combo |
QComboBox |
fetched either via a combo_fetch_fn include or by the caller calling set<ComboSetter>() |
flagged_combo |
QComboBox |
flag_source + combo_fetch_fn (e.g. fetch_country_codes), flag icons applied (T-ui:122-133) |
check_box |
QCheckBox (blank text; optional tristate) |
T-ui:134-150 |
spin_box |
QSpinBox with spin_min=/=spin_max; nullable int gets specialValueText "(unset)" |
T-ui:151-170 |
colour |
QPushButton colour swatch |
T-ui:171-177; wired by wire_colour_picker |
date (date_fields set) |
date edit | validated as ISO-8601 before parse (T-cpp:1316-1334) |
Semantics derived from these columns:
is_key→ the key field: read-only in the.ui, drivescode()=/save log/delete lookup string, and connects to =onCodeChanged.is_required→ contributes tovalidateInput()(T-cpp:1309-1338) and to save-button enablement.- Fields that are
:primary_key:=/=category define=d =locked_fieldsaresetReadOnly(!createMode)/setEnabled(createMode)insetCreateMode()(T-cpp:513-542);is_locked_after_createforces disabled insetReadOnly()(T-cpp:573-620); auto-generated keys are always read-only (T-cpp:517-519). combo_as_of_fetch_fnfields are re-resolved whensetClientManager()=/=setReadOnly()=/=setX()are called (T-cpp:498-509, 627-638).
12.3. 10.3 What is inherited / shared (never re-implemented per entity)
- Shell:
QTabWidget+ provenance tab + button rowDelete=/=Close=/=Savewith the same icons (T-ui:29-263). DetailDialogBase:closeRequested,statusMessage,errorMessage,flagEdited,isCloseConfirmed,setChangeReasonCache,setBadgeCache,setImageCache,promptChangeReason,notifySaveSuccess,onCloseClicked(with the shared unsaved-changes prompt),requestClose,setProvenanceEnabled,populateProvenance,clearProvenance,initFlagButton,initKeyFlagField,nulloptIfZero, pure-virtualtabWidget()=/=provenanceTab()=/=provenanceWidget(), defaultentityImageId()=/=keyFlagField()=/=keyFlagIcon()=/=keyFlagIconSize()(DDB.hpp:84-413).ProvenanceWidget: the six read-only audit rows and their exact labels (PW.ui:15-116).ChangeReasonDialog+ChangeReasonCache: reason filtering, touch-reason rules, commentary requirement, titles and button labels (CRD.cpp,CRC.cpp).ChildEntityTableWidget: generic add/delete table (CETW.cpp).HistoryDialogBase+ (preferably) the genericHistoryDialog: timeline/diff machinery, Reload/Open/Revert toolbar, revert confirmation, diff helperscheckString=/=checkInt=/=checkDouble=/=checkBool(HDB.hpp:286-301),runHistoryRequest(HDB.hpp:310-363).MessageBoxHelper,PasswordMatchIndicator,WidgetUtils::setupComboBoxes,BadgeLabelUtils,IconUtils,LookupFetcher,FlagIconHelper,RelativeTimeHelper.- Controller wiring pattern: construct →
setClientManager→setUsername→setChangeReasonCache→setBadgeCache→setImageCache→setX()(or empty entity for create) → connect status/error/created/updated/deleted signals → wrap inDetachableMdiSubWindow(WA_DeleteOnClose) →register_detachable_window→connect_dialog_close→show_managed_window; history via the toolbarHistoryaction (CTRL:392-477;MDIW:866-882).
12.4. 10.4 Per-entity deltas the implementer actually writes
- The entity-specific field set (
*** Detail fields). - The title:
windowTitle = "<entity_title> Details"(T-ui:26-28). - Static combo contents and dynamic/flagged combo population (
T-cpp:291-300, 411-485, 755-931). validateInput()— the extra required-field / date checks (T-cpp:1309-1338).updateUiFromX()/updateXFromUi()field↔domain mapping (T-cpp:933-1292).- Signal names:
<item>Saved(const QString& code)/<item>Deleted(const QString& code)(T-hpp:111-113). - Optional facets (each adds UI/actions/state):
has_flag_icon(Icon group +setImageCache+entityImageId()),has_key_flag_icon(+keyFlagField()=/=keyFlagIcon(),key_flag_is_pair→keyFlagIconSize()),has_version_navigation(Revert/First/Previous/Next/Last toolbar +setHistory()+historicalVersion_),has_toolbar,has_generate_action(Generate action calling the synthetic generator),has_setting_gated_actions,has_combo_fields,has_colour_fields,has_uuid_primary_key,has_as_of_combo_fields(T-hpp:12-27, 80-97, 107-215). - A composite-child host class (like
AccountChildEntityTables) if the entity has child tables, plus a model:implements 7E4A2C8D-…block. - A controller + plugin registration (menu action,
showListWindow) — seecpp_qt_controller.cpp.mustache.
12.5. 10.5 Account-specific deviations from the generated standard (important for a faithful React port)
- Hand-written, not generated (
AccountDetailDialog.hpp:1-20has no AUTO-GENERATED banner;AccountContactInformationDetailDialog.hpp:20-24does). - 6 tabs, not one group per view_group:
General,Security,Login Status,Roles,Parties,Contact Information(runtime),Provenance; the generated standard emits one tab perview_groupplusProvenance. - Create mode does not prompt a change reason at all and
save_account_requesthas no audit fields (ACCTP.hpp:45-53), whereas the template prompts Create with categorysystem. - Edit mode prompts a change reason only when parties/roles changed, and the account
update_account_requestis sent with empty audit fields even though it has them (ACC.cpp:766-775, 814-821;ACCTP.hpp:85-86). A pure email/full-name/job-title edit is therefore un-audited. - Delete does not prompt a change reason (
ACC.cpp:971-1049), unlike the template (T-cpp:1446-1448). - Password fields exist and are create-only (a hand-written concern absent from the generic template).
- Admin checkbox row exists but is permanently hidden (
ACC.ui:94-112). - Uses the legacy
AccountHistoryDialograther than the genericHistoryDialog; its Changes diff only coversUsernameandEmail(ACH.cpp:84-94). code()is not overridden for the account dialog; identity is exposed asaccountId()returning the UUID string (ACC.hpp:141;ACC.cpp:1168-1170) and the controller matches notifications on it (CTRL:259).- Extra inline help absent from the generic template: password match border, party default tooltip/badge, account-type-driven party warning, reports-to async combo.
13. Quick reference — exact button/action labels
| Surface | Labels (exact) |
|---|---|
| Detail dialog buttons | Delete, Close, Save |
| Detail dialog toolbar (historical) | Revert to this version |
| Change-reason dialog buttons | Create / Save / Confirm Delete, Cancel |
| Change-reason dialog titles | New Record Reason, Change Reason Required, Deletion Reason Required |
| History dialog toolbar | Reload, Open, Revert |
| History dialog buttons | Close |
| Child table toolbar (account contacts) | Add Contact, Delete Contact |
| Roles widget tooltips | Assign selected role, Revoke selected role |
| Parties widget tooltips | Add selected party, Remove selected party |
| List-window toolbar (account) | Reload, Add, Edit, Delete, Lock, Unlock, Reset Pwd, History (View account history), Sessions (View session audit) — MDIW:61-69, 128, 134 |