# audit-requests module

Drop-in NestJS module for the marketing → coordinator → auditor audit-request workflow. Matches the exact conventions of your existing `audit-schedules` module.

## Folder structure

```
audit-requests/
├── audit-requests.module.ts          ← register in AppModule
├── audit-requests.controller.ts      ← REST endpoints
├── dto/
│   ├── create-audit-request.dto.ts   ← marketing submission form
│   ├── update-audit-request.dto.ts   ← marketing edit
│   ├── schedule-audit-request.dto.ts ← coordinator schedule action
│   ├── reject-audit-request.dto.ts   ← coordinator reject
│   ├── cancel-audit-request.dto.ts   ← cancellation
│   └── list-audit-requests.dto.ts    ← list filters
├── entities/
│   └── audit-request.entity.ts       ← maps to audit_requests table
├── services/
│   └── audit-requests.service.ts     ← business logic + notifications
└── migrations/
    └── 1715800000000-CreateAuditRequestsTable.ts
```

## Installation

### 1. Drop the folder into your project

Place under `src/audit-requests/` (same level as your existing `audit-schedules/`).

### 2. Register the module in `AppModule`

```ts
import { AuditRequestsModule } from './audit-requests/audit-requests.module';

@Module({
  imports: [
    // ... your other modules
    AuditSchedulesModule,
    AuditRequestsModule,   // ← add this line
  ],
})
export class AppModule {}
```

### 3. ⚠ REQUIRED: Add the FK column to your existing AuditScheduleRow entity

The migration adds the `audit_request_id` column to the `audit_schedule_rows` table. You must also add the corresponding TypeScript property to your existing entity file at:

```
src/audit-schedules/entities/audit-schedule-row.entity.ts
```

Add this property anywhere in the `AuditScheduleRow` class:

```ts
import {
  // ... your existing imports
  OneToOne,
} from 'typeorm';

// At the top of the file, add this import:
import { AuditRequest } from '../../audit-requests/entities/audit-request.entity';

// Inside the AuditScheduleRow class, add this column:
@Column({ type: 'bigint', nullable: true })
audit_request_id: number | null;

@OneToOne(() => AuditRequest, { nullable: true })
@JoinColumn({ name: 'audit_request_id' })
audit_request: AuditRequest | null;
```

That's the **only** change to your existing audit-schedules module.

### 4. Add the new notification types

In `src/notifications/enums/notification-type.enum.ts`, add:

```ts
export enum NotificationType {
  // ... your existing types
  AUDIT_REQUEST_SUBMITTED = 'AUDIT_REQUEST_SUBMITTED',
  AUDIT_REQUEST_SCHEDULED = 'AUDIT_REQUEST_SCHEDULED',
  AUDIT_REQUEST_REJECTED = 'AUDIT_REQUEST_REJECTED',
}
```

### 5. Run the migration

```bash
npm run typeorm migration:run
```

This will:
- Create the `audit_requests` table
- Add `audit_request_id BIGINT NULL` column to your existing `audit_schedule_rows`
- Add an index + FK constraint on the new column
- Add reverse FK on `audit_requests.audit_schedule_row_id`

### 6. Verify your User entity has a role field

The service queries `User WHERE role = 'COORDINATOR'` to find recipients. If your role check is different (e.g. a separate roles table), adjust the `notifyRequestSubmitted` method in `services/audit-requests.service.ts`.

The controller also checks `req.user.role === 'MARKETING'` to scope marketing's view to their own requests only. Adjust this in `audit-requests.controller.ts` `isMarketingOnly()` if needed.

## API endpoints

| Method | Path                                | Role                   | Action                              |
|--------|-------------------------------------|------------------------|-------------------------------------|
| GET    | `/audit-requests`                   | all                    | List (marketing sees own only)      |
| GET    | `/audit-requests/analytics`         | all                    | Dashboard counts                    |
| GET    | `/audit-requests/:id`               | all                    | Get one with relations              |
| POST   | `/audit-requests`                   | marketing, admin       | Submit new request                  |
| PATCH  | `/audit-requests/:id`               | requester only         | Edit (pre-schedule)                 |
| PATCH  | `/audit-requests/:id/review`        | coordinator, admin     | Auto-mark UNDER_REVIEW              |
| POST   | `/audit-requests/:id/schedule` ⭐   | coordinator, admin     | Create audit_schedule_row + notify  |
| PATCH  | `/audit-requests/:id/reject`        | coordinator, admin     | Reject with reason                  |
| PATCH  | `/audit-requests/:id/cancel`        | requester, coordinator | Cancel (pre-schedule)               |

## How the schedule action works

When the coordinator clicks "Confirm & notify" in the schedule modal, ONE transaction does:

1. **SELECT** with pessimistic lock on `audit_requests` (prevent double-scheduling)
2. **SELECT** or **INSERT** parent `audit_schedules` row for that date + coordinator
3. Generate `audit_code` via your existing `AuditCodeGeneratorService`
4. **INSERT** new `audit_schedule_rows` with `audit_request_id` set
5. **INSERT** `audit_status_history` entry
6. **UPDATE** `audit_requests`: status → 'SCHEDULED', link to row, set timestamps

If any step fails → ALL roll back. After commit, notifications fire (marketing + auditor + coordinator), exactly mirroring your existing `notifyAuditRowCancelled` / `notifyAuditRowRescheduled` pattern.

## Status lifecycle

```
DRAFT → SUBMITTED → UNDER_REVIEW → SCHEDULED → COMPLETED
                          ↘ REJECTED
                          ↘ CANCELLED
```

- `DRAFT` only used if you implement "save draft" later
- `SUBMITTED` set automatically on POST /audit-requests
- `UNDER_REVIEW` set when coordinator opens the detail page first time
- `SCHEDULED` set inside the schedule transaction
- `COMPLETED` should be synced from `audit_schedule_rows.status = COMPLETED` (you can add a listener later, or update it via a separate sync job)

## Frontend response shape

The list endpoint returns `{ data, meta: { total, page, limit, totalPages } }` — same as your `audit-schedules` endpoints, so your frontend pagination logic works unchanged.
