Skip to content

Parties Procedures

1. Executive Summary

This document defines the step-by-step data mutation procedures for the parties domain in Client Processing. The parties domain manages the master records for all entities (individuals, companies, loan-out corporations) that participate in the financial flow: clients, agents, brokers, managers, lawyers, business managers, buyers, and other deal participants.

Parties are central to the payment lifecycle. A party's bank account determines how outbound payments are routed. A party's role on a deal determines their share of PAY settlements. A party's tax profile determines withholding obligations. Every downstream financial operation — from cash receipt application through settlement to outbound payment execution — depends on accurate, complete party data.

Tables in scope:

TablePurpose
partyCore identity record for any person or organization
party_roleTime-bounded role assignments for a party (e.g., Agent, Client)
party_bank_accountLinks a party to a bank account with payment preferences
party_addressesLinks a party to address records with type classification
deal_partyBridge table linking parties to deals with commission terms

Key downstream dependencies:

  • participant_settlement_item references party.party_id as payment_party_id
  • participant_settlement_item references bank_account.bank_account_id as payment_party_bank_id (resolved through party_bank_account)
  • payment_item receives beneficiary details from party and party_bank_account
  • party_tax_info references party.party_id for tax withholding profiles
  • billing_item references party IDs for client, buyer, and contracted party

**PoC Artifact:** In the PoC, party records are primarily seeded from external data loads (NetSuite CSV imports, seed scripts) rather than created through a dedicated party management UI. The PoC services are read-only. Production will require full CRUD operations as specified in this document.


2. Key Procedures

2.1 Create Party

Operation: createParty

Trigger: User submits a new party creation form, or an upstream system sync identifies a party not yet present in the database.

Input Parameters:

  • firstName: String (conditional) — party.first_name; required for individuals
  • lastName: String (conditional) — party.last_name; required for individuals
  • companyName: String (conditional) — party.company_name; required for organizations
  • displayName: String (optional) — party.display_name; derived if not provided
  • fullName: String (optional) — party.full_name; derived if not provided
  • authPartyId: String (optional) — party.auth_party_id; external system identifier
  • actorId: String — identifier for the user performing the operation

Creates a new master party record representing an individual or organization.

Step 1. Validate Input

  • Source: Input parameters.
  • Action: Validate required fields before any database write.
  • Logic:
    • For individuals: require at least one of first_name or last_name.
    • For organizations: require company_name.
    • If neither individual nor organization name field is provided, reject with a validation error.

Step 2. Check for Duplicates

  • Source: party table.
  • Action: SELECT from party to detect potential duplicates before inserting.
  • Logic:
    • Search existing party records matching on auth_party_id (if provided), display_name, company_name, or first_name + last_name combination.
    • Present potential matches to the user before proceeding. Do not auto-block; surface duplicates as a warning.

Step 3. Derive Display Name and Full Name

  • Source: Input parameters.
  • Action: Compute display_name and full_name if not explicitly provided.
  • Logic:
    • For individuals: display_name = "{first_name} {last_name}" (trimmed).
    • For organizations: display_name = "{company_name}".
    • Fallback: display_name = "Unknown Party".
    • full_name defaults to the same derived value as display_name when not explicitly provided.

Step 4. Insert Party Record

  • Source: Validated and derived input.
  • Action: INSERT a new row into party.
  • Logic:
    • Set party.first_name, party.last_name, party.company_name from input.
    • Set party.display_name and party.full_name from derived values (Step 3) or explicit input.
    • Set party.auth_party_id from input (may be null for internally-created parties).
    • Set party.active_ind = true.
    • Set party.created_by and party.updated_by = actorId.
    • Set party.created_dt and party.updated_dt = current timestamp.
    • Return the generated party_id.

IMPORTANT

The auth_party_id field links the CP party record to an external authoritative system. When parties are sourced from an upstream system, this field must be populated to enable cross-system reconciliation and prevent duplicate creation on subsequent syncs.

Side-effects:

  • No child records (roles, bank accounts, addresses) are auto-created. These must be added via their respective procedures.

Postconditions:

  • A new row exists in party with active_ind = true.
  • The generated party_id is returned to the caller.

2.2 Update Party Details

Operation: updateParty

Trigger: User submits an edit to an existing party's name, external reference, or active status.

Input Parameters:

  • partyId: Integer — party.party_id; the party to update
  • firstName: String (optional) — party.first_name
  • lastName: String (optional) — party.last_name
  • companyName: String (optional) — party.company_name
  • displayName: String (optional) — party.display_name; re-derived if omitted and name components changed
  • fullName: String (optional) — party.full_name; re-derived if omitted and name components changed
  • authPartyId: String (optional) — party.auth_party_id; only updated when correcting a linkage error
  • activeInd: Boolean (optional) — party.active_ind
  • actorId: String — identifier for the user performing the operation

Modifies mutable fields on an existing party record.

Step 1. Resolve Party

  • Source: party table.
  • Action: SELECT from party where party.party_id = partyId.
  • Logic:
    • If not found, return error.

Step 2. Validate Changes

  • Source: Input parameters and resolved party record.
  • Action: Confirm that after the update the party still has at least one identifying name field.
  • Logic:
    • After applying changes, at least one of display_name, company_name, or first_name/last_name must remain populated.
    • If none would remain, reject the update.

Step 3. Re-derive Display Name

  • Source: Input parameters.
  • Action: Compute new display_name and full_name when name component fields are changed without an explicit display_name in the input.
  • Logic:
    • If any of first_name, last_name, or company_name is being changed and display_name is not explicitly included in the update, re-derive display_name using the same priority chain as creation (see Section 4.1).
    • Apply the same derivation logic to full_name if not explicitly provided.

