# Transfer of Product Ownership - Frontend Runbook (Abacus)

- **Owner:** PORT enablement team
- **Epic:** [PORT board](https://theorchard.atlassian.net/jira/software/c/projects/PORT/boards/1450)
- **Last Updated:** 2026-06-15

Scope: the Abacus "Transfer Project" UI in `frontend-royalties` - queuing a transfer, choosing
which contract terms to migrate, and monitoring the queue. This is the accounting-relevant
front end; content movement and execution happen elsewhere. Source of truth is
`upstream/master` of `frontend-royalties`
(`src/components/transfer-projects/`).

## What the UI does

The Solutions team uses the Transfer Projects page to queue a transfer and watch it through to
completion. Execution itself is operator-triggered, not a UI action (the "Execute Transfers"
button is present but disabled, labelled "not available yet").

```mermaid
flowchart LR
    Q["Transfer Projects page<br>transfer-projects.tsx"] --> NEW["New Transfer modal<br>create-transfer-form.tsx"]
    NEW --> D["Step 1: Details<br>details-step.tsx"]
    NEW --> T["Step 2: Terms<br>setup-terms-step.tsx"]
    Q --> LIST["Queue table<br>transfer-list.tsx"]
    LIST --> DETAIL["Transfer detail<br>transfer-detail.tsx"]
```

## Queuing a transfer

`create-transfer/create-transfer-form.tsx` is a two-step modal.

**Step 1 - Details** (`details-step.tsx`) collects:

- Originating account (searchable; `GetAccountsForTransfer`)
- Project to transfer (searchable; live search with a full-catalog fallback)
- Destination account (searchable; the originating account is excluded)
- Destination subaccount (optional; shown only if the destination has subaccounts)
- Destination contract (auto-selected and locked if the destination has exactly one)

An informational note explains that the transfer takes effect at the next accounting run and
that the revenue cutoff is the last day of the prior month. The cutoff is computed, not entered.

**Step 2 - Terms** (`setup-terms-step.tsx`) chooses what contract terms migrate to the
destination:

- **Label mode** (default): apply the destination contract's label-level rate to all
  transferred products and tracks. No per-term setup.
- **Custom mode:** define product terms (keyed by UPC) and/or track terms (keyed by ISRC),
  each with its own conditions (priority, rate %, countries, stores, transaction types).
  Validation requires at least one attachment and one condition per term, with a rate of
  0-100% (`term-validation.ts`).

On submit, the form calls two GraphQL mutations (resolved by the Abacus GraphQL gateway):

| Mutation | Persists to | Effect |
|---|---|---|
| `CreateProjectTransferJob` | `project_transfer_job` in art_relations (ows-project-manager) | creates the `QUEUED` job and snapshots its releases |
| `CreateProjectTransferTerms` | `project_transfer_term` / `project_transfer_term_condition` in royalty_accounting (ows-royalties, `POST /transfer-job/{id}/terms`) | stages the terms the accounting step will recreate on the destination |

The staged `project_transfer_term` rows are exactly what the `edit-attachments` lambda reads
later (`GET /transfer-job/{id}/terms`) to add terms on the destination account. A bare label
selection with no conditions is expanded server-side into one staged spec per active label
term condition, so the label rate is captured at queue time.

## Monitoring the queue

`transfer-list.tsx` shows the job queue and polls every 5 seconds so status changes
(`QUEUED -> PROCESSING -> COMPLETED` / `FAILED`) appear without a refresh.

- **Filters:** transfer id, status (multi-select), effective period range (by
  `revenueCutoffDate`), and account (origin or destination).
- **Columns:** id, status, origin, destination, period, created (date + who queued it),
  completed date.
- **Delete:** only `QUEUED` jobs can be removed (`DeleteProjectTransferJob`). Once a job is
  `PROCESSING`/`COMPLETED`/`FAILED`/`DELETED` the delete control is disabled.

`transfer-detail/transfer-detail.tsx` shows one job: the project and the products being moved,
and the terms that will be applied (`transfer-terms-section.tsx`, grouped into Label / Product
/ Track). Queries: `GetProjectTransferJob`, `GetProjectTransferTerms`.

## What is not in the UI yet

- **Execution.** Queued jobs are executed by an operator-triggered batch
  (`POST /transfer/batch/execute`), not from the UI. The "Execute Transfers" button is disabled.
- **PORT-10 transfer warning banner.** The planned banner on the contract detail page (warning
  that a contract references a UPC/ISRC transferred away from its account) is not shipped on
  master. It exists only as a plan today.

## Operating notes

- After editing any `.gql`, run `yarn generate:types` so the generated types under
  `__generated__/` stay in sync.
- The transfer screens are permission-gated: viewing, creating, and the (disabled) execute
  action each check a distinct permission via `use-transfer-permission.ts`.
- If a queued job will not appear in the list, check the status/account/period filters first;
  the list filters client-side on top of the polled query.
