ores.refdata.counterparty

Table of Contents

External trading partners and counterparties that participate in financial transactions with the organisation. Counterparties form a hierarchy through parent_counterparty_id for group structures.

1. Flags

2. Columns

2.1. id

UUID uniquely identifying this counterparty.

Surrogate key for the counterparty record.

2.2. short_code

Short code for quick reference.

A brief mnemonic code used in trading systems.

std::string(faker::string::alpha(6))

2.3. full_name

Full legal name of the counterparty.

The official registered name of the external entity. Not unique (e.g. the same legal name can recur across branches with distinct short codes) — see the non-unique search index declared under the SQL Indexes section below.

std::string(faker::company::companyName())

2.4. transliterated_name

Transliterated (e.g. Latin-script) rendering of the full legal name, for counterparties whose registered name uses a non-Latin script.

std::nullopt

2.5. party_type

Classification of this counterparty.

References the party_type lookup table.

std::string("Bank")

2.6. parent_counterparty_id

Parent counterparty for hierarchy.

References the parent counterparty record for group structures. Null for root counterparties.

std::nullopt

2.7. status

Current lifecycle status.

References the party_status lookup table.

std::string("Active")

2.8. image_id

Optional reference to a logo/branding image in the images table.

std::nullopt

3. SQL

3.1. Flags

3.2. Indexes

name columns unique current_only where_extra
full_name tenant_id, full_name false true  

3.3. Paste blocks

3.3.1. Extra drop statements

The self-referencing profile generates the hierarchy read, and the drop template knows nothing about it, so the entity drops it here. Without this block a drop-and-recreate leaves the function behind.

drop function if exists ores_refdata_counterparties_hierarchy_fn(uuid, uuid, boolean);

4. Foreign keys

4.1. parent_counterparty_id

4.2. image_id

5. Insert trigger

5.1. Validations

column validation_function
party_type ores_refdata_validate_party_type_fn
status ores_refdata_validate_party_status_fn

6. Messages

The composite_as_of pair, declared here so both the header and its TypeScript twin render it.

6.1. get_counterparty_composite_as_of_request

/**
 * @brief Reads a counterparty 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.
 */

6.1.1. id

6.1.2. version

6.2. get_counterparty_composite_as_of_response

6.2.1. success

6.2.2. message

6.2.3. counterparty

6.2.4. identifiers

6.2.5. contacts

6.3. put_counterparty_composite_request

/**
 * @brief Writes a counterparty and the children a confirmed review staged, in
 * one transaction. The eight writes the confirm lists are one act: a refusal
 * or a failure leaves none of them behind.
 */

6.3.1. intent

6.3.2. counterparty

6.3.3. identifiers

6.3.4. contacts

6.3.5. agreements

6.3.6. netting_sets

6.3.7. netting_set_identifiers

6.3.8. csas

6.3.9. eligible_currencies

6.4. put_counterparty_composite_response

6.4.1. result

6.4.2. counterparty

7. C++

7.1. Flags

7.2. Repository

7.3. Domain includes

#include <string>
#include <optional>
#include <boost/uuid/uuid.hpp>

7.4. Entity includes

#include <string>
#include <optional>

7.5. Conventions

7.6. Table display

column header
short_code Code
full_name Name
party_type Type
status Status
modified_by Modified By
version Version

7.7. Presentation

7.7.1. 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     fetch_party_types partyTypeWatcher code description display_order party types PartyTypeCombo party_type  
status Status statusCombo dynamic_combo false           refdata::domain::party_status     fetch_party_statuses partyStatusWatcher code description display_order party statuses PartyStatusCombo party_status  
parent_counterparty_id Parent Counterparty parentCounterpartyCombo dynamic_combo false true         refdata::domain::counterparty full_name   fetch_counterparties parentCounterpartyWatcher id short_code version counterparties ParentCounterpartyCombo   No Parent

7.7.2. Columns

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    
Version version Version int 80        
ModifiedBy modified_by Modified By string 120        
RecordedAt recorded_at Recorded At timestamp 150        

7.8. Custom repository methods

7.9. Custom NATS protocol messages

counterparty declares its composite_as_of message pair in the top-level * Messages section: reads a counterparty 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 header and its TypeScript twin both render it from there.

