# Registration System

## Overview

Registrations are couple-based — each registration contains two registrants (registrant1 + registrant2). Both registrants are now mandatory fields. The system supports Indian and NRI registrations.

---

## Registration Model (key fields)

```
Registration
  ├── registrant1, registrant2        -- Names (both mandatory)
  ├── gender1, gender2                -- MALE / FEMALE (both mandatory)
  ├── countryCode1/2, phone1/2        -- Phone numbers per registrant
  ├── birthdate1/2                    -- Birthdates (both mandatory)
  ├── aadharCardNo, panCardNo         -- ID docs (optional)
  ├── mahayagPlace                    -- Selected seva place name
  ├── mahayagSeva                     -- Total seva amount
  ├── submittedSeva                   -- Amount paid so far
  ├── remainingSeva                   -- mahayagSeva - submittedSeva
  ├── fullyPaidSevaAt                 -- Timestamp when remainingSeva < 500
  ├── karyakartaHaridhamId            -- Linked karyakarta code (optional)
  ├── registrationCountryType         -- INDIAN | NRI
  ├── mandalId, pradeshId             -- Hierarchy captured at registration time
  ├── needATG, needChair              -- Special needs flags
  ├── isDeleted, deletedAt            -- Soft-delete fields
  └── transactions[]                  -- Linked payment transactions
```

---

## Registration Flow

1. **Phone precheck** — before OTP is sent, `formController` verifies the phone is not already registered. Filter: `isDeleted: false` is mandatory. Soft-deleted registrations must not block re-registration of the same phone number.