Step 4. Update Party Record

  • Source: Resolved party record and validated input.
  • Action: UPDATE party where party.party_id = partyId.
  • Logic:
    • Set each provided mutable field: first_name, last_name, company_name, full_name, display_name, active_ind, auth_party_id.
    • Set party.updated_by = actorId.
    • Set party.updated_dt = current timestamp.
    • Return the updated party record.

WARNING

Deactivating a party (active_ind = false) does not cascade to child records. Downstream consumers must check party.active_ind independently. A deactivated party should not be selectable for new deal party assignments or new settlement items, but existing records referencing the party remain valid.

Side-effects:

  • display_name is used as the resolved label in settlement defaults, payment item beneficiary names, and search results. Changes propagate on next read — no denormalized copies need updating.

Postconditions:

  • The party record reflects the submitted changes.
  • party.updated_dt is set to the current timestamp.

2.3 Add Party Role

Operation: addPartyRole

Trigger: User assigns a new functional role to a party (e.g., adding a Client role to a new artist record).

Input Parameters:

  • partyId: Integer — party_role.party_id; the party receiving the role
  • partyRoleTypeCd: String — party_role.party_role_type_cd; validated against code_master
  • partyRoleStartDt: Date — party_role.party_role_start_dt; effective start date
  • partyRoleEndDt: Date (optional) — party_role.party_role_end_dt; null for open-ended
  • actorId: String — identifier for the user performing the operation

Assigns a typed, time-bounded role to a party. Roles classify what function a party serves in the system (e.g., Client, Agent, Broker, Manager).

Step 1. Resolve Party

  • Source: party table.
  • Action: SELECT from party where party.party_id = partyId.
  • Logic:
    • If not found, return error.

Step 2. Validate Role Type Code

  • Source: code_master table.
  • Action: SELECT from code_master where code_master_type = 'PARTY_ROLE_TYPE_CD' and code_master_cd = partyRoleTypeCd.
  • Logic:
    • If no matching code master entry exists, return a validation error.
    • Known role type codes include CLIENT, CLNT, LO, AGT, MGR, ATT, BM, and others. See Parties Data Model Section 5.1 for the full list.

**PoC Artifact:** The PoC uses inconsistent role codes — some seed scripts use `'Agent'`, `'Broker'`, `'Manager'` while others use `'CLNT'`, `'LO'`, `'CLIENT'`. Production should standardize on a single code set in `code_master` with `code_master_type = 'PARTY_ROLE_TYPE_CD'`.

Step 3. Check for Duplicate Active Role

  • Source: party_role table.
  • Action: SELECT from party_role where party_role.party_id = partyId and party_role.party_role_type_cd = partyRoleTypeCd and party_role.party_role_active_ind = true and party_role.party_role_end_dt is null or in the future.
  • Logic:
    • If an active role of the same type already exists, either reject the request or end-date the existing role before inserting the new one.

Step 4. Insert Party Role

  • Source: Validated input.
  • Action: INSERT a new row into party_role.
  • Logic:
    • Set party_role.party_id = partyId.
    • Set party_role.party_role_type_cd = partyRoleTypeCd.
    • Set party_role.party_role_start_dt = partyRoleStartDt (or current date if not provided).
    • Set party_role.party_role_end_dt = partyRoleEndDt (null for open-ended).
    • Set party_role.party_role_active_ind = true.
    • Set party_role.created_by and party_role.updated_by = actorId.
    • Set party_role.created_dt and party_role.updated_dt = current timestamp.
    • Return the generated party_role_id.

Side-effects:

  • None. Adding a role does not affect the parent party record or other role records.

Postconditions:

  • A new party_role row exists with party_role_active_ind = true.
  • A party can hold multiple role types simultaneously — adding one role does not affect others.

2.4 Remove Party Role

Operation: removePartyRole

Trigger: User terminates a party's role assignment (e.g., removing an Agent role when a party is no longer acting as an agent).

Input Parameters:

  • partyRoleId: Integer — party_role.party_role_id; the role assignment to deactivate
  • actorId: String — identifier for the user performing the operation

Deactivates a party role. Roles are soft-deleted by end-dating and deactivating — never physically deleted.

Step 1. Resolve Party Role

  • Source: party_role table.
  • Action: SELECT from party_role where party_role.party_role_id = partyRoleId.
  • Logic:
    • If not found, return error.

Step 2. Validate Role Is Active

  • Source: Resolved party_role record.
  • Action: Confirm party_role.party_role_active_ind = true.
  • Logic:
    • If already inactive, return error (idempotency violation).

Step 3. Check for Downstream Dependencies

  • Source: deal_party and deal tables.
  • Action: SELECT from deal_party joined to deal where deal_party.party_id matches the role's party and deal_party.party_role_type_cd = the role's party_role_type_cd and deal.active_ind = true.
  • Logic:
    • If active deal linkages exist for this role type, warn or reject the deactivation to prevent orphaned deal party records.

Step 4. Deactivate Party Role

  • Source: Resolved party_role record.
  • Action: UPDATE party_role where party_role.party_role_id = partyRoleId.
  • Logic:
    • Set party_role.party_role_active_ind = false.
    • Set party_role.party_role_end_dt = current date (if currently null).
    • Set party_role.updated_by = actorId.
    • Set party_role.updated_dt = current timestamp.

Side-effects:

  • None. Deactivating one role does not affect the party's other role records or the parent party.active_ind.

