# Seat Transfer — Implementation & API Reference

## Overview

Allows an admin to reassign an existing paid registration from one user to another. The original payment history, registration status, and sequence numbers are preserved. Only the linked user changes. Room, pair, and group allocations are cleared automatically on transfer.

---

## Endpoints

### 1. Validate Target User

Pre-validates the target user before submitting the transfer. Checks the user exists and is not already registered for the same program.

```http
GET /registration/:id/seat-transfer/validate-user?phone=<phone>&email=<email>&countryCode=<countryCode>
```

**Auth:** Bearer token required. Roles: `admin`, `operational_manager`.

**Path param:**

| Param | Type   | Description                                        |
|-------|--------|----------------------------------------------------|
| `id`  | number | Source registration ID (the one being transferred) |

**Query params:**

| Param         | Type   | Required | Description                            |
|---------------|--------|----------|----------------------------------------|
| `phone`       | string | Yes      | Phone number (without country code)    |
| `email`       | string | Yes      | Email address of the target user       |
| `countryCode` | string | Yes      | Country code, e.g. `+91`              |

**Success `200`:**

```json
{
  "success": true,
  "data": {
    "user": {
      "id": 9271,
      "fullName": "Madhuri Karedla",
      "email": "madhuri@example.com",
      "phoneNumber": "9493791985"
    },
    "countryCode": "+91",
    "alreadyRegistered": false,
    "proxyAllowed": false,
    "existingRegistrationId": null
  }
}
```

When `alreadyRegistered: true` and `proxyAllowed: true` — show a warning in the UI before proceeding:

```json
{
  "success": true,
  "data": {
    "user": { "id": 9271, "fullName": "Madhuri Karedla" },
    "countryCode": "+91",
    "alreadyRegistered": true,
    "proxyAllowed": true,
    "existingRegistrationId": 88
  }
}
```

**Error responses:**

| HTTP  | Code        | When                                                              |
|-------|-------------|-------------------------------------------------------------------|
| `404` | `RE_NF_001` | Source registration not found                                     |
| `404` | `U_NF_001`  | No user found with the given phone/email                          |
| `400` | `ST_BR_005` | Target user already registered; proxy registration not allowed    |

**Validation logic:**

```text
1. Load source registration by :id  →  programId + program.allowsProxyRegistration
2. Look up target user by phone + email + countryCode
   └── Not found → 404 U_NF_001
3. Check for existing active registration (excludes cancelled/archived):
   Target user has active reg for same program?
     ├── No  → { alreadyRegistered: false, proxyAllowed }
     └── Yes → proxyAllowed?
                 ├── true  → { alreadyRegistered: true, existingRegistrationId }  ← warn
                 └── false → 400 ST_BR_005  ← block
```

---

### 2. Transfer Seat

Executes the seat transfer.

```http
PATCH /registration/:id/seat-transfer
```

**Auth:** Bearer token required. Roles: `admin`, `operational_manager`.

**Path param:**

| Param | Type   | Description                  |
|-------|--------|------------------------------|
| `id`  | number | Registration ID to transfer  |

**Request body:**

```json
{
  "user": {
    "userId": 9271
  },
  "answers": [
    { "questionId": 7468, "answer": "Madhuri Karedla" },
    { "questionId": 7470, "answer": "2001-01-01" },
    { "questionId": 7471, "answer": "+919493791985" },
    { "questionId": 7472, "answer": "madhuri@example.com" }
  ],
  "transferReason": "Original registrant unable to attend",
  "adminNotes": "Approved by program coordinator"
}
```

**Request fields:**

| Field                  | Type   | Required | Description                                                              |
|------------------------|--------|----------|--------------------------------------------------------------------------|
| `user.userId`          | number | Yes      | ID of the user to transfer the seat to (obtained from validate step)     |
| `answers`              | array  | No       | Registration field overrides — payment/invoice answers are ignored       |
| `answers[].questionId` | number | Yes      | Question ID                                                              |
| `answers[].answer`     | any    | Yes      | String, number, boolean, or array depending on question type             |
| `transferReason`       | string | No       | Reason for the transfer                                                  |
| `adminNotes`           | string | No       | Internal admin notes                                                     |

**Success `200`:**

```json
{
  "success": true,
  "data": {
    "registration": {
      "id": 6839,
      "userId": 9271,
      "ownerUserId": 9271,
      "registrationMode": "self",
      "fullName": "Madhuri Karedla",
      "emailAddress": "madhuri@example.com",
      "registrationStatus": "pending"
    },
    "targetUser": { "id": 9271, "fullName": "Madhuri Karedla" }
  }
}
```

**Error responses:**

| HTTP  | Code        | When                                                          |
|-------|-------------|---------------------------------------------------------------|
| `404` | `RE_NF_001` | Registration not found                                        |
| `400` | `ST_BR_001` | Registration not eligible for transfer (see preconditions)    |
| `404` | `U_NF_001`  | Target user not found                                         |
| `500` | `ST_IE_001` | Unexpected server error during transfer                       |

---

## Preconditions (ST_BR_001)

Transfer is rejected if **any** of the following is true:

| Condition                                                                             | Reason                                      |
|---------------------------------------------------------------------------------------|---------------------------------------------|
| Registration status is `waitlisted`                                                   | Waitlisted seats cannot be transferred      |
| `seatAllocated !== true`                                                              | Seat must be physically allocated           |
| No payment with status `online_completed`, `offline_completed`, or `offline_pending`  | Only paid registrations can be transferred  |

---

## What Happens on Transfer

Executed atomically in a single database transaction:

