User Journey: Shape the book structure
Table of Contents
This page documents a user journey: A tenant administrator walks the portfolio tree, opens or creates a book, classifies it, sets the portfolio rights, and reviews the write.
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 the book structure to say exactly what we mean by it: which portfolio each book sits in, what that book is for, how it is classified, and who may see it." The journey is done when the tree reads correctly, each changed book shows its new version, and the rights at the portfolio node are the ones the administrator intended.
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 new desk, a book that must be
opened or reclassified, a book the desk no longer uses, or a colleague who needs
sight of a portfolio. The journey starts at Book tree, or at a book or
portfolio link on another screen, and ends at the outcome when the writes land,
or at the refused screen when the server rejects one. The journey keeps no
server-side state: every step completes when the server replies.
3. Steps
- See the tree. The person opens Book tree. The system shows the tenant's portfolios and the books under them, with a search over both. Every row is the tenant's own data. Each book row carries its status, its regulatory type and its purpose.
- Choose the portfolio. The person chooses the portfolio that will hold the book, or creates a new one. The system shows what the book reads from that node: the aggregation currency, the owner business unit, the portfolio purpose, the sandbox, and who may see it. A book copies none of these. It reads them from its portfolio, and the portfolio's rights cover the book. The model says the book's owner unit must be one named in the portfolio ancestry chain, and the owner-unit picker marks a unit that is not.
- Create or open the book. The person names the book and gives it the fields
finance and the ledger use: the functional currency, the GL account
reference, the cost center, the owner unit, and a description. The name is
the natural key and does not change after the write. A book belongs to
exactly one portfolio. The system writes
refdata.v1.books.putwithmust_not_existfor a new book andmust_match_versionfor a change. - Set its classification. The person sets the status and the three independent classification axes, plus the rates centre and the sweepable flag. The axes combine freely: regulatory type, ledger feed and book purpose are not correlated by any check. The book carries exactly one purpose. Each picker names one row of a shared system-tenant list, so the person can choose a value here and cannot edit the list here. The person can also close the book. The close the book cannot make is the one with activity still open: the system must refuse it and name the trades that remain. See What is missing.
- Set the rights. The person grants or removes
readandopen_sandboxat the portfolio node. The system shows the effective rights for every account, with the ancestor a right was inherited from. A right at a node covers every node below it, and not its siblings. A right granted to a closed account stays on record, and the function stops honouring it. - Review. Nothing is written until the person confirms. The system lists each change with its field, its old value and its new value, and the subject that will carry it. It marks each change as needing a second person or not, and names the function that decides it, as Book types and the controls on each states. The person picks a reason code.
- Raise the request, or read the outcome. When any change needs a second person, the system raises one approval request for the gated changes, shows it waiting, and leaves the book as it was. When none does, the system writes the book and the rights directly and shows the book's new version. Either way the person can open the history, go to the rights, or return to the tree.
- Decide. A different person, from the function the control names, opens the request. The system shows what would change and the checks it ran, and the decider approves or declines with a comment. The person who asked cannot decide. On approval the system applies the change as a new version. A new book goes first to a controller, then to Finance, Market Risk and Operations, who each approve their own part.
Five states sit beside the steps. Waiting shows a raised request with who must still answer. Decide is the checker's screen, and it refuses the person who asked. Declined shows a declined request: the book keeps its version and the declined request stays on record. Refused shows the error the server returns when a write is not allowed, with the subject, the intent, the error code and the record version. History shows every version of one book with the field-level difference from the version before it, and offers a revert, which the system raises as a request when the reverted fields are gated and writes as a new version when they are not. A virtual book in a sandbox is never gated.
No step waits on the server after its reply, and the journey keeps no state.
4. Screens and wireframes
One screen with a step rail: the seven steps in order, with the two side states beneath them. The step in view is a card. The tree step splits into the tree on the left and the chosen book or portfolio on the right. The classification step splits into the book's own pickers on the left and the shared taxonomy on the right. The rights step splits into the direct-rights editor on the left and the effective-rights table on the right. The person moves between books and portfolios without leaving the screen.
Figure 1: Shape the book structure: the step rail, the classification with its transition rule, the tree, the rights, the review, the shared taxonomy, and the two side states.
5. Prototype
A static page at doc/prototypes/books/shape-the-book-structure/index.html: mock data
only, no backend, no build step and no session. It is the design record for this
journey. It settles three questions. First, what a book reads from its portfolio
rather than copies: the aggregation currency, the owner unit ancestry, the
portfolio purpose, the sandbox, and the rights. Second, which rows are the
tenant's own data and which are the shared taxonomy: portfolios, books and
rights are the tenant's, and the five classification lists are the system
tenant's. Third, the close the book cannot make: a close while the ledger still
shows open activity.
- States
?state=tree,?state=portfolio,?state=book,?state=classify,?state=rights,?state=review,?state=outcome,?state=refused,?state=history- Params
?new=1a new book,?book=<name>the book already open,?portfolio=<node>the chosen node,?status,?purpose,?regulatory,?ledger,?sweepable,?account,?right,?fail=open_activity|transition,?version=<n>- Source
doc/prototypes/books/shape-the-book-structure/index.html- Open
- the file directly, or
/OreStudio/doc/prototypes/books/shape-the-book-structure/once the site is built
6. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
book |
The book form and the "This book" panel: name, description, functional currency, GL account ref, cost center, owner business unit, status, regulatory book type, sweepable, rates centre, and open activity | ores.refdata.book |
portfolio |
The tree, the new-portfolio form, and the "Inherited from the portfolio" panel: name, purpose type, aggregation currency, owner business unit, status, virtual, sandbox | ores.refdata.portfolio |
portfolio_right |
The direct-rights editor and the effective-rights table: account, portfolio, right code | ores.refdata.portfolio_right |
business_unit |
The owner-unit picker, constrained by the portfolio ancestry chain | ores.refdata.business_unit |
book_status |
The status picker and the transition rule | ores.refdata.book_status |
regulatory_book_type |
The regulatory book type picker | ores.refdata.regulatory_book_type |
book_purpose_type |
The book purpose picker. The picker is drawn; the book has no wire field for it. | ores.refdata.book_purpose_type |
ledger_feed_type |
The ledger feed picker. The picker is drawn; the book has no wire field for it. | ores.refdata.ledger_feed_type |
purpose_type |
The portfolio's purpose picker | ores.refdata.purpose_type |
The book is a composite child of the portfolio: a book write bumps the parent portfolio's version, so the portfolio's own version shows the change. The book registers a history provider, and the portfolio is versioned too. The book never writes its portfolio's fields, and it copies none of them.
7. Operations and messages
missing marks an operation or a rule the journey needs and the tree does not
carry; each is explained under What is missing. No row is partial any more:
the book write carries every field the screen draws.
| Step | Operation | Subject | Status |
|---|---|---|---|
| See the tree | List the portfolios | refdata.v1.portfolios.list |
exists |
| See the tree | List the books | refdata.v1.books.list |
exists |
| See the tree | List one portfolio's books | refdata.v1.books.list_by_parent_portfolio_id |
exists |
| Choose the portfolio | Read the portfolio | refdata.v1.portfolios.get |
exists |
| Choose the portfolio | Create a portfolio | refdata.v1.portfolios.put |
exists |
| Choose the portfolio | Fill the owner-unit picker | refdata.v1.business_units.list |
exists |
| Choose the portfolio | Refuse an owner unit outside the ancestry chain | the typed result.code unit_outside_ancestry on refdata.v1.books.put |
exists |
| Create or open the book | Read the book | refdata.v1.books.get |
exists |
| Create or open the book | Fill the currency picker | refdata.v1.currencies.list |
exists |
| Create or open the book | Fill the rates-centre picker | refdata.v1.business_centres.list |
exists |
| Create or open the book | Write the book | refdata.v1.books.put |
exists |
| Set its classification | Fill the five pickers | refdata.v1.book_statuses.list, .regulatory_book_types.list, .book_purpose_types.list, .ledger_feed_types.list, .purpose_types.list |
exists |
| Set its classification | Offer the change reasons | dq.v1.change_reasons.list |
exists |
| Set its classification | Refuse a status change the transition rule forbids | the typed result.code status_transition_not_allowed on refdata.v1.books.put |
exists |
| Set its classification | Refuse a close while activity is open | no subject | missing |
| Set the rights | List the rights at the node | refdata.v1.portfolio_rights.list |
exists |
| Set the rights | Fill the account list | iam.v1.accounts.list |
exists |
| Set the rights | Grant the rights | refdata.v1.portfolio_rights.put_many |
exists |
| Set the rights | Remove the rights | refdata.v1.portfolio_rights.delete_many |
exists |
| Review | Read the change reasons | dq.v1.change_reasons.list |
exists |
| Raise the request | Raise one request for the gated changes | inbox.v1.ops.raise_approval |
partial |
| Raise the request | Withdraw the request | inbox.v1.ops.withdraw_approval |
exists |
| Raise the request | Read my requests | inbox.v1.ops.list_my_approval_requests |
exists |
| Decide | Read the queue a decider owns | inbox.v1.ops.list_approval_queue |
exists |
| Decide | Read one request | inbox.v1.ops.get_approval |
exists |
| Decide | Approve or decline with a comment | inbox.v1.ops.decide_approval |
partial |
| Read the outcome | Write the book, when no change is gated | refdata.v1.books.put |
exists |
| Read the outcome | Write the rights, when no change is gated | refdata.v1.portfolio_rights.put_many |
exists |
| Read the outcome | Apply an approved change as a new version | no operation | missing |
| Refused | Read the refused write | refdata.v1.books.put |
exists |
| History | Read every version with its diff | refdata.v1.history.get (entity_type ores.refdata.book) |
exists |
| History | Revert by writing the old values | refdata.v1.books.put |
exists |
8. What is missing
Three items are missing rows of the coverage table above or the parts they need. One is a rule the journey draws that cannot be enforced where it reads as if it would be, and it is recorded rather than dropped. One is out of scope by the story's own decision.
- Nothing refuses closing a book with open activity, and the store cannot.
The journey wants a refusal on the close that names the open trades, unsettled
cash flows and open positions the book still holds. Those rows belong to the
tradingcomponent, and no component's SQL reads another's tables — refdata'screate/directory contains no reference to atrading_table, and neither does any other component's. Refdata's service layer does not depend ontradingeither, so it cannot ask. A refusal here would have to be the first cross-component read in the schema, or a wire round trip that cannot be atomic with the book write. Until that decision is made, the screen checks before it writes and shows what the server would say; the refusal is not enforced, and this document does not claim it is. - The rights panel is drawn here, although the catalogue assigned
portfolio_rightto the Access group. The panel belongs to that group's journey, which owns the permissions and the change reason. This journey draws it because a book carries no rights of its own and inherits its visibility from the portfolio node. Until the rights journey exists, this screen carries the panel and links to the Access group. This is out of scope for the book work by the story's own decision. - A gated change has nowhere to wait, and an approval cannot apply it. The approval request records who asked and who decided, and the book and rights write subjects apply a change at once. No request kind is seeded for the book acts, a request holds no pending change, and nothing applies the change when the last decider approves. The deferred four-eyes authorisation framework is that missing piece (Implement four-eyes authorisation framework). Until it exists the screen cannot raise or apply a gated change, and this document does not claim it can.
- Two of the deciding parts have no account in Acme. The controls name a controller and Finance, and the Acme dataset holds neither. The task Add the missing book-control actors to Acme adds them.
The four items this section used to carry are built, and the coverage table says so: the book carries and saves its purpose and its ledger feed, the status transition rule refuses an edge the lifecycle does not draw, and the owning unit is checked against the portfolio ancestry chain.
9. Related journeys
- Maintain a classification list — the five Books and portfolios lists on the classification step are the shared taxonomy; each picker opens its list there
- Access — the group the catalogue assigns the portfolio-rights journey to; the rights step here draws that journey's subject until it exists
- Tune the tenant — the same pattern for the identity lookups
10. Related knowledge
- Book classification — the three independent axes, the mutual exclusivity of the purpose, and why a Basel rule never forces a correlation
- Book — the hub note
- Books and portfolios — the hierarchy, and how a classified book sits in it
- Book access and permissions — the permission model books inherit, and access review
- Book groups and rates centres — the rates centre and the revaluation snapshot it selects
- Sandbox — virtual books, and the portfolio and the book that share one sandbox
- Reference Data — versioning, provenance and change reasons
- UX Language
- The journey runtime — the step, the rail, and the rule that a final step cannot be returned past
11. See also
- Books and portfolios journeys
- User Journeys
- ores.refdata.book — the entity model
- ores.refdata.portfolio — the entity model
- ores.refdata.portfolio_right — the entity model