# Applications and lead flow: implementation guide

> This describes the earlier basic lead integration. For the current Pending, KYC and status workflow, see [PENDING_APPLICATIONS.md](PENDING_APPLICATIONS.md).

This document records the application work across two projects:

- **Backend:** `C:\Users\ashut\Desktop\goldloan` (this directory)
- **Frontend:** `C:\Users\ashut\Desktop\atd-gold-loan`

The website Apply form and the CRM New Lead form now write to **one MySQL `applications` table**. They enter through different API routes because the website is public and CRM creation requires an admin login. The CRM Create Customer page reads pending rows from that same table.

## 1. Request flow at a glance

```text
Website /apply
  -> POST /api/public/applications (no token)
  -> public rate limiter
  -> ApplicationController.createPublic
  -> ApplicationService.createApplication(input, { source: 'WEB' })
  -> ApplicationRepository.create
  -> MySQL applications table

CRM /crm/create-customer > New Lead
  -> POST /api/crm/applications (admin Bearer token)
  -> authenticateAdmin
  -> ApplicationController.createCrm
  -> ApplicationService.createApplication(input, { source: 'CRM', admin: req.user })
  -> ApplicationRepository.create
  -> same MySQL applications table

CRM /crm/create-customer
  -> GET /api/crm/applications (admin Bearer token)
  -> ApplicationService.listPending
  -> ApplicationRepository.findPending
  -> pending rows and pagination metadata
```

The client sends applicant details. The backend assigns `status`, `source`, and CRM attribution. A browser-supplied `status`, `source`, `createdByAdminId`, or `branchId` does not override the server's values.

## 2. Backend changes, in implementation order

### Step 1: Define the table in Prisma

**Updated:** `prisma/schema.prisma`

- Added `Application` mapped to the MySQL `applications` table.
- Added `ApplicationStatus` with current value `PENDING` and `ApplicationSource` with `WEB` and `CRM`.
- Added required `name`, `mobile`, `status`, and `source`; optional `email`, `dob`, `preferredBranch`, `state`, `city`, `pin`, and `purpose`.
- Added `createdByAdminId` and `branchId` as optional foreign keys to `admins` and `branches`. A public row has null values for these; a CRM row stores the authenticated admin and branch.
- Added `createdAt` and `updatedAt`, plus indexes for pending/date lists, branch/status/date lists, and mobile lookup.
- Added reverse `applications` relations to the existing `Admin` and `Branch` models.

`preferredBranch` is a text field for the applicant's requested branch. It is separate from `branchId`, which records the CRM admin's actual branch.

**Updated:** `package.json`  
**Added:** `scripts/run-prisma.js`

The Prisma scripts now load this backend's `.env.local` before running the Prisma CLI. This makes `npm run prisma:generate` and `npm run db:push` use the same database settings as the server. `prisma:generate` creates/updates Prisma Client code; `db:push` applies the schema to MySQL. Starting `npm run dev` alone does not create tables.

**Database action performed:** Prisma Client was generated and `npm run db:push` synchronized the existing `gold_loan` database. The `applications` table was created without replacing the existing `admins`, `branches`, or `users` tables.

### Step 2: Convert database records to API data

**Added:** `src/models/Application.js`

This class converts Prisma's `BigInt` IDs to JSON-compatible numbers and converts `dob` to `YYYY-MM-DD`. It exposes applicant fields, status, source, admin/branch IDs, and timestamps in API responses.

### Step 3: Add database operations

**Added:** `src/repositories/ApplicationRepository.js`

- `create(data)` inserts one row through `prisma.application.create`.
- `findAdmin(id, branchId)` confirms that the admin and branch carried by the signed token still match a current admin row before CRM creation.
- `findPending(...)` counts and returns pending rows. It searches name, mobile, email, and numeric application ID; filters by creation date; orders newest first; and applies `skip`/`take` pagination. The count and rows are requested in one Prisma transaction.

### Step 4: Add shared creation and list rules

**Added:** `src/services/ApplicationService.js`

Both POST routes call the same `createApplication(input, context)` method. It requires a name of 2–100 characters and a 10-digit Indian mobile beginning with 6–9. Optional values are validated when supplied: email format and length, real `YYYY-MM-DD` DOB that is not in the future, six-digit PIN, and text length limits. Empty optional fields become null.

The service sets every new row to `PENDING`. The public controller passes `source: 'WEB'`, leaving admin and branch IDs null. The CRM controller passes `source: 'CRM'` plus the verified token payload; the service looks up the matching admin and stores its IDs. This is why the two routes can share database logic without trusting the request body for security fields.