Postconditions:

  • party_role.party_role_active_ind = false.
  • party_role.party_role_end_dt is set to the deactivation date.
  • Historical record is preserved; no physical deletion occurs.

2.5 Create Party Bank Account

Operation: createPartyBankAccount

Trigger: User links a party to a bank account and sets their payment delivery preferences.

Input Parameters:

  • partyId: Integer — party_bank_account.party_id; the party receiving the bank account link
  • partyBankTypeCd: String — party_bank_account.party_bank_type_cd; e.g., 'PRIMARY', 'CHECKING', 'SAVINGS'
  • bankAccountId: Integer (optional) — party_bank_account.bank_account_id; FK to existing bank_account; if absent, a new bank_account is created
  • bankAccountName: String (conditional) — bank_account.bank_account_name; required if creating a new bank account
  • bankAccountRoutingNo: String (conditional) — bank_account.bank_account_routing_no; required if creating a new bank account
  • bankAccountNo: String (conditional) — bank_account.bank_account_no; required if creating a new bank account
  • currencyCd: String (conditional) — bank_account.currency_cd; ISO 4217 code; required if creating a new bank account
  • countryCd: String (conditional) — bank_account.country_cd; ISO 3166 code; required if creating a new bank account
  • partyBankAccountStartDt: Date — party_bank_account.party_bank_account_start_dt
  • partyBankAccountEndDt: Date (optional) — party_bank_account.party_bank_account_end_dt
  • preferredPaymentMethod: String — party_bank_account.preferred_payment_method; 'WIRE' or 'ACH'
  • actorId: String — identifier for the user performing the operation

Links a party to a bank account record and sets their payment preferences. This linkage is critical for outbound payment processing — the preferred_payment_method on this record determines whether payments are sent via WIRE or ACH.

Step 1. Resolve Party

  • Source: party table.
  • Action: SELECT from party where party.party_id = partyId.
  • Logic:
    • If not found, return error.

Step 2. Resolve or Create Bank Account

  • Source: bank_account table.
  • Action: If bankAccountId is provided and the account exists, use it. Otherwise INSERT a new row into bank_account.
  • Logic:
    • For a new bank_account: set bank_account_name, bank_account_routing_no, bank_account_no, currency_cd, country_cd from input.
    • Set bank_account.active_ind = true.
    • Set bank_account.is_uta_account = false (party accounts are always external).
    • Set bank_account.created_by = actorId and bank_account.created_dt = current timestamp.

Step 3. Check for Duplicate Linkage

  • Source: party_bank_account table.
  • Action: SELECT from party_bank_account where party_bank_account.party_id = partyId and party_bank_account.bank_account_id = resolved bankAccountId and party_bank_account.active_ind = true.
  • Logic:
    • If an active linkage already exists between this party and bank account, reject as duplicate.

Step 4. Insert Party Bank Account

  • Source: Resolved party, resolved or newly created bank account, and validated input.
  • Action: INSERT a new row into party_bank_account.
  • Logic:
    • Set party_bank_account.party_id = partyId.
    • Set party_bank_account.party_bank_type_cd = partyBankTypeCd.
    • Set party_bank_account.bank_account_id = resolved bank_account.bank_account_id.
    • Set party_bank_account.party_bank_account_start_dt = partyBankAccountStartDt (or current date).
    • Set party_bank_account.party_bank_account_end_dt = partyBankAccountEndDt (null for open-ended).
    • Set party_bank_account.active_ind = true.
    • Set party_bank_account.preferred_payment_method = preferredPaymentMethod; must be 'WIRE' or 'ACH'; default to 'ACH' if not specified.
    • Set party_bank_account.created_by and party_bank_account.updated_by = actorId.
    • Set party_bank_account.created_dt and party_bank_account.updated_dt = current timestamp.
    • Return the generated party_bank_account_id.

IMPORTANT

The preferred_payment_method field directly drives the service level selection in outbound payment processing. When set to 'WIRE', the payment processor sends via wire transfer. When set to 'ACH' (the default), it sends via ACH. This field is read by the payment processor during payment preparation to set the service level on the outbound payment execution request.

WARNING

The bank_account.is_uta_account flag must be false for party (external) bank accounts. UTA's own bank accounts (is_uta_account = true) are used as source accounts for outbound payments and deposit accounts for inbound receipts. Mixing these up would route payments incorrectly.

Side-effects:

  • If a new bank_account row was created, it becomes available for reference by other parties and deal-party overrides.

Postconditions:

  • A new party_bank_account row exists with active_ind = true.
  • The party's active bank account linkages now include this new record.
  • The new linkage is eligible for settlement defaults resolution.

2.6 Update Party Bank Account

Operation: updatePartyBankAccount

Trigger: User modifies payment preferences, effective dates, or the underlying bank account on an existing party-bank-account linkage.

Input Parameters:

  • partyBankAccountId: Integer — party_bank_account.party_bank_account_id; the linkage to update
  • partyBankTypeCd: String (optional) — party_bank_account.party_bank_type_cd
  • bankAccountId: Integer (optional) — party_bank_account.bank_account_id; to change the underlying account
  • partyBankAccountStartDt: Date (optional) — party_bank_account.party_bank_account_start_dt
  • partyBankAccountEndDt: Date (optional) — party_bank_account.party_bank_account_end_dt; setting this effectively deactivates
  • activeInd: Boolean (optional) — party_bank_account.active_ind
  • preferredPaymentMethod: String (optional) — party_bank_account.preferred_payment_method; 'WIRE' or 'ACH'
  • actorId: String — identifier for the user performing the operation

