User Journey: Keep a party's details current
Table of Contents
This page documents a user journey: A tenant administrator corrects one of the tenant's own legal entities: its names and type, its identifiers, its contacts, and the countries, currencies, counterparties and business units it connects to.
A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.
1. Goal
"I want this legal entity to read correctly everywhere it is used: its name and short code, the schemes it is known by, the people to reach for what, and the countries, currencies, counterparties and units it connects to." The journey is done when the party reads correctly and its history shows why each field changed.
2. Actor and trigger
The actor is the tenant administrator: an account with the TenantAdmin role,
acting inside their own tenant. The trigger is a legal name that changed at the
registry, an identifier that was reissued, a contact or a desk that moved, or a
wrong value found in a run or a report. The journey starts at Parties, or at a
link from any screen that names a party, and ends when the person leaves the
party.
The journey corrects a party that already exists. It does not create one and it does not provision it: New party does that. The distinction is load-bearing, because the party keeps its id, its codename and its registration default across every correction here.
3. Steps
- Find the party. The person opens Parties. The system lists the tenant's
legal entities: short code, full name, type, status, business center, version
and the account that last modified the row. The person searches by name,
short code or codename, or opens a party from a link elsewhere. The tenant's
Systemparty is not offered:party_categoryis not user-editable. - Correct the party record. The person edits the short code, the full name,
the party type, the status, the parent party, the business center or the
registration default. The codename and the category are shown read-only. The
person states a change reason and a commentary, and the system writes the
party with
must_match_version, so an edit made meanwhile is refused instead of lost. - Correct the identifiers. The system lists the party's identifiers, one row
per scheme, with the scheme's cardinality. The person records a new value
under a scheme. Because the value is part of the row's key, the system
retires the old value with a delete and writes the new one, and the scheme's
max_cardinalitydecides how many values one party may hold. A change of legal name and a change of identifier do not read the same on screen, and this step says why. - Correct the contacts. The system lists one row per contact type: Legal, Operations, Settlement and Billing. The person opens a row and edits its address, phone, email and web page, and writes it with the row's own version as its precondition. The screen marks one row primary. See What is missing: the model carries no flag behind that mark.
- Set the memberships. The system lists the countries and the currencies the party operates in, each with the junction read for this party. The person closes a membership the party no longer needs, or links another. Closing writes a closed version of the junction row; the country or currency definition is tenant-level and is untouched.
- Set the structure. The system shows the counterparties the party can see and the business units it is made of. The person links or unlinks a tenant-wide counterparty, and reclassifies a business unit by choosing its unit type. A unit type carries a level, and the system refuses a child unit whose level is not strictly greater than its parent's; the refusal keeps the unit and its parent on screen so the person sees what broke.
- Review. The system lists every change as one row: what changes, the value before, the value after and the operation that writes it. Nothing is written until the person confirms. The change reason and the commentary are written with every row, and the party write states the version it read.
- Confirm. The system writes each operation. The party moves to a new version; the previous version is untouched. A child write, such as an identifier or a contact, bumps the party's version on its own, so a party write prepared against the old version is refused.
- Check the history. The person opens History. The system reads every version of the party with the generic history request and shows the actor, the reason and the field-level difference from the version before. It also reads the party with its identifiers and contacts as they stood in that version's window, so the two kinds of change stay comparable after the fact.
- Revert. The person chooses an older version. The system writes its values
back with
refdata.v1.parties.putas a new version, with a reason. There is no revert subject; history is append-only. - Leave. The person goes back. Every screen that names the party reads the corrected values.
No step waits on the server after its reply, and the journey keeps no state of its own. A refused write leaves the record exactly as it was and the person can try again. The journey is a run of screens over immediate operations, so it needs no workflow instance.
4. Four-eyes controls
A change that touches data other functions rely on needs a second person, and the person who asks cannot decide. Book types and the controls on each records the rule for each act of this journey:
- The party's names, type, status, parent, business center or registration default, an identifier, and a business unit's type: Finance decides, because a party starts the accounting hierarchy Finance owns.
- Closing or opening a country or currency membership, and linking or unlinking a counterparty: Operations decides.
- A contact: not gated. It is written directly.
Review marks each change as needing a named function or no approval. When any
change is gated, the confirm becomes Raise request: the system raises one
approval request (inbox.v1.ops.raise_approval), shows it waiting, and leaves the
record as it was. A decider from the named function opens the request with
inbox.v1.ops.get_approval, reads what would change and the check, and approves or
declines with a comment (inbox.v1.ops.decide_approval). The request applies only when
every named function has approved, and one decline ends it. The prototype draws the
waiting, decide and declined states (?state=waiting, ?state=decide, ?state=declined); the check it draws is that the party is still at the version the maker read, which ?check=stale makes fail.
The approval operations exist. No request kind is seeded for these acts, a request holds no pending change, and nothing applies the change when the last decider approves, so a screen cannot yet raise or apply a gated change. The deferred four-eyes authorisation framework is that missing piece. The deciders are Operations, Finance and Market Risk, and the Acme dataset holds no Finance account; the task Add the missing book-control actors to Acme adds it.
5. Screens and wireframes
The screens that serve this journey are the standard party screens, and not the walk drawn below. The Parties list opens the page of one party. The page has a tab for the details, the identifiers, the contacts, the memberships, the structure and the history, and each card or table on it is changed in a dialog, as a currency is. The walk in the wireframe was the first design of this journey, and the user experience no longer follows it step by step. See Entity screen specification, which says that a journey is a design document and not a screen.
One screen, ten states. The first state is the party list, which the person starts from and returns to. The other nine are the walk: a flat left rail with eight steps, plus the history and the refusal as side states. Each step draws the one view it needs. The party header, the short code, the type, the status and the version, stays above the body so the person never loses which record is open.
Figure 1: Keep a party's details current: the list, the eight-step walk (overview, identifiers, contacts, memberships, structure, review, outcome), the refusal and the history.
6. Prototype
A clickable prototype is merged at
doc/prototypes/parties/keep-a-partys-details-current/, and it is the design record for
this journey. It is plain HTML, CSS and JavaScript over mock data, with no
service behind it. It draws the same screens as the wireframe, and it adds the
three refusal variants the wireframe states in words:
list— the party list, its search and its create action;overview— the party's own record, with the correction notice, the read-only codename and category, and the audit line;identifiers— the identifier rows, the scheme picker, the cardinality note, and the comparison of a name change against an identifier change;contacts— the contact rows, the primary mark and the full contact form;memberships— the country and currency junctions, what closing one means, and the gap it leaves;structure— the party hierarchy, the counterparty links, the business units and the unit-type level ladder;review— one row per change, with the before and after value and the operation, above the change reason;outcome— what was written and what changed as a side effect;refused— three refusals, selected with?faillevel,cardinalityorstale: a business-unit level broken, an identifier cardinality exceeded, and a stale party write;history— the version buttons, the field diff of the chosen version, the composite as-of read and the revert.
The design questions the prototype settles, and which this document carries:
- Is a correction a replacement? No. The party keeps its id and its codename, and each correction appends a version. The write states the version it read, so a stale write is refused rather than overwriting a concurrent edit. That decision fixes the whole shape of the journey: there is no create form here and no destructive action.
- Do a name change and an identifier change read the same? No. The legal name is one field on the party row; an identifier is a row whose value is part of its key, so it is retired and replaced. The identifiers step draws the two side by side to make the difference visible before the person acts.
- Is a membership an edit or a link? A link. Closing a country or a currency membership hides it from this party and leaves every other party and the tenant-level definition alone, and it is reversible. The screen states this, because the action reads like a delete and is not one.
- How is a rule that lives in the database shown? As a state, not a toast. The refusal keeps the unit, its parent and both levels on screen, so the person can see the record the change would have broken.
- Where does the party's own view of its children come from? From the junction reads scoped to this party, one per panel, so a slow panel does not hold the others, and from the party write for the record itself.
7. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
party |
The party record, the list, the header and the history | ores.refdata.party |
party_identifier |
The identifiers step: the scheme, the value and its description | ores.refdata.party_identifier |
party_id_scheme |
The scheme picker and the scheme's max_cardinality |
ores.refdata.party_id_scheme |
party_contact_information |
The contacts step: one row per contact type | ores.refdata.party_contact_information |
contact_type |
The contact type each row is keyed by | ores.refdata.contact_type |
party_country |
The country membership rows | ores.refdata.party_country |
party_currency |
The currency membership rows | ores.refdata.party_currency |
party_counterparty |
The counterparty links this party can see | ores.refdata.party_counterparty |
business_unit |
The units this party is made of, and their hierarchy | ores.refdata.business_unit |
business_unit_type |
The unit type picker, and the level the rule reads | ores.refdata.business_unit_type |
party_type |
The party type picker | ores.refdata.party_type |
party_status |
The status picker | ores.refdata.party_status |
counterparty |
The tenant-wide counterparties offered for linking | ores.refdata.counterparty |
The party, the identifier and the contact are versioned, and each registers a history provider. The three junctions keep closed versions in their tables, but no operation reads them, so the membership panels have no history of their own; see What is missing. The party's own history is what carries the story of a membership that closed, because the junction write bumps the party's version.
8. Operations and messages
| Step | Operation | Subject | Status |
|---|---|---|---|
| Find the party | List and search the parties | refdata.v1.parties.list |
exists |
| Find the party | Read one party | refdata.v1.parties.get |
exists |
| Find the party | Fill the parent party picker | refdata.v1.parties.list |
exists |
| Correct the party record | Fill the party type picker | refdata.v1.party_types.list |
exists |
| Correct the party record | Fill the status picker | refdata.v1.party_statuses.list |
exists |
| Correct the party record | Offer the change reasons | dq.v1.change_reasons.list |
exists |
| Correct the party record | Write the party with must_match_version |
refdata.v1.parties.put |
exists |
| Correct the identifiers | List the party's identifiers | refdata.v1.party_identifiers.list_by_party_id |
exists |
| Correct the identifiers | Fill the scheme picker | refdata.v1.party_id_schemes.list |
exists |
| Correct the identifiers | Refuse a value over the scheme's cardinality | refdata.v1.party_identifiers.put |
exists |
| Correct the identifiers | Retire the old value | refdata.v1.party_identifiers.delete |
exists |
| Correct the identifiers | Write the new value | refdata.v1.party_identifiers.put |
exists |
| Correct the contacts | List the party's contacts | refdata.v1.party_contact_informations.list_by_party_id |
exists |
| Correct the contacts | Fill the contact type picker | refdata.v1.contact_types.list |
exists |
| Correct the contacts | Write a contact row | refdata.v1.party_contact_informations.put |
exists |
| Correct the contacts | Mark a contact primary | is_primary on refdata.v1.party_contact_informations.put |
exists |
| Set the memberships | List the party's countries | refdata.v1.party_countries.list_by_party_id |
exists |
| Set the memberships | List the party's currencies | refdata.v1.party_currencies.list_by_party_id |
exists |
| Set the memberships | Fill the country picker | refdata.v1.countries.list |
exists |
| Set the memberships | Fill the currency picker | refdata.v1.currencies.list |
exists |
| Set the memberships | Close a country membership | refdata.v1.party_countries.delete |
exists |
| Set the memberships | Close a currency membership | refdata.v1.party_currencies.delete |
exists |
| Set the memberships | Read a membership's own versions | none | missing |
| Set the structure | List the party's counterparty links | refdata.v1.party_counterparties.list_by_party_id |
exists |
| Set the structure | Fill the counterparty picker | refdata.v1.counterparties.list |
exists |
| Set the structure | Link or unlink a counterparty | refdata.v1.party_counterparties.put, .delete |
exists |
| Set the structure | List the party's business units | refdata.v1.business_units.list_by_party_id |
exists |
| Set the structure | Fill the unit type picker | refdata.v1.business_unit_types.list |
exists |
| Set the structure | Reclassify a business unit | refdata.v1.business_units.put |
exists |
| Set the structure | Read the refusal a level breach gives | the typed result.code level_violation and its result.message on refdata.v1.business_units.put |
exists |
| Review | Offer the change reasons | dq.v1.change_reasons.list |
exists |
| Confirm | Write every change as one unit | refdata.v1.ops.put_party_composite |
exists |
| Confirm | Read the party back | refdata.v1.parties.get |
exists |
| Check the history | Read every version with its diff | refdata.v1.history.get (entity_type ores.refdata.party) |
exists |
| Check the history | Read the party with its identifiers and contacts as of one version | refdata.v1.ops.get_party_composite_as_of |
exists |
| Check the history | Read a child row's own field diff | refdata.v1.history.get (entity_type ores.refdata.party_identifier, ores.refdata.party_contact_information) |
exists |
| Revert | Write the old values as a new version | refdata.v1.parties.put |
exists |
Partial marks an operation the server carries and this journey cannot use
whole, and each is explained under What is missing. Missing marks an
operation or a field the journey needs and the tree does not carry.
The generic history request is the one ores.history defines: the subject is
derived from the entity type, and the reply carries every version's full field
render and the field-level diff from the version before. One request therefore
serves the party and each of its children, under ores.refdata.party,
ores.refdata.party_identifier and ores.refdata.party_contact_information.
The per-entity refdata.v1.parties_versions.list and .get also exist and
return the raw rows; the screen needs the diff, so it uses the generic request,
and it uses refdata.v1.ops.get_party_composite_as_of for the children as they
stood. The two membership junctions register no history provider, so their
versions are item 1 below.
9. What is missing
One item. It is the missing row of the coverage table above; the coverage table
carries no partial row any more.
- A membership leaves no readable version. The
party_countriesandparty_currenciestables keep closed versions, but the generated protocol carrieslist,.get,.put,.deleteand.list_by_party_idand no versions subject. A closed membership therefore leaves the panel with no on-screen record of when it closed or who closed it. The candidate is a versions read for the two junctions, in the shape the party's children already have. Until it exists, the screen shows the memberships that are open, marks the one being closed in this session, and gives the party's own history as the only trail. The work is filed as Give the party memberships their versions, and it opens with a question this table cannot answer: whether the screen wants a list of versions or the field-level difference the generic history request gives the party, because a junction has no single id for that request to be addressed by.
10. Related journeys
- New party — the create and provision path; a person goes there when the legal entity is missing rather than wrong
- Onboard a counterparty — the same identity, identifier and contact panels for the external entity the tenant trades with
- Tune the tenant — the tenant whose legal entities these are, and its registration default
- Maintain a classification list — correct the party types, party statuses and contact types the pickers offer
- Shape the book structure — the books and portfolios the business units of this party carry
11. Related knowledge
- Reference Data — versioning, provenance and change reasons
- UX Language — the journey screen and the badge semantics this screen follows
- Business Centre — the business center a party carries
- Business Unit — the hierarchy the units and their levels form
- Parties — the user manual chapter for this screen
12. See also
- Parties journeys
- User Journeys
doc/prototypes/parties/keep-a-partys-details-current/— the merged prototype that answers the design questions above