ores.refdata.party
Table of Contents
- Flags
- Columns
- Foreign keys
- Insert trigger
- SQL
- C++
- See also
Internal legal entities (the organisation and its subsidiaries) that
participate in financial transactions. Parties form a hierarchy through
parent_party_id, with exactly one root party per tenant representing the
organisation itself.
Flags
Cross-cutting properties. Consumed by both C++ and SQL codegen.
Columns
Logical, human-meaningful identifiers (full_name, short_code,
flagged :natural_key: true below) are inserted into the C++ struct
alongside the primary key, and exposed in SQL as additional
unique-indexed columns.
id
UUID uniquely identifying this party.
Surrogate key for the party record.
full_name
Full legal name of the party.
The official registered name of the entity.
std::string(faker::company::companyName())
short_code
Short code for quick reference.
A brief mnemonic code used in trading systems.
std::string(faker::string::alpha(6))
codename
Globally unique human-readable codename (adjective_noun).
Used as the per-party pgmq queue prefix, pg_cron job names, and operator tooling. Immutable once assigned; auto-generated by the SQL trigger if not supplied on insert.
""
transliterated_name
ASCII transliteration of the entity name.
Populated from GLEIF data for entities with non-Latin names (CJK, Cyrillic, Arabic, etc.). Null for entities already in Latin script.
std::nullopt
party_category
Structural classification of this party.
References the party_category lookup table. Values: 'System' (one per tenant, auto-created during provisioning) or 'Operational' (business entities created during normal system operation). Not user-editable via the Qt UI – every party created through the detail dialog is 'Operational'; 'System' parties are exclusively auto-created during tenant provisioning. Defaults here so a freshly-constructed party (the Add dialog, with no UI field for this column) always carries a value the party_category-validation trigger accepts.
std::string("Operational")
party_type
Classification of this party.
References the party_type lookup table.
std::string("Corporate")
parent_party_id
Parent party for hierarchy.
References the parent party record for group structures. Null for root parties.
std::nullopt
business_center_code
Business center location code.
FpML business center code indicating primary location. Synthetic
parties default to WRLD (the global sentinel business centre,
guaranteed seeded for every tenant by the system-tenant bootstrap –
see the same reasoning in ores.refdata.book.org), not a real FpML
city code like GBLO: real city codes only exist once the large,
optional FpML business-centre reference dataset has been loaded, so
using one here would make a freshly-generated party fail its
FK-validation trigger in any environment that hasn't loaded it.
std::string("WRLD")
status
Current lifecycle status.
References the party_status lookup table.
std::string("Active")
image_id
Optional reference to a logo/branding image in the images table.
std::nullopt
Foreign keys
parent_party_id
image_id
Insert trigger
Validations
| column | validation_function |
|---|---|
| party_category | ores_refdata_validate_party_category_fn |
| party_type | ores_refdata_validate_party_type_fn |
| status | ores_refdata_validate_party_status_fn |
| business_center_code | ores_refdata_validate_business_centre_fn |
SQL
Everything below is consumed by the SQL codegen pipeline only.
Flags
Checks
| expression |
|---|
| length("codename") <= 32 |
| "codename" = '' or "codename" ~ '^[a-z][a-z_]+$' |
Indexes
| name | columns | unique | current_only | where_extra |
|---|---|---|---|---|
| full_name | tenant_id, full_name | false | true | |
| codename_uniq | codename | true | true | codename <> '' |
| root_party_uniq | tenant_id | true | false | parent_party_id is null and party_category <> 'System' and valid_to = ores_utility_infinity_timestamp_fn() |
| system_party_uniq | tenant_id | true | false | party_category = 'System' and valid_to = ores_utility_infinity_timestamp_fn() |
Codename sequence
Sequence used to generate a unique base-26 suffix for auto-generated
codenames. nextval() is outside MVCC: it advances even within a
multi-row INSERT statement, so every row in a batch gets a different
suffix, avoiding duplicate-key collisions that a NOT EXISTS loop
cannot prevent due to the statement-level snapshot in READ COMMITTED.
-- Sequence used to generate a unique base-26 suffix for auto-generated -- codenames. nextval() is outside MVCC: it advances even within a -- multi-row INSERT statement, so every row in a batch gets a different -- suffix, avoiding duplicate-key collisions that a NOT EXISTS loop cannot -- prevent due to the statement-level snapshot in READ COMMITTED. create sequence if not exists ores_refdata_party_codename_seq;
drop sequence if exists ores_refdata_party_codename_seq;
Codename immutability (update path)
Codename is immutable: restore it from the active row on every update, rather than letting a client-supplied value silently overwrite it.
-- Codename is immutable: restore from active row on every update. select codename into NEW.codename from "ores_refdata_parties_tbl" where tenant_id = NEW.tenant_id and id = NEW.id and valid_to = ores_utility_infinity_timestamp_fn();
Codename generation and queue provisioning (insert path)
Auto-generates codename (whimsical name + a globally-unique base-26
sequence suffix) when the client leaves it blank, validates the final
format, and provisions the per-party report-event queue – all only on
first insert, since codename is immutable thereafter (see above).
-- Auto-generate codename using whimsical name + sequence suffix. -- The sequence suffix is outside MVCC (nextval always advances), -- so every row in a multi-row INSERT gets a distinct suffix, making -- the codename globally unique without relying on a NOT EXISTS -- check that cannot see sibling rows in the same statement. if NEW.codename = '' or NEW.codename is null then NEW.codename := ores_utility_generate_whimsical_name_fn() || '_' || ores_utility_to_base26_fn(nextval('ores_refdata_party_codename_seq')); end if; -- Validate the final codename. if NEW.codename !~ '^[a-z][a-z_]+$' then raise exception 'codename must match ^[a-z][a-z_]+$ got: %', NEW.codename using errcode = '23514'; end if; -- Provision the per-party report event queue. perform ores_mq_queues_create_fn( NEW.tenant_id, NEW.id, 'party', 'task', 'report_events', 'Per-party report scheduling queue', current_user);
C++
Everything below is consumed by the C++ codegen pipeline only.
Flags
Repository
Domain includes
#include <chrono> #include <string> #include <optional> #include <boost/uuid/uuid.hpp>
Entity includes
#include <string> #include <optional> #include "sqlgen/Timestamp.hpp" #include "sqlgen/PrimaryKey.hpp"
Conventions
Table display
| column | header |
|---|---|
| short_code | Code |
| full_name | Name |
| party_type | Type |
| status | Status |
| business_center_code | Business Center |
| modified_by | Modified By |
| version | Version |
Qt
Detail fields
| field | label | widget | type | is_key | is_required | placeholder | flag_source | combo_allow_blank | combo_fetch | combo_domain_type | combo_display_field | 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 | combo_blank_label |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| short_code | Short Code | codeEdit | line_edit | true | true | Enter short code | |||||||||||||||
| full_name | Full Name | nameEdit | line_edit | false | true | Enter full name | |||||||||||||||
| party_type | Party Type | partyTypeCombo | dynamic_combo | false | refdata::domain::party_type | ores.qt/LookupFetcher.hpp | fetch_party_types | partyTypeWatcher | code | description | display_order | party types | PartyTypeCombo | party_type | |||||||
| status | Status | statusCombo | dynamic_combo | false | refdata::domain::party_status | ores.qt/LookupFetcher.hpp | fetch_party_statuses | partyStatusWatcher | code | description | display_order | party statuses | PartyStatusCombo | party_status | |||||||
| business_center_code | Business Center | businessCenterCombo | flagged_combo | false | business_centre | false | fetch_business_centre_codes | ores.qt/LookupFetcher.hpp | |||||||||||||
| parent_party_id | Parent Party | parentPartyCombo | dynamic_combo | false | true | refdata::domain::party | full_name | ores.qt/LookupFetcher.hpp | fetch_parties | parentPartyWatcher | id | short_code | version | parties | ParentPartyCombo | No Parent |
Columns (Qt model)
| enum_name | field | header | type | width | is_badge | badge_key | formatter | formatter_include |
|---|---|---|---|---|---|---|---|---|
| ShortCode | short_code | Code | string | 120 | ||||
| FullName | full_name | Name | string | 250 | ||||
| PartyType | party_type | Type | string | 120 | true | party_type | ||
| Status | status | Status | string | 100 | true | party_status | ||
| BusinessCenterCode | business_center_code | Business Center | string | 130 | ||||
| Version | version | Version | int | 80 | ||||
| ModifiedBy | modified_by | Modified By | string | 120 | ||||
| RecordedAt | recorded_at | Recorded At | timestamp | 150 |
Icon columns (Qt model)
Business center gets its own flag icon, same as any other single-business-centre cell (e.g. Book's rates centre code column).
| column | accessor | field1 | field2 |
|---|---|---|---|
| BusinessCenterCode | business_centre_flag_icon | business_center_code |
Custom repository methods
The two methods below are beyond the standard repository template because
they need raw SQL access: read_system_party (calls a stored procedure)
and read_descendants (a recursive CTE over the hierarchy). The
paginated read_latest(offset, limit) and get_total_party_count() used
to be hand-maintained here too, but are now covered by the generic
:service_pagination: true facet — removed to avoid duplicate
declarations. The codegen mechanism injects the remaining two at
template-rendered markers.
read_system_party
Calls ores_refdata_read_system_party_fn stored procedure. Every tenant has
exactly one system party (party_category'System'=) which serves as the
root of the party hierarchy.
std::vector<domain::party> read_system_party(context ctx, const std::string& tenant_id);
#include <boost/uuid/uuid_io.hpp> #include <boost/lexical_cast.hpp> #include "ores.utility/uuid/tenant_id.hpp"
std::vector<domain::party> party_repository::read_system_party(context ctx, const std::string& tenant_id) { BOOST_LOG_SEV(lg(), debug) << "Reading system party for tenant: " << tenant_id; const std::string sql = "SELECT * FROM ores_refdata_read_system_party_fn('" + tenant_id + "'::uuid)"; const auto rows = execute_raw_multi_column_query(ctx, sql, lg(), "Reading system party by tenant"); std::vector<domain::party> result; result.reserve(rows.size()); static constexpr std::array required_columns = {0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14}; for (const auto& row : rows) { if (row.size() >= 16 && std::ranges::all_of(required_columns, [&row](int i) { return static_cast<bool>(row[i]); })) { domain::party p; p.id = boost::lexical_cast<boost::uuids::uuid>(*row[0]); p.tenant_id = utility::uuid::tenant_id::from_string(*row[1]).value(); p.version = std::stoi(*row[2]); p.full_name = *row[3]; p.short_code = *row[4]; p.party_category = *row[5]; p.party_type = *row[6]; if (row[7]) p.parent_party_id = boost::lexical_cast<boost::uuids::uuid>(*row[7]); if (row[8]) p.business_center_code = *row[8]; p.status = *row[9]; p.modified_by = *row[10]; result.push_back(p); } } return result; }
read_descendants
Traverses the party hierarchy via a recursive CTE starting from root_id.
Returns the root plus every descendant (active records only).
std::vector<boost::uuids::uuid> read_descendants(context ctx, const boost::uuids::uuid& root_id);
#include <boost/uuid/uuid_io.hpp> #include <boost/lexical_cast.hpp>
std::vector<boost::uuids::uuid> party_repository::read_descendants(context ctx, const boost::uuids::uuid& root_id) { BOOST_LOG_SEV(lg(), debug) << "Reading party descendants. Root: " << root_id; const auto id_str = boost::uuids::to_string(root_id); const std::string sql = "WITH RECURSIVE party_tree AS (" " SELECT id FROM ores_refdata_parties_tbl" " WHERE id = '" + id_str + "' AND valid_to = '" + MAX_TIMESTAMP + "'" " UNION ALL" " SELECT p.id FROM ores_refdata_parties_tbl p" " JOIN party_tree pt ON p.parent_party_id = pt.id" " WHERE p.valid_to = '" + MAX_TIMESTAMP + "'" ") SELECT id FROM party_tree"; const auto rows = execute_raw_multi_column_query(ctx, sql, lg(), "Reading party descendants"); std::vector<boost::uuids::uuid> result; result.reserve(rows.size()); for (const auto& row : rows) if (!row.empty() && row[0]) result.push_back(boost::lexical_cast<boost::uuids::uuid>(*row[0])); return result; }
Custom NATS protocol messages
party exposes one hand-crafted NATS message pair beyond the standard CRUD
surface and generic facets: composite_as_of (reads a party as it stood at
a specific version, together with its identifiers and contact information
as they stood during that same version's window — see the "Temporal
composite entity versioning" architecture doc). The bulk cache-warming
read that used to be hand-written here (read_parties_for_cache) is now
covered by the generic :read_for_cache: true facet (see Flags) — party
was the archetype's original motivating precedent, now migrated onto it.
#include "ores.refdata.api/domain/party_contact_information.hpp" #include "ores.refdata.api/domain/party_identifier.hpp"
/** * @brief Reads a party as it stood at a specific version, together with its * identifiers and contact information as they stood during that same * version's [valid_from, valid_to) window. See the "Temporal composite * entity versioning" architecture doc. */ struct get_party_composite_as_of_request { using response_type = struct get_party_composite_as_of_response; static constexpr std::string_view nats_subject = "refdata.v1.parties.composite_as_of"; std::string id; int version = 0; }; struct get_party_composite_as_of_response { bool success = false; std::string message; ores::refdata::domain::party party; std::vector<ores::refdata::domain::party_identifier> identifiers; std::vector<ores::refdata::domain::party_contact_information> contacts; };
Custom NATS handler methods
Hand-written handler method for composite_as_of, dispatching to
party_service=/=party_identifier_service=/=party_contact_information_service.
#include "ores.refdata.core/service/party_contact_information_service.hpp" #include "ores.refdata.core/service/party_identifier_service.hpp" #include "ores.database/service/tenant_context.hpp" #include "ores.service/messaging/workflow_helpers.hpp" #include <chrono>
save's workflow-step-command branch (below) special-cases orchestration
commands ahead of the generated standard flow: it bypasses JWT auth and
uses the X-Tenant-Id header instead, replays a cached result if the step
already completed (idempotency), and reports completion/failure back to
the workflow engine rather than replying to the caller directly.
using ores::service::messaging::is_workflow_command; using ores::service::messaging::extract_workflow_header; using ores::service::messaging::publish_step_completion; using ores::service::messaging::check_step_idempotency; using ores::service::messaging::workflow_step_id_header; using ores::service::messaging::workflow_instance_id_header; using ores::service::messaging::workflow_tenant_id_header; // Workflow step command: bypass JWT auth; use X-Tenant-Id for context. if (is_workflow_command(msg)) { const auto step_id = extract_workflow_header(msg, workflow_step_id_header); const auto inst_id = extract_workflow_header(msg, workflow_instance_id_header); const auto tenant_id = extract_workflow_header(msg, workflow_tenant_id_header); // Idempotency guard: replay cached result if this step already completed. if (auto cached = check_step_idempotency(nats_, step_id)) { publish_step_completion(nats_, step_id, inst_id, cached->outcome, cached->result_json, cached->error_message, cached->log); return; } auto req = decode<save_party_request>(msg); if (!req) { publish_step_completion(nats_, step_id, inst_id, ores::workflow::messaging::step_outcome::failed, "", "Failed to decode save_party_request"); return; } try { using ores::database::service::tenant_context; auto wf_ctx = tenant_context::with_tenant(ctx_, tenant_id); service::party_service svc(wf_ctx); svc.save_party(req->data); BOOST_LOG_SEV(party_handler_lg(), debug) << "Workflow step completed: " << msg.subject; publish_step_completion(nats_, step_id, inst_id, ores::workflow::messaging::step_outcome::completed, rfl::json::write(save_party_response{.success = true}), ""); } catch (const std::exception& e) { BOOST_LOG_SEV(party_handler_lg(), error) << "Workflow step failed: " << msg.subject << " — " << e.what(); publish_step_completion(nats_, step_id, inst_id, ores::workflow::messaging::step_outcome::failed, "", e.what()); } return; }
void composite_as_of(ores::nats::message msg) { BOOST_LOG_SEV(party_handler_lg(), debug) << "Handling " << msg.subject; auto ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_); if (!ctx_expected) { error_reply(nats_, msg, ctx_expected.error()); return; } const auto& ctx = *ctx_expected; auto req = decode<get_party_composite_as_of_request>(msg); if (!req) { BOOST_LOG_SEV(party_handler_lg(), warn) << "Failed to decode: " << msg.subject; reply(nats_, msg, get_party_composite_as_of_response{.success = false, .message = "Failed to decode request"}); return; } try { service::party_service party_svc(ctx); const auto version = static_cast<std::uint32_t>(req->version); auto current = party_svc.get_party_at_version(req->id, version); if (!current) { reply(nats_, msg, get_party_composite_as_of_response{.success = false, .message = "No such party version: " + req->id + " v" + std::to_string(version)}); return; } // Windows are contiguous by construction: the next version's // valid_from is this version's valid_to. If there is no next // version, this is the current one — its window is still open, // so bound it with a safely-far-future instant instead (see the // "Temporal composite entity versioning" architecture doc; the // domain object does not surface valid_to directly). auto next = party_svc.get_party_at_version(req->id, version + 1); const auto window_end = next ? next->recorded_at : std::chrono::system_clock::now() + std::chrono::hours(24 * 365 * 100); service::party_identifier_service identifier_svc(ctx); auto identifiers = identifier_svc.list_party_identifiers_by_party_id_as_of( req->id, current->recorded_at, window_end); service::party_contact_information_service contact_svc(ctx); auto contacts = contact_svc.list_party_contact_informations_by_party_id_as_of( req->id, current->recorded_at, window_end); BOOST_LOG_SEV(party_handler_lg(), debug) << "Completed " << msg.subject; reply(nats_, msg, get_party_composite_as_of_response{.success = true, .party = std::move(*current), .identifiers = std::move(identifiers), .contacts = std::move(contacts)}); } catch (const std::exception& e) { BOOST_LOG_SEV(party_handler_lg(), error) << msg.subject << " failed: " << e.what(); reply(nats_, msg, get_party_composite_as_of_response{.success = false, .message = e.what()}); } }
Custom NATS subscriptions
Hand-crafted subjects wired into the generated party_registrar.cpp via the
nats-sub-registrar facet's custom-subscription paste point.
subs.push_back(nats.queue_subscribe(
get_party_composite_as_of_request::nats_subject, queue_group, [h](ores::nats::message msg) {
h->composite_as_of(std::move(msg));
}));
Cache aux index
The generated party_cache (nats-event-cache facet, cached_by:
iam) carries a parent/child aux index alongside the plain
tenant/id → party map, so IAM can answer "which parties are visible
from this root" without a DB round-trip. See the cache_aux_type flag
under * C++ / ** Flags and the facet doc's "Optional aux index"
section.
using children_map = immer::map<boost::uuids::uuid, std::vector<boost::uuids::uuid>, key_hash>;
auto children_t = children_map{}.transient(); for (const auto& [id, p] : entries) { if (p.parent_party_id) { const auto* existing = children_t.find(*p.parent_party_id); auto siblings = existing ? *existing : std::vector<boost::uuids::uuid>{}; siblings.push_back(id); children_t.set(*p.parent_party_id, std::move(siblings)); } } auto aux = children_t.persistent();
std::vector<boost::uuids::uuid> compute_visible_party_ids(const std::string& tenant_id, const boost::uuids::uuid& root_id) const { const auto snap = cache_.snapshot(tenant_id); if (!snap) return {root_id}; std::vector<boost::uuids::uuid> result; std::vector<boost::uuids::uuid> stack{root_id}; while (!stack.empty()) { const auto node = stack.back(); stack.pop_back(); result.push_back(node); const auto* siblings = snap->aux.find(node); if (siblings) for (const auto& child : *siblings) stack.push_back(child); } return result; }
Custom composite child-entity tables
Embeds party_identifier and party_contact_information as editable
tables in dedicated tabs — the composite children under temporal
versioning (see the "Temporal composite entity versioning" architecture
doc). The actual widget/NATS logic is hand-written (not generated) in
PartyChildEntityTables; these paste blocks just wire it into the
generated dialog.
#include "ores.qt/PartyChildEntityTables.hpp" #include "ores.qt/PartyHierarchyTab.hpp"
PartyChildEntityTables* childTables_{nullptr}; PartyHierarchyTab* hierarchyTab_{nullptr};
childTables_ = new PartyChildEntityTables(this); childTables_->attachTo(tabWidget());
hierarchyTab_ = new PartyHierarchyTab(this); hierarchyTab_->attachTo(tabWidget());
updateFlagDisplay(); childTables_->reload(party_.id, clientManager_, username_, imageCache(), changeReasonCache()); hierarchyTab_->reload(party_.id, clientManager_);
childTables_->setReadOnly(readOnly);
Qt: auxiliary lookup toolbar buttons
Cross-navigation from the Parties list window to the three lookup entities backing its own combo fields — ores.refdata.party_type, ores.refdata.party_status, and ores.refdata.party_id_scheme — via three toolbar buttons, mirroring ores.refdata.currency_pair's "Conventions" button. Signals declared on the MdiWindow and relayed through the Controller; Counterparty implements the identical seam since it shares all three lookups with Party.
void showPartyTypesRequested(); void showPartyStatusesRequested(); void showPartyIdSchemesRequested();
{
toolbar_->addSeparator();
auto* partyTypesAction = toolbar_->addAction(
IconUtils::createRecoloredIcon(Icon::Tag, IconUtils::DefaultIconColor), tr("Party Types"));
partyTypesAction->setToolTip(tr("Open Party Types"));
connect(partyTypesAction, &QAction::triggered, this,
&PartyMdiWindow::showPartyTypesRequested);
auto* partyStatusesAction = toolbar_->addAction(
IconUtils::createRecoloredIcon(Icon::Classification, IconUtils::DefaultIconColor),
tr("Party Statuses"));
partyStatusesAction->setToolTip(tr("Open Party Statuses"));
connect(partyStatusesAction, &QAction::triggered, this,
&PartyMdiWindow::showPartyStatusesRequested);
auto* partyIdSchemesAction = toolbar_->addAction(
IconUtils::createRecoloredIcon(Icon::Chart, IconUtils::DefaultIconColor),
tr("Id Schemes"));
partyIdSchemesAction->setToolTip(tr("Open Party Id Schemes"));
connect(partyIdSchemesAction, &QAction::triggered, this,
&PartyMdiWindow::showPartyIdSchemesRequested);
}
void showPartyTypesRequested(); void showPartyStatusesRequested(); void showPartyIdSchemesRequested();
connect(listWindow_, &PartyMdiWindow::showPartyTypesRequested, this, &PartyController::showPartyTypesRequested); connect(listWindow_, &PartyMdiWindow::showPartyStatusesRequested, this, &PartyController::showPartyStatusesRequested); connect(listWindow_, &PartyMdiWindow::showPartyIdSchemesRequested, this, &PartyController::showPartyIdSchemesRequested);
See also
- ores.refdata — component group overview.