Modifies the payment preferences or effective dates on an existing party-bank-account linkage.

Step 1. Resolve Party Bank Account

  • Source: party_bank_account table.
  • Action: SELECT from party_bank_account where party_bank_account.party_bank_account_id = partyBankAccountId.
  • Logic:
    • If not found, return error.

Step 2. Validate Changes

  • Source: Input parameters.
  • Action: Validate preferred_payment_method if being changed.
  • Logic:
    • If preferred_payment_method is provided, it must be exactly 'WIRE' or 'ACH'.
    • Any other value is rejected.

Step 3. Update Party Bank Account

  • Source: Resolved party_bank_account record and validated input.
  • Action: UPDATE party_bank_account where party_bank_account.party_bank_account_id = partyBankAccountId.
  • Logic:
    • Set each provided mutable field: party_bank_type_cd, bank_account_id, party_bank_account_start_dt, party_bank_account_end_dt, active_ind, preferred_payment_method.
    • Set party_bank_account.updated_by = actorId.
    • Set party_bank_account.updated_dt = current timestamp.
    • Return the updated record.

WARNING

Changing the bank_account_id or deactivating a party_bank_account record does not retroactively affect existing payment items or settlement items that already reference the prior bank account. Those records are immutable once created. The change only affects future settlement defaults and payment item creation.

Side-effects:

  • None on historical records. Only future settlement defaults and payment item creation are affected.

Postconditions:

  • The party_bank_account record reflects the submitted changes.
  • party_bank_account.updated_dt is set to the current timestamp.

2.7 Deactivate Party Bank Account

Operation: deactivatePartyBankAccount

Trigger: User closes or replaces a party's bank account linkage (e.g., account closed, replaced with a new account).

Input Parameters:

  • partyBankAccountId: Integer — party_bank_account.party_bank_account_id; the linkage to deactivate
  • actorId: String — identifier for the user performing the operation

End-dates and deactivates a party bank account linkage. Bank account linkages are soft-deleted — never physically removed.

Step 1. Resolve Party Bank Account

  • Source: party_bank_account table.
  • Action: SELECT from party_bank_account where party_bank_account.party_bank_account_id = partyBankAccountId.
  • Logic:
    • If not found, return error.

Step 2. Check for Pending Payments

  • Source: participant_settlement_item joined to payment_item.
  • Action: SELECT from participant_settlement_item joined to payment_item where participant_settlement_item.payment_party_bank_id = the linkage's bank_account_id and payment_item.payment_execution_status_cd IN ('WAITING', 'PENDING', 'PROCESSING').
  • Logic:
    • If any pending payments exist against this bank account, warn the user that deactivation may disrupt in-flight payments.
    • The system does not block deactivation, but the user should be informed.

Step 3. Deactivate Party Bank Account

  • Source: Resolved party_bank_account record.
  • Action: UPDATE party_bank_account where party_bank_account.party_bank_account_id = partyBankAccountId.
  • Logic:
    • Set party_bank_account.active_ind = false.
    • Set party_bank_account.party_bank_account_end_dt = current date (if currently null).
    • Set party_bank_account.updated_by = actorId.
    • Set party_bank_account.updated_dt = current timestamp.

Side-effects:

  • Settlement defaults resolution filters party_bank_account by active_ind = true. Deactivated accounts will no longer appear as default bank account options for new settlements.
  • Existing deal_party.bank_account_id references are not updated. These should be reviewed separately.

Postconditions:

  • party_bank_account.active_ind = false.
  • party_bank_account.party_bank_account_end_dt is set to the deactivation date.
  • Historical record is preserved; no physical deletion occurs.

2.8 Create Party Address

Operation: createPartyAddress

Trigger: User links a party to a physical address and classifies its purpose.

Input Parameters:

  • partyId: Integer — party_addresses.party_id; the party receiving the address link
  • partyAddressesTypeCd: String — party_addresses.party_addresses_type_cd; validated against code_master
  • primaryInd: Boolean — party_addresses.primary_ind; whether this is the primary address for this type
  • authAddressId: String (optional) — party_addresses.auth_address_id; external system address identifier
  • addressLine1: String (conditional) — address.address_line_1; required if creating a new address record
  • addressLine2: String (optional) — address.address_line_2
  • city: String (conditional) — address.city; required if creating a new address record
  • stateProvince: String (optional) — address.state_province
  • postalCode: String (conditional) — address.postal_code; required if creating a new address record
  • countryCd: String (conditional) — address.country_cd; ISO 3166 code; required if creating a new address record
  • actorId: String — identifier for the user performing the operation

Links a party to an address record, classifying its purpose (e.g., home, office, billing).

Step 1. Resolve Party

  • Source: party table.
  • Action: SELECT from party where party.party_id = partyId.
  • Logic:
    • If not found, return error.

Step 2. Resolve or Create Address

  • Source: address table.
  • Action: If a physical address already exists in address, use its address_id. Otherwise INSERT a new row into address.
  • Logic:
    • For a new address: set address_line_1, address_line_2, address_line_3, city, state_province, postal_code, country_cd from input.
    • Set address.created_by = actorId and address.created_dt = current timestamp.

Step 3. Handle Primary Address Logic

  • Source: party_addresses table.
  • Action: If primaryInd = true, UPDATE existing party_addresses records.
  • Logic:
    • If the new address is being set as primary, first set primary_ind = false on all existing party_addresses records for the same party_id and party_addresses_type_cd.
    • This enforces the single-primary-per-type constraint.

