# Cashflow & Transactions

## Overview

Payments flow through a multi-tier hierarchy: registrant pays → karyakarta collects cash → pradesh admin settles → super admin accepts. Online payments go through Razorpay and bypass the cash collection steps.

---

## Transaction Model

```
Transaction
  ├── registrationId
  ├── amount
  ├── method                      -- "CASH" or "ONLINE"
  ├── status                      -- See status state machine below
  ├── paymentSessionId            -- Links to PaymentSession if online
  │
  -- Cash collection timestamps:
  ├── cashRequestedToKaryakartaAt
  ├── cashAcceptedByKaryakartaAt
  ├── cashRequestedToPradeshAt
  ├── cashAcceptedByPradeshAt
  │
  -- Receipt:
  ├── receiptGeneratedAt
  ├── receiptNumber
  ├── receiptUrl
  ├── haridhamReceiptId
  ├── haridhamReceiptUrl
  ├── haridhamReceiptStoragePath
  │
  -- Cheque (for certain payment methods):
  ├── chequePhotoUrl
  ├── chequeNumber
  ├── chequeApprovedAt
  └── cashflowBunchId             -- Links to CashflowBunch when bundled
```

### Transaction Status State Machine

**ONLINE transactions:**
- `SUCCESS` — Razorpay webhook confirmed payment

**CASH transactions:**
- `COLLECTED_BY_KARYAKARTA` — Karyakarta confirmed receipt of cash
- `SETTLED_BY_PRADESH` — Pradesh admin confirmed settlement to super admin

---

## CashflowBunch

A `CashflowBunch` bundles multiple CASH transactions from a single Pradesh into one settlement batch:

```
CashflowBunch
  ├── status                        -- REQUESTED_TO_SUPER_ADMIN | ACCEPTED_BY_SUPER_ADMIN
  ├── pradeshId
  ├── requestedByUserId             -- Pradesh admin who created the bunch
  ├── acceptedByUserId              -- Super admin who accepted
  ├── totalAmount
  ├── transactionCount
  ├── receiptNumber / receiptUrl    -- Generated after acceptance
  ├── acceptanceRemark              -- Optional remark entered by Super Admin on bulk accept
  ├── requestedAt
  └── acceptedAt
```

Transactions are linked to a bunch via `Transaction.cashflowBunchId`.

---

## Cashflow Controller — Critical Prisma Notes

When filtering transactions by karyakarta in `cashflowController`, use the correct field path:

```js
// CORRECT
where: {
  registration: {
    karyakartaHaridhamId: karyakartaCode
  }
}

// WRONG — causes runtime 500 (unknown field on Registration model)
where: {
  registration: {
    haridhamId: karyakartaCode   // ← This field does not exist on Registration
  }
}
```

The Prisma field is `karyakartaHaridhamId` (maps to column `karyakartaCode` in DB via `@map`).

---

## Payment Sessions (Razorpay Online)

```
PaymentSession
  ├── id (UUID)
  ├── razorpayOrderId (unique)
  ├── razorpayPaymentId
  ├── razorpaySignature
  ├── amount
  ├── currency
  ├── status                  -- created | authorized | captured | failed
  ├── registrationId
  ├── customerName / Email / Phone
  └── notes (JSON)
```

The Razorpay webhook handler receives payment confirmation and transitions the linked `PaymentSession` + `Transaction` to `SUCCESS`.

Razorpay payments must be captured before the app records an online transaction as `SUCCESS`. Order creation requests automatic capture, and backend verification/webhook handling explicitly captures `authorized` payments with `razorpay.payments.capture(paymentId, amountInPaise, currency)` before updating local `PaymentSession.status = "captured"`.

For temporary testing, set `DISABLE_ONLINE_PAYMENT_MINIMUM=true` on the backend and `NEXT_PUBLIC_DISABLE_ONLINE_PAYMENT_MINIMUM=true` on the frontend. This disables the online minimum/full-amount payment rule while keeping amount-positive and remaining-seva checks.

---

## Installment Minimum Rules

Each payment installment must be at least a **scheduled percentage of total seva** (`mahayagSeva` / `totalSeva`), **not** a percentage of remaining balance.

**Schedule (4 installments = 100%):** `20%` → `20%` → `30%` → `30%` of total seva.

- **Payment 1** (after registration): 20%
- **Payment 2:** 20%
- **Payment 3:** 30%
- **Payment 4:** 30%

The next installment percent is chosen from the schedule using the count of existing `SUCCESS` transactions on the registration.

- **Shared constants:** `sdm-backend/src/constants/paymentRules.js`, `sdm-frontend/src/lib/payment-rules.ts`
- **Enforced on:** `POST /api/payment/create-order` (`paymentController.js`) and cash/cheque requests (`cashflowController.js`)
- **UI surfaces:** registration payment step (always payment 1), home first-payment modal (payment 1), profile pay-seva (uses completed payment count)

When payable remaining is less than the scheduled percent of total, the minimum is capped at the payable remaining (after pending transactions/sessions).