#include "ores.refdata.api/domain/counterparty_contact_information.hpp"
#include "ores.refdata.api/domain/counterparty_identifier.hpp"
#include "ores.refdata.api/domain/csa.hpp"
#include "ores.refdata.api/domain/csa_eligible_currency.hpp"
#include "ores.refdata.api/domain/netting_agreement.hpp"
#include "ores.refdata.api/domain/netting_set.hpp"
#include "ores.refdata.api/domain/netting_set_identifier.hpp"

7.10. Custom NATS handler methods

#include "ores.database/repository/unit_of_work.hpp"
#include "ores.refdata.core/service/counterparty_contact_information_service.hpp"
#include "ores.refdata.core/service/counterparty_identifier_service.hpp"
#include "ores.refdata.core/service/csa_eligible_currency_service.hpp"
#include "ores.refdata.core/service/csa_service.hpp"
#include "ores.refdata.core/service/netting_agreement_service.hpp"
#include "ores.refdata.core/service/netting_set_identifier_service.hpp"
#include "ores.refdata.core/service/netting_set_service.hpp"
#include <boost/uuid/string_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <chrono>
void composite_as_of(ores::nats::message msg) {
    BOOST_LOG_SEV(counterparty_handler_lg(), debug) << "Handling " << msg.subject;
    auto req_ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
    if (!req_ctx_expected) {
        error_reply(nats_, msg, req_ctx_expected.error());
        return;
    }
    const auto& req_ctx = *req_ctx_expected;
    if (!has_permission(req_ctx, "refdata::counterparties:read")) {
        error_reply(nats_, msg, ores::service::error_code::forbidden);
        return;
    }
    auto req = decode<get_counterparty_composite_as_of_request>(msg);
    if (!req) {
        BOOST_LOG_SEV(counterparty_handler_lg(), warn) << "Failed to decode: " << msg.subject;
        error_reply(nats_, msg, ores::service::error_code::bad_request);
        return;
    }
    try {
        service::counterparty_service cpty_svc(req_ctx);
        const auto version = static_cast<std::uint32_t>(req->version);
        auto current = cpty_svc.get_counterparty_at_version(req->id, version);
        if (!current) {
            reply(nats_,
                  msg,
                  get_counterparty_composite_as_of_response{
                      .success = false,
                      .message = "No such counterparty version: " +
                                 boost::uuids::to_string(req->id) + " v" +
                                 std::to_string(version)});
            return;
        }

        // See party_handler::composite_as_of — windows are contiguous
        // by construction; the domain object doesn't surface valid_to.
        auto next = cpty_svc.get_counterparty_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::counterparty_identifier_service identifier_svc(req_ctx);
        auto identifiers =
            identifier_svc.list_counterparty_identifiers_by_counterparty_id_as_of(
                boost::uuids::to_string(req->id), current->recorded_at, window_end);

        service::counterparty_contact_information_service contact_svc(req_ctx);
        auto contacts =
            contact_svc.list_counterparty_contact_informations_by_counterparty_id_as_of(
                boost::uuids::to_string(req->id), current->recorded_at, window_end);

        BOOST_LOG_SEV(counterparty_handler_lg(), debug) << "Completed " << msg.subject;
        reply(nats_,
              msg,
              get_counterparty_composite_as_of_response{.success = true,
                                                        .counterparty = std::move(*current),
                                                        .identifiers = std::move(identifiers),
                                                        .contacts = std::move(contacts)});
    } catch (const std::exception& e) {
        BOOST_LOG_SEV(counterparty_handler_lg(), error)
            << msg.subject << " failed: " << e.what();
        reply(nats_,
              msg,
              get_counterparty_composite_as_of_response{.success = false, .message = e.what()});
    }
}