Step 4. Insert Party Address Bridge Record

  • Source: Resolved party, resolved or newly created address, and validated input.
  • Action: INSERT a new row into party_addresses.
  • Logic:
    • Set party_addresses.party_id = partyId.
    • Set party_addresses.party_addresses_type_cd = partyAddressesTypeCd.
    • Set party_addresses.primary_ind = primaryInd (default false).
    • Set party_addresses.auth_address_id = authAddressId (may be null).
    • Set party_addresses.created_by and party_addresses.updated_by = actorId.
    • Set party_addresses.created_dt and party_addresses.updated_dt = current timestamp.
    • Return the generated party_addresses_id.

NOTE

The party_addresses table is a bridge table. The actual street address, city, postal code, and country are stored in the shared address table. The party_addresses_type_cd field classifies the address's purpose for this specific party. The same physical address row can be linked to multiple parties or serve multiple purposes via different bridge records.

**PoC Artifact:** The PoC `DrizzlePartyAddressesRepository.getByPartyId()` method currently returns an empty array with a TODO comment indicating the `party_id` field lookup is not yet implemented. Production must implement the proper query using `party_addresses.party_id`.

Side-effects:

  • If primaryInd = true, all other party_addresses records for the same party and type code have their primary_ind set to false.

Postconditions:

  • A new party_addresses bridge row exists.
  • At most one party_addresses record per party_id + party_addresses_type_cd combination has primary_ind = true.

2.9 Update Party Address

Operation: updatePartyAddress

Trigger: User changes the type classification, primary designation, or external reference on an existing party address linkage.

Input Parameters:

  • partyAddressesId: Integer — party_addresses.party_addresses_id; the bridge record to update
  • partyAddressesTypeCd: String (optional) — party_addresses.party_addresses_type_cd; new type classification
  • primaryInd: Boolean (optional) — party_addresses.primary_ind
  • authAddressId: String (optional) — party_addresses.auth_address_id; correct external linkage
  • actorId: String — identifier for the user performing the operation

Modifies the type classification, primary indicator, or external reference on a party address linkage.

Step 1. Resolve Party Address

  • Source: party_addresses table.
  • Action: SELECT from party_addresses where party_addresses.party_addresses_id = partyAddressesId.
  • Logic:
    • If not found, return error.

Step 2. Handle Primary Address Change

  • Source: party_addresses table.
  • Action: If primaryInd is being set to true, UPDATE existing sibling records first.
  • Logic:
    • Update all other party_addresses records for the same party_id and party_addresses_type_cd to set primary_ind = false.
    • Use the resolved record's party_id and either the new or existing party_addresses_type_cd.

Step 3. Update Party Address Bridge Record

  • Source: Resolved party_addresses record and validated input.
  • Action: UPDATE party_addresses where party_addresses.party_addresses_id = partyAddressesId.
  • Logic:
    • Set each provided field: party_addresses_type_cd, primary_ind, auth_address_id.
    • Set party_addresses.updated_by = actorId.
    • Set party_addresses.updated_dt = current timestamp.
    • Return the updated record.

NOTE

To change the underlying physical address (street, city, etc.), update the address table directly. This is a separate operation that affects all entities linked to that address record.

Side-effects:

  • If primaryInd is set to true, sibling party_addresses records for the same type have their primary_ind cleared.

Postconditions:

  • The party_addresses bridge record reflects the submitted changes.
  • At most one party_addresses record per party_id + party_addresses_type_cd combination has primary_ind = true.

Operation: createDealParty

Trigger: User adds a party (e.g., manager, lawyer, business manager) to a deal's participant list, specifying their role and commission terms.

Input Parameters:

  • dealId: Integer — deal_party.deal_id; the deal to link the party to
  • partyId: Integer — deal_party.party_id; the party being added
  • partyRoleTypeCd: String — deal_party.party_role_type_cd; validated against code_master
  • dealPartyCommissionFlatInd: Boolean — deal_party.deal_party_commission_flat_ind; true = flat dollar, false = percentage
  • dealPartyCommissionPerc: String (conditional) — deal_party.deal_party_commission_perc; decimal(7,4); required when flatInd = false
  • dealPartyCommissionAmt: String (conditional) — deal_party.deal_party_commission_amt; decimal(15,2); required when flatInd = true
  • bankAccountId: Integer (optional) — deal_party.bank_account_id; deal-level bank account override
  • actorId: String — identifier for the user performing the operation

Creates a deal_party record that associates a party with a deal, specifying their role and commission terms. This is a critical setup step — deal parties drive settlement defaults and determine who receives PAY distributions.

Step 1. Resolve Deal

  • Source: deal table.
  • Action: SELECT from deal where deal.deal_id = dealId.
  • Logic:
    • If not found, return error.

Step 2. Resolve Party

  • Source: party table.
  • Action: SELECT from party where party.party_id = partyId.
  • Logic:
    • If not found, return error.
    • If party.active_ind = false, return error (inactive parties cannot be added to deals).

Step 3. Validate Role Type Code

  • Source: code_master table.
  • Action: SELECT from code_master where code_master_type = 'PARTY_ROLE_TYPE_CD' and code_master_cd = partyRoleTypeCd.
  • Logic:
    • If no matching code master entry exists, return validation error.
    • Ideally, verify the party holds an active party_role with the same party_role_type_cd.