`listPending(query)` validates `status`, `page`, `per_page`, `search`, `date_from`, and `date_to`. Currently only `status=PENDING` is supported. `page` defaults to 1; `per_page` defaults to 10 and is limited to 100. Dates use `YYYY-MM-DD`; `date_to` includes the whole selected day. The response includes total rows, current page, page size, and total pages.

### Step 5: Add HTTP controllers and route access rules

**Added:** `src/controllers/ApplicationController.js`

- `createPublic` returns HTTP 201 and the saved row.
- `createCrm` returns HTTP 201 and the saved row.
- `listCrm` returns HTTP 200 with `data` and `pagination`.

**Added:** `src/routes/publicRoutes.js`  
**Updated:** `src/routes/index.js`  
**Updated:** `src/routes/crmRoutes.js`

`index.js` mounts public routes under `/api/public` and CRM routes under `/api/crm` because `src/app.js` already mounts the combined router under `/api`. The CRM application routes are placed **after** `router.use(authenticateAdmin)`, so they require a valid admin Bearer token. The public POST has no login middleware.

**Added:** `src/middlewares/publicApplicationRateLimit.js`

The public POST allows five attempts per client IP in 15 minutes in this Node process. A sixth attempt returns HTTP 429 and `Retry-After`. Invalid submissions count as attempts. This is an in-memory development limit; multiple server processes would need a shared store. No real CAPTCHA or OTP was added.

### Step 6: Update backend setup documentation

**Added:** `.env.example` and `.gitignore`  
**Updated:** `README.md` and `BACKEND_GUIDE.md`

The example environment file uses placeholders, while `.gitignore` excludes local secrets. The README now describes `.env.local`, Prisma table creation, application routes, and request fields. The beginner guide points to the current implementation; its longer lead example is still a teaching example, not the implemented endpoint.

### Existing backend files used but not changed

- `server.js` calls `initializeDatabase()` before listening.
- `src/config/env.js` loads `.env.local` for the server.
- `src/config/database.js` creates the Prisma connection and seeds the initial branch/admin if configured. The new application route uses its exported `prisma` object.
- `src/app.js` mounts all API routes at `/api` and provides JSON parsing, CORS, `/health`, and error handling.
- `src/middlewares/authMiddleware.js` verifies the admin JWT and sets `req.user`.
- `src/utils/jwt.js` signs and verifies those tokens.
- The existing login and branch API files were left in place.

## 3. API contract given to the frontend

Local backend base URL: `http://localhost:4000/api`. The frontend reads `NEXT_PUBLIC_API_URL` and currently points to that base URL for local development.

### Public application

`POST /api/public/applications` needs no login. Required JSON fields are `name` and `mobile`; optional fields are `email`, `dob`, `branch`, `state`, `city`, `pin`, and `purpose`.

```json
{
  "name": "Example Applicant",
  "mobile": "9876543210",
  "email": "example@example.com",
  "dob": "1995-06-12",
  "branch": "Noida",
  "state": "Uttar Pradesh",
  "city": "Noida",
  "pin": "201301"
}
```

Success is HTTP 201 with `{ "success": true, "data": { ... } }`. The returned `data` includes an application `id`, `status: "PENDING"`, and `source: "WEB"`. The public row has null `createdByAdminId` and `branchId`.

### CRM application

`POST /api/crm/applications` uses the same applicant fields and validation. It requires `Authorization: Bearer <accessToken>` from `POST /api/crm/login`. A successful row has `source: "CRM"`, `status: "PENDING"`, and `createdByAdminId`/`branchId` from the authenticated admin. Sending those fields in the JSON body cannot change them.

### CRM pending list

`GET /api/crm/applications?status=PENDING&page=1&per_page=10&search=&date_from=&date_to=` also requires the admin Bearer token. A typical response shape is:

```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "Example Applicant",
      "mobile": "9876543210",
      "status": "PENDING",
      "source": "WEB"
    }
  ],
  "pagination": {
    "total": 1,
    "current_page": 1,
    "per_page": 10,
    "total_pages": 1
  }
}
```

The real response rows include more fields than this shortened example. Common failures are HTTP 400 for invalid input/filter values, 401 for missing or expired admin tokens, and 429 for the public rate limit.

## 4. Frontend changes, by page and component

These files are in `C:\Users\ashut\Desktop\atd-gold-loan`.

### Website Apply page

**Updated:** `src/components/pages/apply/ApplyForm.jsx`

- The form now calls `createPublicApplication(values)` on submit and shows the returned application ID as a reference number.
- It resets only after a successful API response and shows an error message if the request fails.
- Name and mobile remain required. DOB, branch, state, city, PIN, and email were made optional in form validation to match the backend; supplied PIN and email are still checked.
- The previous `123456` demo OTP screen and success message were removed because they did not verify a phone or save a row.
- The submit button now says **Submit Application**.

