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:
| Table | Purpose |
|---|---|
party | Core identity record for any person or organization |
party_role | Time-bounded role assignments for a party (e.g., Agent, Client) |
party_bank_account | Links a party to a bank account with payment preferences |
party_addresses | Links a party to address records with type classification |
deal_party | Bridge table linking parties to deals with commission terms |
Key downstream dependencies:
participant_settlement_itemreferencesparty.party_idaspayment_party_idparticipant_settlement_itemreferencesbank_account.bank_account_idaspayment_party_bank_id(resolved throughparty_bank_account)payment_itemreceives beneficiary details frompartyandparty_bank_accountparty_tax_inforeferencesparty.party_idfor tax withholding profilesbilling_itemreferences 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 individualslastName: String (conditional) —party.last_name; required for individualscompanyName: String (conditional) —party.company_name; required for organizationsdisplayName: String (optional) —party.display_name; derived if not providedfullName: String (optional) —party.full_name; derived if not providedauthPartyId: String (optional) —party.auth_party_id; external system identifieractorId: 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_nameorlast_name. - For organizations: require
company_name. - If neither individual nor organization name field is provided, reject with a validation error.
- For individuals: require at least one of
Step 2. Check for Duplicates
- Source:
partytable. - Action: SELECT from
partyto detect potential duplicates before inserting. - Logic:
- Search existing
partyrecords matching onauth_party_id(if provided),display_name,company_name, orfirst_name+last_namecombination. - Present potential matches to the user before proceeding. Do not auto-block; surface duplicates as a warning.
- Search existing
Step 3. Derive Display Name and Full Name
- Source: Input parameters.
- Action: Compute
display_nameandfull_nameif 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_namedefaults to the same derived value asdisplay_namewhen not explicitly provided.
- For individuals:
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_namefrom input. - Set
party.display_nameandparty.full_namefrom derived values (Step 3) or explicit input. - Set
party.auth_party_idfrom input (may be null for internally-created parties). - Set
party.active_ind=true. - Set
party.created_byandparty.updated_by=actorId. - Set
party.created_dtandparty.updated_dt= current timestamp. - Return the generated
party_id.
- Set
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
partywithactive_ind=true. - The generated
party_idis 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 updatefirstName: String (optional) —party.first_namelastName: String (optional) —party.last_namecompanyName: String (optional) —party.company_namedisplayName: String (optional) —party.display_name; re-derived if omitted and name components changedfullName: String (optional) —party.full_name; re-derived if omitted and name components changedauthPartyId: String (optional) —party.auth_party_id; only updated when correcting a linkage erroractiveInd: Boolean (optional) —party.active_indactorId: String — identifier for the user performing the operation
Modifies mutable fields on an existing party record.
Step 1. Resolve Party
- Source:
partytable. - Action: SELECT from
partywhereparty.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, orfirst_name/last_namemust remain populated. - If none would remain, reject the update.
- After applying changes, at least one of
Step 3. Re-derive Display Name
- Source: Input parameters.
- Action: Compute new
display_nameandfull_namewhen name component fields are changed without an explicitdisplay_namein the input. - Logic:
- If any of
first_name,last_name, orcompany_nameis being changed anddisplay_nameis not explicitly included in the update, re-derivedisplay_nameusing the same priority chain as creation (see Section 4.1). - Apply the same derivation logic to
full_nameif not explicitly provided.
- If any of
Step 4. Update Party Record
- Source: Resolved party record and validated input.
- Action: UPDATE
partywhereparty.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.
- Set each provided mutable field:
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_nameis 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
partyrecord reflects the submitted changes. party.updated_dtis 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 rolepartyRoleTypeCd: String —party_role.party_role_type_cd; validated againstcode_masterpartyRoleStartDt: Date —party_role.party_role_start_dt; effective start datepartyRoleEndDt: Date (optional) —party_role.party_role_end_dt; null for open-endedactorId: 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:
partytable. - Action: SELECT from
partywhereparty.party_id=partyId. - Logic:
- If not found, return error.
Step 2. Validate Role Type Code
- Source:
code_mastertable. - Action: SELECT from
code_masterwherecode_master_type='PARTY_ROLE_TYPE_CD'andcode_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_roletable. - Action: SELECT from
party_rolewhereparty_role.party_id=partyIdandparty_role.party_role_type_cd=partyRoleTypeCdandparty_role.party_role_active_ind=trueandparty_role.party_role_end_dtis 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_byandparty_role.updated_by=actorId. - Set
party_role.created_dtandparty_role.updated_dt= current timestamp. - Return the generated
party_role_id.
- Set
Side-effects:
- None. Adding a role does not affect the parent
partyrecord or other role records.
Postconditions:
- A new
party_rolerow exists withparty_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 deactivateactorId: 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_roletable. - Action: SELECT from
party_rolewhereparty_role.party_role_id=partyRoleId. - Logic:
- If not found, return error.
Step 2. Validate Role Is Active
- Source: Resolved
party_rolerecord. - 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_partyanddealtables. - Action: SELECT from
deal_partyjoined todealwheredeal_party.party_idmatches the role's party anddeal_party.party_role_type_cd= the role'sparty_role_type_cdanddeal.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_rolerecord. - Action: UPDATE
party_rolewhereparty_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.
- Set
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_dtis 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 linkpartyBankTypeCd: String —party_bank_account.party_bank_type_cd; e.g.,'PRIMARY','CHECKING','SAVINGS'bankAccountId: Integer (optional) —party_bank_account.bank_account_id; FK to existingbank_account; if absent, a newbank_accountis createdbankAccountName: String (conditional) —bank_account.bank_account_name; required if creating a new bank accountbankAccountRoutingNo: String (conditional) —bank_account.bank_account_routing_no; required if creating a new bank accountbankAccountNo: String (conditional) —bank_account.bank_account_no; required if creating a new bank accountcurrencyCd: String (conditional) —bank_account.currency_cd; ISO 4217 code; required if creating a new bank accountcountryCd: String (conditional) —bank_account.country_cd; ISO 3166 code; required if creating a new bank accountpartyBankAccountStartDt: Date —party_bank_account.party_bank_account_start_dtpartyBankAccountEndDt: Date (optional) —party_bank_account.party_bank_account_end_dtpreferredPaymentMethod: 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:
partytable. - Action: SELECT from
partywhereparty.party_id=partyId. - Logic:
- If not found, return error.
Step 2. Resolve or Create Bank Account
- Source:
bank_accounttable. - Action: If
bankAccountIdis provided and the account exists, use it. Otherwise INSERT a new row intobank_account. - Logic:
- For a new
bank_account: setbank_account_name,bank_account_routing_no,bank_account_no,currency_cd,country_cdfrom input. - Set
bank_account.active_ind=true. - Set
bank_account.is_uta_account=false(party accounts are always external). - Set
bank_account.created_by=actorIdandbank_account.created_dt= current timestamp.
- For a new
Step 3. Check for Duplicate Linkage
- Source:
party_bank_accounttable. - Action: SELECT from
party_bank_accountwhereparty_bank_account.party_id=partyIdandparty_bank_account.bank_account_id= resolvedbankAccountIdandparty_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= resolvedbank_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_byandparty_bank_account.updated_by=actorId. - Set
party_bank_account.created_dtandparty_bank_account.updated_dt= current timestamp. - Return the generated
party_bank_account_id.
- Set
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_accountrow was created, it becomes available for reference by other parties and deal-party overrides.
Postconditions:
- A new
party_bank_accountrow exists withactive_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 updatepartyBankTypeCd: String (optional) —party_bank_account.party_bank_type_cdbankAccountId: Integer (optional) —party_bank_account.bank_account_id; to change the underlying accountpartyBankAccountStartDt: Date (optional) —party_bank_account.party_bank_account_start_dtpartyBankAccountEndDt: Date (optional) —party_bank_account.party_bank_account_end_dt; setting this effectively deactivatesactiveInd: Boolean (optional) —party_bank_account.active_indpreferredPaymentMethod: 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_accounttable. - Action: SELECT from
party_bank_accountwhereparty_bank_account.party_bank_account_id=partyBankAccountId. - Logic:
- If not found, return error.
Step 2. Validate Changes
- Source: Input parameters.
- Action: Validate
preferred_payment_methodif being changed. - Logic:
- If
preferred_payment_methodis provided, it must be exactly'WIRE'or'ACH'. - Any other value is rejected.
- If
Step 3. Update Party Bank Account
- Source: Resolved
party_bank_accountrecord and validated input. - Action: UPDATE
party_bank_accountwhereparty_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.
- Set each provided mutable field:
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_accountrecord reflects the submitted changes. party_bank_account.updated_dtis 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 deactivateactorId: 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_accounttable. - Action: SELECT from
party_bank_accountwhereparty_bank_account.party_bank_account_id=partyBankAccountId. - Logic:
- If not found, return error.
Step 2. Check for Pending Payments
- Source:
participant_settlement_itemjoined topayment_item. - Action: SELECT from
participant_settlement_itemjoined topayment_itemwhereparticipant_settlement_item.payment_party_bank_id= the linkage'sbank_account_idandpayment_item.payment_execution_status_cdIN ('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_accountrecord. - Action: UPDATE
party_bank_accountwhereparty_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.
- Set
Side-effects:
- Settlement defaults resolution filters
party_bank_accountbyactive_ind=true. Deactivated accounts will no longer appear as default bank account options for new settlements. - Existing
deal_party.bank_account_idreferences are not updated. These should be reviewed separately.
Postconditions:
party_bank_account.active_ind=false.party_bank_account.party_bank_account_end_dtis 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 linkpartyAddressesTypeCd: String —party_addresses.party_addresses_type_cd; validated againstcode_masterprimaryInd: Boolean —party_addresses.primary_ind; whether this is the primary address for this typeauthAddressId: String (optional) —party_addresses.auth_address_id; external system address identifieraddressLine1: String (conditional) —address.address_line_1; required if creating a new address recordaddressLine2: String (optional) —address.address_line_2city: String (conditional) —address.city; required if creating a new address recordstateProvince: String (optional) —address.state_provincepostalCode: String (conditional) —address.postal_code; required if creating a new address recordcountryCd: String (conditional) —address.country_cd; ISO 3166 code; required if creating a new address recordactorId: 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:
partytable. - Action: SELECT from
partywhereparty.party_id=partyId. - Logic:
- If not found, return error.
Step 2. Resolve or Create Address
- Source:
addresstable. - Action: If a physical address already exists in
address, use itsaddress_id. Otherwise INSERT a new row intoaddress. - Logic:
- For a new
address: setaddress_line_1,address_line_2,address_line_3,city,state_province,postal_code,country_cdfrom input. - Set
address.created_by=actorIdandaddress.created_dt= current timestamp.
- For a new
Step 3. Handle Primary Address Logic
- Source:
party_addressestable. - Action: If
primaryInd=true, UPDATE existingparty_addressesrecords. - Logic:
- If the new address is being set as primary, first set
primary_ind=falseon all existingparty_addressesrecords for the sameparty_idandparty_addresses_type_cd. - This enforces the single-primary-per-type constraint.
- If the new address is being set as primary, first set
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(defaultfalse). - Set
party_addresses.auth_address_id=authAddressId(may be null). - Set
party_addresses.created_byandparty_addresses.updated_by=actorId. - Set
party_addresses.created_dtandparty_addresses.updated_dt= current timestamp. - Return the generated
party_addresses_id.
- Set
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 otherparty_addressesrecords for the same party and type code have theirprimary_indset tofalse.
Postconditions:
- A new
party_addressesbridge row exists. - At most one
party_addressesrecord perparty_id+party_addresses_type_cdcombination hasprimary_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 updatepartyAddressesTypeCd: String (optional) —party_addresses.party_addresses_type_cd; new type classificationprimaryInd: Boolean (optional) —party_addresses.primary_indauthAddressId: String (optional) —party_addresses.auth_address_id; correct external linkageactorId: 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_addressestable. - Action: SELECT from
party_addresseswhereparty_addresses.party_addresses_id=partyAddressesId. - Logic:
- If not found, return error.
Step 2. Handle Primary Address Change
- Source:
party_addressestable. - Action: If
primaryIndis being set totrue, UPDATE existing sibling records first. - Logic:
- Update all other
party_addressesrecords for the sameparty_idandparty_addresses_type_cdto setprimary_ind=false. - Use the resolved record's
party_idand either the new or existingparty_addresses_type_cd.
- Update all other
Step 3. Update Party Address Bridge Record
- Source: Resolved
party_addressesrecord and validated input. - Action: UPDATE
party_addresseswhereparty_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.
- Set each provided field:
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
primaryIndis set totrue, siblingparty_addressesrecords for the same type have theirprimary_indcleared.
Postconditions:
- The
party_addressesbridge record reflects the submitted changes. - At most one
party_addressesrecord perparty_id+party_addresses_type_cdcombination hasprimary_ind=true.
2.10 Link Party to Deal
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 topartyId: Integer —deal_party.party_id; the party being addedpartyRoleTypeCd: String —deal_party.party_role_type_cd; validated againstcode_masterdealPartyCommissionFlatInd: Boolean —deal_party.deal_party_commission_flat_ind;true= flat dollar,false= percentagedealPartyCommissionPerc: String (conditional) —deal_party.deal_party_commission_perc;decimal(7,4); required whenflatInd=falsedealPartyCommissionAmt: String (conditional) —deal_party.deal_party_commission_amt;decimal(15,2); required whenflatInd=truebankAccountId: Integer (optional) —deal_party.bank_account_id; deal-level bank account overrideactorId: 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:
dealtable. - Action: SELECT from
dealwheredeal.deal_id=dealId. - Logic:
- If not found, return error.
Step 2. Resolve Party
- Source:
partytable. - Action: SELECT from
partywhereparty.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_mastertable. - Action: SELECT from
code_masterwherecode_master_type='PARTY_ROLE_TYPE_CD'andcode_master_cd=partyRoleTypeCd. - Logic:
- If no matching code master entry exists, return validation error.
- Ideally, verify the party holds an active
party_rolewith the sameparty_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_amtmust be provided and> 0. Treatdeal_party_commission_percas null or0. - If
deal_party_commission_flat_ind=false:deal_party_commission_percmust be provided and>= 0. Treatdeal_party_commission_amtas null or0. - Blank strings and nulls for either commission field are normalized to
"0"before persisting. - If neither commission field is provided, default both to
"0".
- If
Step 5. Resolve Bank Account
- Source:
bank_accounttable. - Action: If
bankAccountIdis provided, SELECT frombank_accountwherebank_account.bank_account_id=bankAccountId. - Logic:
- If provided but not found, return error.
- This is typically the
bank_account_idfrom the party's activeparty_bank_accountrecord, but may be any validbank_account.
Step 6. Check for Duplicate
- Source:
deal_partytable. - Action: SELECT from
deal_partywheredeal_party.deal_id=dealIdanddeal_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= resolvedbankAccountId(may be null). - Set
deal_party.created_byanddeal_party.updated_by=actorId. - Set
deal_party.created_dtanddeal_party.updated_dt= current timestamp. - Return the generated
deal_party_id.
- Set
Side-effects:
- When settlement defaults are resolved for a worksheet, the system queries
deal_partyjoined topartyandparty_bank_account(whereactive_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_cdondeal_partyappears in settlement UIs and reporting, joined tocode_masterto resolve a human-readable description.
Postconditions:
- A new
deal_partyrow 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 updatepartyRoleTypeCd: String (optional) —deal_party.party_role_type_cddealPartyCommissionFlatInd: Boolean (optional) —deal_party.deal_party_commission_flat_inddealPartyCommissionPerc: 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_idactorId: 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_partytable. - Action: SELECT from
deal_partywheredeal_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_percanddeal_party_commission_amtare normalized to"0"before persisting.
- Blank strings and nulls for
Step 3. Update Deal Party
- Source: Resolved
deal_partyrecord and validated input. - Action: UPDATE
deal_partywheredeal_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.
- Set each provided mutable field:
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_partyrecord reflects the submitted changes. deal_party.updated_dtis 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 deleteactorId: 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_partytable. - Action: SELECT from
deal_partywheredeal_party.deal_party_id=dealPartyId. - Logic:
- If not found, return error.
Step 2. Check for Downstream References
- Source:
participant_settlement_itemtable. - Action: SELECT from
participant_settlement_itemwhereparticipant_settlement_item.payment_party_idmatches the deal party'sparty_idand 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_partyrecord. - Action: DELETE from
deal_partywheredeal_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_itemrecords that reference the party'sparty_idare not affected — they referencepartydirectly, notdeal_party.
Postconditions:
- The
deal_partyrow is permanently removed from the database. - The party still exists in
partyand 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 createsdealName: String (conditional) —deal.deal_name; required for new dealsdealReference: String (optional) —deal.deal_reference; auto-generated if not provided on createparties: Array of deal party objects — each containsdealPartyId(optional),partyId,partyRoleTypeCd, commission fields,bankAccountId(optional), andisDeletedflagactorId: 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
dealIdis provided: UPDATEdealsettingupdated_dt= now(). - If
dealIdis absent: INSERT intodeal.deal_nameis required. Ifdeal_referenceis not provided, auto-generate one (e.g.,DEAL-{random alphanumeric}). Return the newly generateddeal_id.
- If
Step 3. Load Existing Parties
- Source:
deal_partytable. - Action: SELECT all existing
deal_partyrecords for the resolveddeal_id. - Logic:
- Build a set of existing
deal_party_idvalues to compare against the submitted list.
- Build a set of existing
Step 4. Process Submitted Party List
- Source: Input
partiesarray. - Action: For each party object in the submitted array, apply the appropriate INSERT, UPDATE, or DELETE.
- Logic:
- Ensure
deal_idis set to the resolved deal ID on every party record. - Normalize
deal_party_commission_percanddeal_party_commission_amt: blank strings or nulls become"0". - If
deal_party_idis present andis_deleted=true: DELETE fromdeal_party(hard delete). - If
deal_party_idis present andis_deleted=false: UPDATE the existingdeal_partyrecord. - If
deal_party_idis absent andis_deleted=false: INSERT a newdeal_partyrecord.
- Ensure
Step 5. Handle Implicit Deletions
- Source: Set of existing
deal_party_idvalues (from Step 3) and processed IDs (from Step 4). - Action: DELETE any
deal_partyrows whosedeal_party_idwas not present in the submitted list. - Logic:
- The submitted party list is treated as authoritative. Any existing
deal_partyrecords not represented in the submission (and not explicitly marked as deleted) are removed. - This ensures the
deal_partyset exactly matches what the user configured.
- The submitted party list is treated as authoritative. Any existing
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
dealrecord is created or updated. - The
deal_partyset 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_masterwherecode_master_type='PARTY_ROLE_TYPE_CD'andcode_master_cd= input code. - Guard: If no matching
code_masterentry exists, reject the insert or update with a validation error. - Write:
party_role.party_role_type_cdanddeal_party.party_role_type_cdstore 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_accountjoined tobank_accountwhereparty_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_methodis provided and is not'WIRE'or'ACH', reject with a validation error. - Write:
party_bank_account.preferred_payment_methoddefaults 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_addresseswhereparty_addresses.party_id= target party andparty_addresses.party_addresses_type_cd= target type andparty_addresses.primary_ind=true. - Guard: If a new address is being set as primary, first clear
primary_indon all other records for the same party and type. - Write: Set
party_addresses.primary_ind=falseon existing primary records before settingprimary_ind=trueon 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_inddetermines which field is active. - Guard: When
flat_ind=false,deal_party_commission_percmust be a validdecimal(7,4)value. Whenflat_ind=true,deal_party_commission_amtmust be a validdecimal(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_party→party→party_bank_account(whereactive_ind=true) →bank_accountfor a givendeal_id. - Guard: If no active
party_bank_accountexists 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_namefield on the beneficiary party record. - Guard: None — this is a classification rule, not a validation guard.
- Write: If
party.company_nameis 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.Field | Target Table.Field | Transform |
|---|---|---|
party.display_name | UI label | Priority 1: use as-is if populated |
party.company_name | UI label | Priority 2: use if display_name is null |
party.first_name + party.last_name | UI label | Priority 3: concatenate and trim |
| — | UI label | Priority 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.Field | Target Table.Field | Transform |
|---|---|---|
Input dealPartyCommissionPerc = "" | deal_party.deal_party_commission_perc | Stored as "0" |
Input dealPartyCommissionPerc = null | deal_party.deal_party_commission_perc | Stored as "0" |
Input dealPartyCommissionAmt = "" | deal_party.deal_party_commission_amt | Stored as "0" |
Input dealPartyCommissionAmt = null | deal_party.deal_party_commission_amt | Stored as "0" |
| Any non-null numeric string | deal_party.deal_party_commission_perc or deal_party.deal_party_commission_amt | Kept 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.Field | Target Table.Field | Transform |
|---|---|---|
bank_account.bank_account_name + bank_account.bank_account_no | UI display string | "{bank_account_name} (...{last_4_digits})" |
bank_account.bank_account_no (when bank_account_name is null) | UI display string | bank_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.Field | Target Table.Field | Transform |
|---|---|---|
deal_party.party_role_type_cd | UI label | JOIN 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 label | Display 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.Field | Target Table.Field | Transform |
|---|---|---|
actorId (input parameter) | *.created_by | Copied as-is on INSERT |
| Current timestamp | *.created_dt | System-generated on INSERT |
actorId (input parameter) | *.updated_by | Copied as-is on INSERT and UPDATE |
| Current timestamp | *.updated_dt | System-generated on INSERT; explicitly set on UPDATE |
5. Cross-References
| Document | Relationship |
|---|---|
| Parties Data Model | Table definitions, field types, relationships, status lifecycles, and code master values for all tables in scope |
| Parties Queries | Read queries for party search, lookup, and resolution patterns used upstream of these procedures |
| Settlements Procedures | How deal parties flow into settlement creation via the party-to-settlement resolution chain (Section 3.5) |
| Settlements Procedures | How party_bank_account.preferred_payment_method and party.company_name drive outbound payment formatting and routing |
| Tax Withholding Procedures | How party_tax_info.party_id → party.party_id drives withholding calculations per party |
| Worksheets Procedures | Where settlement defaults (derived from deal parties) are consumed during worksheet settlement |
| Deals, Sales Items & Payment Terms Procedures | Deal creation and lifecycle; deal_party is managed as part of the deal upsert flow |
| Parties Data Model | code_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 |