void put_composite(ores::nats::message msg) {
    BOOST_LOG_SEV(counterparty_handler_lg(), debug) << "Handling " << msg.subject;
    auto req_ctx_expected = ores::service::service::make_request_context(ctx_, msg, verifier_);
    if (!req_ctx_expected) {
        error_reply(nats_, msg, req_ctx_expected.error());
        return;
    }
    const auto& req_ctx = *req_ctx_expected;
    if (!has_permission(req_ctx, "refdata::counterparties:write")) {
        error_reply(nats_, msg, ores::service::error_code::forbidden);
        return;
    }
    auto req = decode<put_counterparty_composite_request>(msg);
    if (!req) {
        BOOST_LOG_SEV(counterparty_handler_lg(), warn) << "Failed to decode: " << msg.subject;
        error_reply(nats_, msg, ores::service::error_code::bad_request);
        return;
    }
    try {
        // One transaction over every row the confirm staged. The unit of
        // work rolls back when it is destroyed uncommitted, so a refusal
        // anywhere leaves none of the eight writes behind. Each service is
        // built on the unit of work's context, which is what makes its
        // repository calls join the transaction rather than open their own.
        ores::database::repository::unit_of_work uow(req_ctx);
        const auto& uctx = uow.ctx();
        const auto reason =
            req->intent.reason_code.empty() ?
                std::string(ores::service::messaging::change_reasons::new_record) :
                req->intent.reason_code;
        // The reason is the caller's, and the commentary with it; the audit
        // stamps stay the services' to fill.
        const auto apply_intent = [&](auto& row) {
            if (row.change_reason_code.empty())
                row.change_reason_code = reason;
            row.change_commentary = req->intent.commentary;
        };

        auto counterparty = req->counterparty;
        apply_intent(counterparty);
        service::counterparty_service counterparties(uctx);
        counterparties.save_counterparty(counterparty);

        auto identifiers = req->identifiers;
        for (auto& row : identifiers)
            apply_intent(row);
        service::counterparty_identifier_service identifier_service(uctx);
        identifier_service.save_counterparty_identifiers(identifiers);

        auto contacts = req->contacts;
        for (auto& row : contacts)
            apply_intent(row);
        service::counterparty_contact_information_service contact_service(uctx);
        contact_service.save_counterparty_contact_informations(contacts);

        auto agreements = req->agreements;
        for (auto& row : agreements)
            apply_intent(row);
        service::netting_agreement_service agreement_service(uctx);
        agreement_service.save_netting_agreements(agreements);

        auto netting_sets = req->netting_sets;
        for (auto& row : netting_sets)
            apply_intent(row);
        service::netting_set_service netting_set_service(uctx);
        netting_set_service.save_netting_sets(netting_sets);

        auto set_identifiers = req->netting_set_identifiers;
        for (auto& row : set_identifiers)
            apply_intent(row);
        service::netting_set_identifier_service set_identifier_service(uctx);
        set_identifier_service.save_netting_set_identifiers(set_identifiers);

        auto csas = req->csas;
        for (auto& row : csas)
            apply_intent(row);
        service::csa_service csa_service(uctx);
        csa_service.save_csas(csas);

        auto currencies = req->eligible_currencies;
        for (auto& row : currencies)
            apply_intent(row);
        service::csa_eligible_currency_service eligible_currency_service(uctx);
        eligible_currency_service.save_csa_eligible_currencies(currencies);

        uow.commit();

        // Read the row back from the committed transaction, so the reply
        // carries the version and the audit stamps the store gave it.
        service::counterparty_service reader(req_ctx);
        auto written = reader.find_counterparty(counterparty.id);
        BOOST_LOG_SEV(counterparty_handler_lg(), debug) << "Completed " << msg.subject;
        reply(nats_,
              msg,
              put_counterparty_composite_response{
                  .result = {},
                  .counterparty = written ? std::move(*written) : std::move(counterparty)});
    } catch (const std::exception& e) {
        // The store refused one of the rows and the transaction is gone
        // with it. The reason is the store's own words, so nothing here
        // states a code the catalogue could own instead.
        BOOST_LOG_SEV(counterparty_handler_lg(), error)
            << msg.subject << " failed: " << e.what();
        put_counterparty_composite_response failure;
        failure.result.outcome = ores::utility::domain::outcome::failed;
        failure.result.code = "internal_error";
        failure.result.message = e.what();
        reply(nats_, msg, failure);
    }
}

7.11. Custom NATS subscriptions

Hand-crafted subject wired into the generated counterparty_registrar.cpp via the nats-sub-registrar facet's custom-subscription paste point.

subs.push_back(
    nats.queue_subscribe(get_counterparty_composite_as_of_request::nats_subject,
                         counterparty_queue_group,
                         [h](ores::nats::message msg) { h->composite_as_of(std::move(msg)); }));
subs.push_back(
    nats.queue_subscribe(put_counterparty_composite_request::nats_subject,
                         counterparty_queue_group,
                         [h](ores::nats::message msg) { h->put_composite(std::move(msg)); }));

8. See also

Emacs 29.3 (Org mode 9.6.15)