**Updated:** `src/components/pages/apply/ApplyPage.jsx`

The page copy now describes an enquiry and later contact from the team. It no longer promises instant pre-approval or a specific cash-disbursement time from this initial form.

### Shared frontend API calls

**Added:** `src/lib/services/applicationService.js`

- `createPublicApplication(payload)` uses plain `fetch` without an admin token, posts to the public API, and raises the backend error message when available.
- `createCrmApplication(payload)` uses the existing Axios instance, which attaches the logged-in admin Bearer token.
- `getPendingApplications(params)` uses Axios with `status=PENDING`, page, page size, search, and date filters.

The existing `src/utils/axiosInsatnce.js` supplies the CRM base URL, token header, and response handling; it was **used but not edited**.

### CRM Create Customer page

**Updated:** `src/components/crm/create-customer/CreateCustomer.jsx`

- Replaced the local Zustand `useLoanStore().applications` list with `GET /api/crm/applications`.
- Search, date filters, and page controls now ask the backend for filtered and paginated rows. The page shows loading, empty, and request-error states.
- **New Lead** calls `POST /api/crm/applications`, then refreshes the pending list after a successful save.
- The table shows a **Source** column (`WEB` or `CRM`). The page currently lists only `PENDING` rows.

**Updated:** `src/components/crm/create-customer/NewLeadModal.jsx`

The modal waits for the asynchronous CRM save. It closes and resets only on success, shows an error toast on failure, and disables the Save button while saving.

**Updated:** `src/components/crm/create-customer/CreateCustomerTableRow.jsx`

The row displays the new `source` field and defaults to `PENDING`. The old KYC action cell was removed from this queue because that KYC page still looks only in the mock store and cannot open these database rows yet.

### CRM login and frontend documentation

**Updated:** `src/lib/store/authAdminStore.js`

The `admin/admin` demo login bypass was removed. CRM creation and list pages now require a real backend login token. The existing `POST /api/crm/login` integration remains.

**Updated:** `PROJECT_FLOW.md`

Its current-state note and integration order now say which lead pages use the real API and which later CRM screens still use mock data.

The Next.js route files for `/apply` and `/crm/create-customer` were **not changed**; they already render the components above. The existing mock store remains for other CRM screens.

## 5. How to set up and verify this change

1. In the backend directory, set local database/admin values in `.env.local`. Never put real passwords in `.env.example` or frontend code.
2. If changing the Prisma schema, stop the backend dev server on Windows first so its Prisma DLL is not locked. Run `npm.cmd run prisma:generate`, then `npm.cmd run db:push` (or the equivalent `npm` commands in a terminal that permits them).
3. Start the backend with `npm.cmd run dev`. Check `http://localhost:4000/health`.
4. In the frontend directory, keep `NEXT_PUBLIC_API_URL=http://localhost:4000/api` for local use and start it with `npm.cmd run dev`.
5. Open `http://localhost:3000/apply`. Submit a name and mobile. Note the reference ID in the success message.
6. Log in at `http://localhost:3000/crm` using the real admin credentials. Open **Create Customer** at `/crm/create-customer`. The public row should appear with `source=WEB` and `status=PENDING`. Refresh the page if it was already open during submission.
7. Click **New Lead** in CRM and save another applicant. It should appear in the same list with `source=CRM` and the logged-in admin/branch IDs in MySQL.
8. In MySQL Workbench, confirm both kinds of rows with:

```sql
SELECT id, name, mobile, status, source,
       preferred_branch, created_by_admin_id, branch_id, created_at
FROM gold_loan.applications
ORDER BY id DESC;
```

During implementation, both POST routes, authentication, validation, server-controlled attribution, pagination/search, date filtering, and the public 429 limit were exercised through HTTP. The temporary test rows were removed afterward. The frontend production build completed successfully, and both `/apply` and `/crm/create-customer` returned HTTP 200 locally.

## 6. Current scope and next work

- The public endpoint has an in-memory, single-process rate limit. It has no real OTP/CAPTCHA. Add server-side phone verification or bot protection before relying on the website at scale.
- The Apply form's branch dropdown is currently a hardcoded list of names; `preferredBranch` stores that text. It does not automatically assign a real `branches.id`.
- The Create Customer page refetches on open, filter/page change, and after CRM creation. An already-open CRM tab needs a refresh to show a newly submitted website row.
- KYC, follow-up, other application statuses, and later loan stages still use mock frontend state or lack corresponding backend APIs. The next integration is to read a saved application by ID and attach real KYC data before restoring the KYC action in this queue.
- Only `PENDING` is available in the current backend status enum and list API. Later status transitions require schema and API work.