Step 4. Validate Commission Fields

  • Source: Input parameters.
  • Action: Enforce mutual exclusivity between flat and percentage commission modes.
  • Logic:
    • If deal_party_commission_flat_ind = true: deal_party_commission_amt must be provided and > 0. Treat deal_party_commission_perc as null or 0.
    • If deal_party_commission_flat_ind = false: deal_party_commission_perc must be provided and >= 0. Treat deal_party_commission_amt as null or 0.
    • Blank strings and nulls for either commission field are normalized to "0" before persisting.
    • If neither commission field is provided, default both to "0".

Step 5. Resolve Bank Account

  • Source: bank_account table.
  • Action: If bankAccountId is provided, SELECT from bank_account where bank_account.bank_account_id = bankAccountId.
  • Logic:
    • If provided but not found, return error.
    • This is typically the bank_account_id from the party's active party_bank_account record, but may be any valid bank_account.

Step 6. Check for Duplicate

  • Source: deal_party table.
  • Action: SELECT from deal_party where deal_party.deal_id = dealId and deal_party.party_id = partyId.
  • Logic:
    • If a record already exists, either reject as duplicate or update the existing record.

Step 7. Insert Deal Party

  • Source: Validated input and resolved records.
  • Action: INSERT a new row into deal_party.
  • Logic:
    • Set deal_party.deal_id = dealId.
    • Set deal_party.party_id = partyId.
    • Set deal_party.party_role_type_cd = partyRoleTypeCd.
    • Set deal_party.deal_party_commission_flat_ind = dealPartyCommissionFlatInd.
    • Set deal_party.deal_party_commission_perc = normalized percentage value (or "0").
    • Set deal_party.deal_party_commission_amt = normalized amount value (or "0").
    • Set deal_party.bank_account_id = resolved bankAccountId (may be null).
    • Set deal_party.created_by and deal_party.updated_by = actorId.
    • Set deal_party.created_dt and deal_party.updated_dt = current timestamp.
    • Return the generated deal_party_id.

Side-effects:

  • When settlement defaults are resolved for a worksheet, the system queries deal_party joined to party and party_bank_account (where active_ind = true) to build the party list with commission terms and bank account details. This new record will appear in those defaults.
  • The party_role_type_cd on deal_party appears in settlement UIs and reporting, joined to code_master to resolve a human-readable description.

Postconditions:

  • A new deal_party row exists linking the party to the deal.
  • The party will appear in settlement default lookups for worksheets associated with this deal.

2.11 Update Deal Party

Operation: updateDealParty

Trigger: User changes commission terms, role, or bank account on an existing deal-party linkage.

Input Parameters:

  • dealPartyId: Integer — deal_party.deal_party_id; the linkage to update
  • partyRoleTypeCd: String (optional) — deal_party.party_role_type_cd
  • dealPartyCommissionFlatInd: Boolean (optional) — deal_party.deal_party_commission_flat_ind
  • dealPartyCommissionPerc: String (optional) — deal_party.deal_party_commission_perc; decimal(7,4)
  • dealPartyCommissionAmt: String (optional) — deal_party.deal_party_commission_amt; decimal(15,2)
  • bankAccountId: Integer (optional) — deal_party.bank_account_id
  • actorId: String — identifier for the user performing the operation

Modifies commission terms, role, or bank account on an existing deal-party linkage.

Step 1. Resolve Deal Party

  • Source: deal_party table.
  • Action: SELECT from deal_party where deal_party.deal_party_id = dealPartyId.
  • Logic:
    • If not found, return error.

Step 2. Validate Commission Fields

  • Source: Input parameters.
  • Action: Apply the same mutual exclusivity rules as creation (see Section 2.10, Step 4).
  • Logic:
    • Blank strings and nulls for deal_party_commission_perc and deal_party_commission_amt are normalized to "0" before persisting.

Step 3. Update Deal Party

  • Source: Resolved deal_party record and validated input.
  • Action: UPDATE deal_party where deal_party.deal_party_id = dealPartyId.
  • Logic:
    • Set each provided mutable field: party_role_type_cd, deal_party_commission_flat_ind, deal_party_commission_perc, deal_party_commission_amt, bank_account_id.
    • Set deal_party.updated_by = actorId.
    • Set deal_party.updated_dt = current timestamp.
    • Return the updated record.

WARNING

Changing commission terms on a deal party does not retroactively alter existing settlements or payment items. Those are point-in-time records created when the settlement was saved. Changes only affect future settlement defaults.

Side-effects:

  • None on historical records. Only future settlement defaults are affected.

Postconditions:

  • The deal_party record reflects the submitted changes.
  • deal_party.updated_dt is set to the current timestamp.

2.12 Remove Deal Party

Operation: deleteDealParty

Trigger: User removes a party from a deal's participant list.

Input Parameters:

  • dealPartyId: Integer — deal_party.deal_party_id; the linkage to delete
  • actorId: String — identifier for the user performing the operation

Removes a party from a deal. Unlike party roles and bank accounts, deal party records are physically deleted (hard delete).

Step 1. Resolve Deal Party

  • Source: deal_party table.
  • Action: SELECT from deal_party where deal_party.deal_party_id = dealPartyId.
  • Logic:
    • If not found, return error.

Step 2. Check for Downstream References

  • Source: participant_settlement_item table.
  • Action: SELECT from participant_settlement_item where participant_settlement_item.payment_party_id matches the deal party's party_id and the related settlement's worksheet references this deal.
  • Logic:
    • If active settlements exist that reference this party on this deal, warn or reject the deletion to prevent orphaned settlement items.

Step 3. Delete Deal Party

  • Source: Resolved deal_party record.
  • Action: DELETE from deal_party where deal_party.deal_party_id = dealPartyId.
  • Logic:
    • Execute a hard delete. No soft-delete/end-dating pattern applies here.