1. Registration preconditions validated
2. Target user looked up by `userId`
3. Answers validated — any payment/invoice-mapped questions are blocked
4. All seat allocations cleared via `allocationClearingService.clearRegistrationAllocations()`
5. `userId`, `ownerUserId`, and `registrationMode` updated on the registration row (see below)
6. Provided `answers` applied — only registration-table fields updated
7. Transfer logged in `seat_transfer_track` with full audit trail (chain-transfer aware)
8. Final registration state returned

> No notifications are sent automatically. The admin is responsible for any follow-up communication.

---

## Registration Field Updates on Transfer

When a seat is transferred, three fields on `program_registration` are updated atomically:

| Field              | Before transfer      | After transfer              |
|--------------------|----------------------|-----------------------------|
| `userId`           | original participant | new participant             |
| `ownerUserId`      | original owner       | new participant (= `userId`)|
| `registrationMode` | `self` or `other`    | always `self`               |

**Why `ownerUserId` changes:** `GET /user/:id/registrations` filters by `ownerUserId`. Without updating it, the transferred-to user would never see the registration in their list.

**Why `registrationMode = self`:** After transfer, the new user is both the beneficiary (`userId`) and the owner (`ownerUserId`), so the registration is effectively "for themselves". Any prior proxy relationship (original `registrationMode = other`) is dissolved by the transfer.

---

## Answers — What Gets Updated

Answers are matched to registration fields via each question's `answerLocation`.

| Answer type                                                           | Behaviour                                         |
|-----------------------------------------------------------------------|---------------------------------------------------|
| Maps to a registration-table field (fullName, dob, email, etc.)       | Updated via targeted SQL UPDATE                   |
| Maps to a payment or invoice table                                    | **Blocked** — belongs to the original payer       |
| Maps to `registration.proForma*` and payment is completed             | **Blocked** — proforma locked after payment       |
| Maps to `registration.proForma*` and payment is NOT completed         | Allowed                                           |
| Empty string (`""`)                                                   | Saved as `null`                                   |
| Non-string value (number, boolean, array)                             | Saved as-is                                       |

---

## Audit Trail — `seat_transfer_track`

Every transfer writes one row to `seat_transfer_track`. Chain transfers (A→B→C) are tracked via `sourceUserId`, which always points back to the original registrant.

| Column                  | Description                                                            |
|-------------------------|------------------------------------------------------------------------|
| `programRegistrationId` | The registration that was transferred                                  |
| `originalUserId`        | The user who held the seat before this transfer                        |
| `newUserId`             | The user who received the seat                                         |
| `sourceUserId`          | First-ever holder of the seat (preserved across chain transfers)       |
| `originalUserName`      | Snapshot of the outgoing user's name at transfer time                  |
| `newUserName`           | Snapshot of the incoming user's name at transfer time                  |
| `transferReason`        | Reason provided by admin                                               |
| `adminNotes`            | Internal admin notes                                                   |
| `transferStatus`        | Always `COMPLETED`                                                     |

---

## Typical UI Flow

```text
Admin opens seat transfer modal for registration #6839
  │
  ├─ [Step 1] Enter target user phone + email + country code
  │   └─ GET /registration/6839/seat-transfer/validate-user
  │          ?phone=9493791985&email=user@example.com&countryCode=%2B91
  │       ├─ 200 { alreadyRegistered: false }
  │       │     → Show user name + "Looks good, proceed"
  │       │
  │       ├─ 200 { alreadyRegistered: true, proxyAllowed: true, existingRegistrationId: 88 }
  │       │     → Warn: "This person already has registration #88 for this program.
  │       │              Proxy is allowed — confirm to proceed anyway."
  │       │
  │       ├─ 400 ST_BR_005 → "This person is already registered and duplicates are not allowed."
  │       └─ 404 U_NF_001  → "User not found"
  │
  └─ [Step 2] Confirm transfer (optionally edit registration field answers)
      └─ PATCH /registration/6839/seat-transfer
          { "user": { "userId": 9271 }, "answers": [...] }
          ├─ 200 → Success — close modal, refresh registration detail
          └─ 400 ST_BR_001 → "This registration is not eligible for transfer"
```

---

## Error Codes

| Code        | Description                                                          |
|-------------|----------------------------------------------------------------------|
| `ST_BR_001` | Registration not eligible — waitlisted, no seat, or invalid payment  |
| `ST_BR_002` | Answer targets a payment-locked field                                |
| `ST_BR_003` | Room unallocation failed                                             |
| `ST_BR_004` | Pair unlink failed                                                   |
| `ST_BR_005` | Target user already registered; proxy not allowed                    |
| `ST_IE_001` | Unexpected server error during transfer                              |

Reuses existing codes: `U_BR_004` (email mismatch), `U_BR_005` (phone mismatch), `U_NF_001` (user not found), `RE_NF_001` (registration not found).

---

## Key Files

| File                                                    | Role                                                                        |
|---------------------------------------------------------|-----------------------------------------------------------------------------|
| `src/registration/registration.controller.ts`           | `GET :id/seat-transfer/validate-user`, `PATCH :id/seat-transfer` endpoints  |
| `src/registration/registration-action.service.ts`       | `transferSeat()`, `validateSeatTransferUser()`                              |
| `src/registration/registration.repository.ts`           | `updateUserId()` — updates `userId`, `ownerUserId`, `registrationMode`      |
| `src/registration/dto/seat-transfer.dto.ts`             | `SeatTransferDto`, `SeatTransferUserDto`, `SeatTransferAnswerDto`           |
| `src/common/entities/seat-transfer-track.entity.ts`     | Audit trail entity                                                          |
| `src/common/enum/registration-mode.enum.ts`             | `RegistrationModeEnum.SELF / OTHER`                                         |