**Without assigned karyakarta account:** online payment must cover the full remaining seva in a single transaction (unchanged).

**Previous rules (deprecated):** 40% + 30% + 30%; then flat 20% for every installment.

---

## Receipt Generation

After a transaction is completed (or a cashflow bunch is accepted):

1. `receiptService.js` generates a PDF receipt.
2. Receipt is uploaded to Firebase Storage.
3. `receiptUrl` and `receiptNumber` are stored on the Transaction (or CashflowBunch).
4. `haridhamReceiptFetchService.js` can fetch an external Haridham receipt (if integration is active).

---

## Analytics Segmentation

Cashflow analytics segments by:
- Pradesh
- Transaction method (CASH vs ONLINE)
- Registration country type (INDIAN vs NRI)
- Date range

All queries must include `isDeleted: false` on the linked `Registration`.

---

## Pradesh cash acceptance invariant (orphan prevention)

> **Last updated:** July 2026

When Pradesh accepts **cash** (`acceptBulkCashByPradesh` / `acceptCashByPradesh`), the API must **always**:

1. Create a `CashflowBunch` with status `REQUESTED_TO_SUPER_ADMIN`
2. Set `Transaction.cashflowBunchId` on every accepted cash transaction in the **same DB transaction**

**Historical bug:** Before commit `7b8f8b3` (2026-07-05), `acceptBulkCashByPradesh` marked transactions `SUCCESS` without creating a bunch. That left “orphan” rows visible in Registrations but missing from Haridham Cashflow bunch tabs.

**Prevention (current code):**

- Shared helper: `createPradeshCashBunchAndAcceptCashTransactions` in `src/services/cashflowBunchService.js`
- Post-accept safety check: `repairIfOrphanCashTransactions` runs after each cash accept
- Startup safety net: `repairOrphanPradeshCashTransactions` on server boot

**Manual repair (if needed):**

```bash
node private-seeds/repair-orphan-cashflow-bunches.mjs
```

---

## Super Admin bulk accept (Requested Cash/Cheque Bunch)

> **Last updated:** June 2026

**Endpoint:** `PATCH /api/cashflow/super-admin/bunches/accept-bulk`  
**Roles:** Super Admin, Account Admin  
**UI:** Cashflow dashboard → Requested Cash Bunch / Requested Cheque Bunch

### Flow

1. Super Admin selects one or more bunches with status `REQUESTED_TO_SUPER_ADMIN`.
2. **Accept Selected** opens a confirmation dialog showing bunch count, transaction count, total amount, and bunch IDs.
3. **Remark** (optional) — saved on every accepted bunch as `acceptanceRemark` and shown on **Submitted Cash Bunch** / **Submitted Cheque Bunch** (table column + bunch detail sheet).
4. **Download PDF** — fetches full detail for each selected bunch and opens a single print/PDF with **all transactions** across all selected bunches (e.g. 3 bunches with 6 transactions → one PDF listing 6 rows). If a remark was entered, it is included in the PDF header.
5. **Confirm Accept** — accepts all selected bunches in one request; the same remark is applied to each.

**Request body:**

```json
{
  "bunchIds": [153, 152, 151],
  "remark": "Optional note for all accepted bunches"
}
```

**Database:** requires `CashflowBunch.acceptanceRemark` (`TEXT`, nullable). Apply with `npx prisma db push` or:

```sql
ALTER TABLE "CashflowBunch" ADD COLUMN IF NOT EXISTS "acceptanceRemark" TEXT;
```

---

## Cash on Hand dashboard

> **Last updated:** June 2026

**Endpoint:** `GET /api/cashflow/admin/cash-on-hand`  
**Roles:** Pradesh Admin, Super Admin, DB Admin, Account Admin  
**UI:** `/admin-panel/cash-on-hand`

### What this dashboard shows

**Cash in hand** = payments already **accepted by karyakarta** from registrants, but **not yet submitted to Pradesh** (`status` in `CASH_REQUESTED_TO_PRADESH` / `CHEQUE_REQUESTED_TO_PRADESH`, `cashflowBunchId` is null).

**Not included:** cash/cheque requests still waiting for karyakarta to accept from registrants (`CASH_REQUESTED_TO_KARYAKARTA`, etc.). Those appear in the Karyakarta Cashflow dashboard under Requested Transactions.

### Response tables

| Field | Purpose |
|---|---|
| `karyakartaSummary` | Per-karyakarta **cash in hand** — accepted by karyakarta, not yet submitted to Pradesh |
| `awaitingKaryakartaSummary.rows` | Cash/cheque **yet to accept** — registrant requested, karyakarta has not accepted |
| `pradeshCashOnHandSummary.rows` | **Cash on Pradesh** — one row per Pradesh; bunches submitted to Super Admin not yet accepted |

Both karyakarta tables return one row per karyakarta with `haridhamId`, `karyakartaName`, `mandalName`, `pradeshName`, `transactionCount`, and `pendingAmount`.

`pradeshCashOnHandSummary.rows` includes `pradeshId`, `pradeshName`, `bunchCount`, `transactionCount`, `pendingAmount`.

---
