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

  1. 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 System party is not offered: party_category is not user-editable.
  2. 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.
  3. 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_cardinality decides 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. Revert. The person chooses an older version. The system writes its values back with refdata.v1.parties.put as a new version, with a reason. There is no revert subject; history is append-only.
  11. 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.

wireframe_keep_a_partys_details_current.png

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 ?fail level, cardinality or stale: 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

  1. A membership leaves no readable version. The party_countries and party_currencies tables keep closed versions, but the generated protocol carries list, .get, .put, .delete and .list_by_party_id and 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

Emacs 29.3 (Org mode 9.6.15)