Side-effects:

  • The party no longer appears in settlement default lookups for worksheets on this deal.
  • Existing participant_settlement_item records that reference the party's party_id are not affected — they reference party directly, not deal_party.

Postconditions:

  • The deal_party row is permanently removed from the database.
  • The party still exists in party and retains all other deal linkages and roles.

2.13 Upsert Deal with Parties (Transactional)

Operation: upsertDeal

Trigger: User saves a deal from the Deal Management UI, submitting the deal's metadata and its complete current party list in a single operation.

Input Parameters:

  • dealId: Integer (optional) — deal.deal_id; present for updates, absent for creates
  • dealName: String (conditional) — deal.deal_name; required for new deals
  • dealReference: String (optional) — deal.deal_reference; auto-generated if not provided on create
  • parties: Array of deal party objects — each contains dealPartyId (optional), partyId, partyRoleTypeCd, commission fields, bankAccountId (optional), and isDeleted flag
  • actorId: String — identifier for the user performing the operation

A composite procedure that creates or updates a deal along with its full set of deal parties in a single database transaction. This is the primary mutation path used by the Deal Management UI.

Step 1. Begin Transaction

  • Source:
  • Action: Open a database transaction. All subsequent steps run within this transaction.
  • Logic:
    • If any step fails, roll back the entire transaction. Neither the deal nor any party changes are persisted on failure.

Step 2. Upsert Deal Record

  • Source: Input parameters.
  • Action: INSERT or UPDATE deal.
  • Logic:
    • If dealId is provided: UPDATE deal setting updated_dt = now().
    • If dealId is absent: INSERT into deal. deal_name is required. If deal_reference is not provided, auto-generate one (e.g., DEAL-{random alphanumeric}). Return the newly generated deal_id.

Step 3. Load Existing Parties

  • Source: deal_party table.
  • Action: SELECT all existing deal_party records for the resolved deal_id.
  • Logic:
    • Build a set of existing deal_party_id values to compare against the submitted list.

Step 4. Process Submitted Party List

  • Source: Input parties array.
  • Action: For each party object in the submitted array, apply the appropriate INSERT, UPDATE, or DELETE.
  • Logic:
    • Ensure deal_id is set to the resolved deal ID on every party record.
    • Normalize deal_party_commission_perc and deal_party_commission_amt: blank strings or nulls become "0".
    • If deal_party_id is present and is_deleted = true: DELETE from deal_party (hard delete).
    • If deal_party_id is present and is_deleted = false: UPDATE the existing deal_party record.
    • If deal_party_id is absent and is_deleted = false: INSERT a new deal_party record.

Step 5. Handle Implicit Deletions

  • Source: Set of existing deal_party_id values (from Step 3) and processed IDs (from Step 4).
  • Action: DELETE any deal_party rows whose deal_party_id was not present in the submitted list.
  • Logic:
    • The submitted party list is treated as authoritative. Any existing deal_party records not represented in the submission (and not explicitly marked as deleted) are removed.
    • This ensures the deal_party set exactly matches what the user configured.

Step 6. Commit Transaction

  • Source:
  • Action: Commit the database transaction.
  • Logic:
    • All INSERTs, UPDATEs, and DELETEs from Steps 2–5 are atomically committed.

IMPORTANT

The entire deal + parties upsert runs within a single database transaction. If any step fails (e.g., invalid party ID, constraint violation), the entire operation rolls back — neither the deal nor any party changes are persisted.

Side-effects:

  • Settlement default lookups for worksheets associated with this deal are immediately updated to reflect the new party set.
  • Deleted deal parties no longer appear in settlement defaults for this deal.

Postconditions:

  • The deal record is created or updated.
  • The deal_party set for this deal exactly matches the submitted party list.
  • The generated deal_id (for new deals) and the saved deal record are returned.

3. Business Rules & Logic

3.1 Role Code Validation

Business rule: Party role type codes on both party_role and deal_party must match entries in the code_master reference table.

Data-level enforcement:

  • Read: code_master where code_master_type = 'PARTY_ROLE_TYPE_CD' and code_master_cd = input code.
  • Guard: If no matching code_master entry exists, reject the insert or update with a validation error.
  • Write: party_role.party_role_type_cd and deal_party.party_role_type_cd store the validated code value.

**PoC Artifact:** The PoC does not enforce strict code_master validation at the service or repository layer. Seed scripts insert role codes directly without validation. Production should enforce lookup validation on all insert and update operations.


3.2 Bank Account Validation Rules

Business rule: Party bank accounts must reference external (non-UTA) bank accounts, and the preferred_payment_method must be a valid payment delivery mode.

Data-level enforcement:

  • Read: party_bank_account joined to bank_account where party_bank_account.active_ind = true. Only active linkages are considered for settlement defaults and payment processing.
  • Guard: If bank_account.is_uta_account = true, reject the linkage — UTA's own accounts cannot be used as party bank accounts.
  • Guard: If party_bank_account.preferred_payment_method is provided and is not 'WIRE' or 'ACH', reject with a validation error.
  • Write: party_bank_account.preferred_payment_method defaults to 'ACH' when not specified.

3.3 Primary Address Rules

Business rule: At most one address per party per type classification should be marked as primary.

Data-level enforcement:

  • Read: party_addresses where party_addresses.party_id = target party and party_addresses.party_addresses_type_cd = target type and party_addresses.primary_ind = true.
  • Guard: If a new address is being set as primary, first clear primary_ind on all other records for the same party and type.
  • Write: Set party_addresses.primary_ind = false on existing primary records before setting primary_ind = true on the new record.

