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
- ores.refdata — component group overview.