2. **OTP send** — OTP sent to `phone1` (registrant1's phone). OTP record stored in `RegistrationOtp` table.

3. **OTP verify** — verified OTP is marked `verifiedAt`; registration session proceeds.

4. **Form submission** — Registration record created with linked User (role: `PERSONAL`).

5. **Payment** — Can be online (Razorpay) or cash (via karyakarta).

6. **Receipt generation** — After full payment, receipt is generated and stored in Firebase Storage.

7. **Success screen** — Shown in `sdm-frontend/src/components/registration-form.tsx` after payment completes.

### Registration success screen (post-payment)

> **Last updated:** June 2026

**Component:** `registration-form.tsx` (final success branch when `paymentCompleted` is true).

**i18n keys** (add to `en.json`, `gu.json`, `hi.json` together):
- `registration.success.title`
- `registration.success.subtitle`
- `registration.success.registerAnother`
- `registration.success.goToHome`

| Button | Behavior |
|---|---|
| **Register Another** | Clears auth session (`clearAuthSession()` — token/user cookies removed); navigates to a fresh registration form (`/register` for Indian, `/nri-registration-form` for NRI). Does **not** redirect to `/login`. |
| **Go to Home** | Navigates to `/` while keeping the current PERSONAL session (user stays logged in to view their dashboard). |

**Language:** All success-screen copy uses `useI18n()` / `t(...)` — never hardcode English strings on this screen.

> **Important**: Registration phone precheck and Nimit Sevak phone precheck must stay **separate**. Registration UI queries `Registration` rows only; Nimit Sevak UI queries `NimitSevak` only.

---

## NRI Registrations

- `registrationCountryType` field: `INDIAN` (default) or `NRI`.
- NRI registrations use `NriMahayagRates` table instead of `MahayagRates`.
- NRI registrants have a separate form at `/nri-registration-form`.
- Analytics queries must segment by `registrationCountryType`.

---

## Soft Delete vs Hard Delete

### Old behavior (soft delete only)
- `isDeleted = true` on the Registration record.
- Problem: phone number remained "taken" and inflated analytics dashboards.

### New behavior (archive + hard delete)

When an admin deletes a registration:

1. A **full snapshot** is written to `DeletedRegistrationArchive`:
   - `registrationSnapshot` (JSON)
   - `userSnapshot` (JSON)
   - `transactionsSnapshot` (JSON)
   - `paymentSessionsSnapshot` (JSON)
   - `primaryPhone`, `countryCode`, `registrant1`, `registrant2` for quick lookup

2. A `DeletedRegistrationAction` record is written (who deleted, when, reason).

3. The **active** Registration, User, Transaction, and PaymentSession records are **hard-deleted**.

This allows the phone number to be reused immediately, and keeps a full audit trail in the archive table.

### Frontend admin copy
- Use language like "archived and removed from active records" rather than "soft-deleted" or "struck out".

---

## Analytics: isDeleted Filter

All analytics and dashboard queries **must** include `isDeleted: false`. There was a legacy soft-deleted `Registration` row in production (Shri Hari Pradesh) that inflated totals by 1 until the filter was applied.

---

## DeletedRegistrationArchive Table

```
DeletedRegistrationArchive
  ├── id
  ├── originalRegistrationId
  ├── originalUserId
  ├── deletedByUserId
  ├── reason
  ├── registrationSnapshot (JSON)
  ├── userSnapshot (JSON)
  ├── transactionsSnapshot (JSON)
  ├── paymentSessionsSnapshot (JSON)
  ├── primaryPhone
  ├── countryCode
  ├── registrant1
  ├── registrant2
  └── deletedAt
```

Indexed on `originalRegistrationId` and `primaryPhone` for fast lookup.

---

## ID Documents

Registration can have:
- Aadhar card image (uploaded via `registrationUploadMiddleware`)
- PAN card image

Both are stored in Firebase Storage. Fields:
- `aadharCardUrl` / `aadharCardStoragePath`
- `panCardUrl` / `panCardStoragePath`

The `registrationDocumentService.js` handles Firebase upload/re-upload for ID documents.

---

## Hierarchy Fields

At registration time, the hierarchy is captured:
- `karyakartaHaridhamId` — karyakarta's Haridham code (if provided)
- `district` — fallback if karyakarta code is not known
- `mandalId`, `pradeshId` — resolved and stored at submission time

This allows admin filtering by mandal/pradesh without requiring joins through the Karyakarta table every time.

> **Prisma field name caution**: Use `Registration.karyakartaHaridhamId` (not `haridhamId`) in `Transaction` relation filters. Using the wrong field name causes runtime 500.

---

## Admin Registration Editing (SUPER_ADMIN + ACCOUNT_ADMIN)

> **Last updated:** June 2026 — admin edit modal + partial-update API.

**Roles:** `SUPER_ADMIN` and `ACCOUNT_ADMIN` only (`DB_ADMIN` is **not** authorized on the details endpoint). `DB_ADMIN` may still use the legacy name-only endpoint.

**Frontend entry points:**
- `sdm-frontend/src/components/registrations-display.tsx` — edit modal, save handlers, Assign Haridham sheet
- `sdm-frontend/src/components/registrations-data-table.tsx` — registrations table columns and row actions

### Recent Registrations table (current UI)

| Column / action | Behavior |
|---|---|
| Name | Display only (registrant1 + registrant2); no inline Edit button |
| Karyakarta, Submitted, Remaining, Status, Receipt, Date | Display / existing actions |
| Haridham ID | **Assign** button when `User.haridhamId` is empty; shows ID text when assigned (no pencil in table) |
| Docs, Delete, Txn | Unchanged for authorized roles |
| **Edit** (end column) | Opens edit modal — SUPER_ADMIN + ACCOUNT_ADMIN only |

**Removed from main table:** Phone and Mahayag Place columns (editable in modal instead).

### Edit modal fields

All fields support **partial save** — only changed fields are sent; others remain unchanged.

| Field | UI control | Backend |
|---|---|---|
| Registrant 1 name | Text input | `registrant1` on details PATCH; also updates `User.name` |
| Registrant 2 name | Text input (only if registration has non-empty `registrant2`) | `registrant2` on details PATCH |
| Mahayag Place | **Dropdown only** (from `GET /api/form/mahayag-rates`) | `mahayagPlace` — recalculates `mahayagSeva`, `remainingSeva`, `fullyPaidSevaAt` |
| Phone | Text input (10-digit IN) | `phone1` + `countryCode1`; duplicate check on active registrations and users |
| Haridham ID | Text input (always visible in modal) | `PATCH .../assign-personal-haridham-id` when value changed |
| Karyakarta Haridham ID | Text + Lookup (`GET /api/form/karyakarta/:code`) | `karyakartaHaridhamId` — updates `mandalId`, `pradeshId` on Registration + User |

**Place selection:** Mahayag place is changed **only** via the dropdown. There is no free-text “place name” field in the edit modal. The `district` field is not exposed in this UI.

**Haridham ID split:**
- **Table:** first-time **Assign** only (opens small Assign sheet).
- **Edit modal:** view/change Haridham ID for registrations (assign or update on Save).

### Primary API — partial registration update

```
PATCH /api/admin/registrations/:registrationId/details
Authorization: SUPER_ADMIN | ACCOUNT_ADMIN
```

**Body** (at least one field required; omit unchanged fields):

```json
{
  "registrant1": "string",
  "registrant2": "string",
  "mahayagPlace": "04 BHAKTI",
  "phone1": "9865432102",
  "countryCode1": "+91",
  "karyakartaHaridhamId": "4287"
}
```

**Mahayag place change rules:**
- Looks up rate from `MahayagRates` or `NriMahayagRates` based on `registrationCountryType`.
- Keeps `submittedSeva` unchanged; sets `mahayagSeva` from rate and `remainingSeva = mahayagSeva - submittedSeva`.
- Returns `400` if `submittedSeva` exceeds the new place total.

**Phone change rules:**
- Normalizes to 10 digits for `+91`.
- Returns `400` with `"A registration with this phone number already exists"` or `"A user with this phone number already exists"` on conflict (`isDeleted: false`, excluding current registration/user).

**Karyakarta change rules:**
- Validates karyakarta exists by `haridhamId`.
- Updates `Registration.karyakartaHaridhamId`, `mandalId`, `pradeshId` and matching fields on linked `User`.

**Response `data`** includes updated registration fields plus resolved `karyakartaName`, `mandalName`, `pradeshName` when applicable.

### Haridham ID assign / update (separate call from modal save)

```
PATCH /api/admin/registrations/:registrationId/assign-personal-haridham-id
Authorization: SUPER_ADMIN | ACCOUNT_ADMIN
Body: { "haridhamId": "2314" }
```

- Updates `User.haridhamId`.
- Rejects duplicate personal Haridham ID on another active registration.
- Allows same ID on a karyakarta account (self-registration edge case).

The edit modal calls this endpoint when Haridham ID changed, in addition to the details PATCH when other fields changed.

### Legacy / auxiliary admin endpoints

| Endpoint | Roles | Purpose |
|---|---|---|
| `PATCH .../name` | SUPER_ADMIN, ACCOUNT_ADMIN, **DB_ADMIN** | Name-only partial update (`registrant1`, `registrant2`) |
| `PATCH .../assign-karyakarta` | SUPER_ADMIN, ACCOUNT_ADMIN | Assign by registration id + karyakarta code |
| `PATCH .../change-karyakarta-by-haridham-id` | SUPER_ADMIN, ACCOUNT_ADMIN | Change karyakarta using personal registration Haridham ID |
| `GET .../by-personal-haridham-id?haridhamId=` | SUPER_ADMIN, ACCOUNT_ADMIN | Lookup before karyakarta change by personal ID |

Use `GET /api/admin/registrations/by-personal-haridham-id?haridhamId=...` before `change-karyakarta-by-haridham-id`. If multiple active registrations share the same personal Haridham ID, the API returns `409` instead of guessing.

---

## Registrations list (admin UI)

> **Last updated:** June 2026

The registrations table shows **ID** as the first column. Default sort is **registration ID descending** (highest ID first).

- Frontend: `sdm-frontend/src/components/registrations-display.tsx`, `registrations-data-table.tsx`
- Backend: `GET /api/registrations` — `sortBy=id`, `sortOrder=desc` (defaults when query params omitted)
- Sortable fields include `id`, `registrant1`, `phone1`, `submittedSeva`, `remainingSeva`, `status`, `receipt`, `createdAt`

---

## Haridham receipt count (`uploaded/total`)

> **Last updated:** June 2026

The registrations **Receipt** column (e.g. `1/1`) must **not** count deleted or failed transactions.

**Excluded:** any transaction whose `status` contains `DELETED` or `FAILED` (e.g. `DELETED_BY_KARYAKARTA`, `FAILED_TO_REACH_HARIDHAM`).

**Shared logic:**
- Backend: `src/utils/receiptTransactionUtils.js` → `getHaridhamReceiptCounts()`
- Used in `registrationController.js` for `transactionCount`, `haridhamReceiptUploadedCount`, and receipt sorting
- Frontend: `src/lib/receipt-transactions.ts` → `getHaridhamReceiptSummary()`

Deleted transactions may still appear in the payment history sheet for audit; they must not inflate the table/export `1/2` summary.
