ores.refdata.party

Table of Contents

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

Emacs 29.3 (Org mode 9.6.15)