3.4 Deal Party Commission Rules

Business rule: Commission terms on deal_party use a dual-mode model — either a percentage of the net amount or a flat dollar amount — controlled by the deal_party_commission_flat_ind flag.

Data-level enforcement:

  • Read: deal_party.deal_party_commission_flat_ind determines which field is active.
  • Guard: When flat_ind = false, deal_party_commission_perc must be a valid decimal(7,4) value. When flat_ind = true, deal_party_commission_amt must be a valid decimal(15,2) value.
  • Write: Blank strings and nulls for either commission field are normalized to "0" before database write. The non-active field should be null or zero and must not be treated as meaningful by downstream processes.

3.5 Party-to-Settlement Resolution Chain

Business rule: When creating a settlement, the system resolves party and bank information through a fixed join chain, not from ad hoc lookups.

Data-level enforcement:

  • Read: JOIN deal_partypartyparty_bank_account (where active_ind = true) → bank_account for a given deal_id.
  • Guard: If no active party_bank_account exists for a deal party, the party still appears in settlement defaults but without a bank account pre-populated. The user must supply a bank account at settlement time.
  • Write: participant_settlement_item.payment_party_id = party.party_id; participant_settlement_item.payment_party_bank_id = bank_account.bank_account_id.

3.6 Party Type Determination for Payments

Business rule: When building outbound payment payloads, the system determines party type from the party record's populated name fields.

Data-level enforcement:

  • Read: party.company_name field on the beneficiary party record.
  • Guard: None — this is a classification rule, not a validation guard.
  • Write: If party.company_name is populated, classify as 'ORGANIZATION'. Otherwise classify as 'INDIVIDUAL'. This classification affects the format of the beneficiary block in outbound payment messages (ISO 20022 pain.001 and EASI Link JSON).

4. Field Mapping & Transformation

4.1 Party Display Name Resolution

Display name resolution follows a priority chain used consistently across the system for UI labels and search results:

Source Table.FieldTarget Table.FieldTransform
party.display_nameUI labelPriority 1: use as-is if populated
party.company_nameUI labelPriority 2: use if display_name is null
party.first_name + party.last_nameUI labelPriority 3: concatenate and trim
UI labelPriority 4: default to literal string "Unknown Party"

This logic is applied in server actions that return party data for UI selection (e.g., party search, deal party listing).


4.2 Deal Party Commission Sanitization

Before persisting commission fields, blank strings and nulls are normalized to prevent database errors and ensure consistent downstream calculations.

Source Table.FieldTarget Table.FieldTransform
Input dealPartyCommissionPerc = ""deal_party.deal_party_commission_percStored as "0"
Input dealPartyCommissionPerc = nulldeal_party.deal_party_commission_percStored as "0"
Input dealPartyCommissionAmt = ""deal_party.deal_party_commission_amtStored as "0"
Input dealPartyCommissionAmt = nulldeal_party.deal_party_commission_amtStored as "0"
Any non-null numeric stringdeal_party.deal_party_commission_perc or deal_party.deal_party_commission_amtKept as-is

This sanitization occurs in the deal upsert procedure (Section 2.13, Step 4) before any database write.


4.3 Bank Account Display Format

When bank account information is displayed in settlement defaults, it is formatted for readability:

Source Table.FieldTarget Table.FieldTransform
bank_account.bank_account_name + bank_account.bank_account_noUI display string"{bank_account_name} (...{last_4_digits})"
bank_account.bank_account_no (when bank_account_name is null)UI display stringbank_account_no only, no truncation

4.4 Party Role Label Resolution

Party role codes are resolved to human-readable labels for display in settlement UIs and reporting:

Source Table.FieldTarget Table.FieldTransform
deal_party.party_role_type_cdUI labelJOIN code_master where code_master_cd = party_role_type_cd and code_master_type = 'PARTY_ROLE_TYPE_CD'; display code_master.code_master_desc
deal_party.party_role_type_cd (no code_master match)UI labelDisplay raw party_role_type_cd value as fallback

4.5 Audit Field Population

All tables in the parties domain follow the standard audit field pattern on every insert and update:

Source Table.FieldTarget Table.FieldTransform
actorId (input parameter)*.created_byCopied as-is on INSERT
Current timestamp*.created_dtSystem-generated on INSERT
actorId (input parameter)*.updated_byCopied as-is on INSERT and UPDATE
Current timestamp*.updated_dtSystem-generated on INSERT; explicitly set on UPDATE

5. Cross-References

DocumentRelationship
Parties Data ModelTable definitions, field types, relationships, status lifecycles, and code master values for all tables in scope
Parties QueriesRead queries for party search, lookup, and resolution patterns used upstream of these procedures
Settlements ProceduresHow deal parties flow into settlement creation via the party-to-settlement resolution chain (Section 3.5)
Settlements ProceduresHow party_bank_account.preferred_payment_method and party.company_name drive outbound payment formatting and routing
Tax Withholding ProceduresHow party_tax_info.party_idparty.party_id drives withholding calculations per party
Worksheets ProceduresWhere settlement defaults (derived from deal parties) are consumed during worksheet settlement
Deals, Sales Items & Payment Terms ProceduresDeal creation and lifecycle; deal_party is managed as part of the deal upsert flow
Parties Data Modelcode_master values for PARTY_ROLE_TYPE_CD, PARTY_BANK_TYPE_CD, PARTY_ADDRESSES_TYPE_CD, and CONTACT_METHOD_TYPE_CD are documented inline in the data model

Confidential. For internal use only.