# Admission Module — Implementation Workflow

This document explains the internal implementation flow for the Admission module. It is intended for developers building or maintaining this module.

---

## Module Dependencies

```
AdmissionModule
├── PrismaService          (database access)
├── PaymentModule          (verify application fee before allowing form submission)
├── AuditModule            (log application submissions, reviews, offers)
├── NotificationModule     (notify applicants of status changes)
└── AuthModule             (JWT userId extraction)
```

---

## Complete Admission Pipeline

```
┌─────────────┐    ┌──────────────┐    ┌──────────────────┐    ┌──────────────┐
│  Register   │───▶│ Pay App Fee  │───▶│  Fill 8-Step     │───▶│ Admin Review │
│  (Auth)     │    │  ₦10,000     │    │  Application     │    │              │
│             │    │  (Payment)   │    │  Form            │    │              │
└─────────────┘    └──────────────┘    └──────────────────┘    └──────┬───────┘
                                                                      │
                                                        ┌─────────────┴─────────────┐
                                                        │                           │
                                                   ┌────▼────┐              ┌───────▼───────┐
                                                   │ ADMITTED │              │   REJECTED    │
                                                   └────┬────┘              └───────────────┘
                                                        │
                                                   ┌────▼──────────┐
                                                   │ Create        │
                                                   │ Admission     │
                                                   │ Offer         │
                                                   └────┬──────────┘
                                                        │
                                                   ┌────▼──────────┐
                                                   │ Applicant     │
                                                   │ Accepts Offer │
                                                   └────┬──────────┘
                                                        │
                                               ┌───────▼────────────┐
                                               │ Pay Acceptance Fee │
                                               │ ₦30,000 (Payment) │
                                               └───────┬────────────┘
                                                       │
                                               ┌───────▼────────────┐
                                               │ Pay Tuition Fee    │
                                               │ ₦195,000 (Payment) │
                                               └───────┬────────────┘
                                                       │
                                               ┌───────▼────────────┐
                                               │ Student Record     │
                                               │ Created (auto)     │
                                               └────────────────────┘
```

---

## 1. Application Submission Flow

**Endpoint:** `POST /admissions/applications`

```
Authenticated user submits multipart/form-data
         │
         ▼
┌──────────────────────────────────────────────────┐
│  AdmissionController.submitApplication(req)      │
│  - @UseGuards(AuthGuard)                         │
│  - @UseInterceptors(FileFieldsInterceptor([      │
│      { name: 'passport', maxCount: 1 },          │
│      { name: 'first_school_leaving', maxCount: 1},│
│      { name: 'o_level', maxCount: 1 },           │
│      { name: 'first_sitting_result', maxCount: 1},│
│      { name: 'second_sitting_result', maxCount: 1},│
│      { name: 'other_documents', maxCount: 5 },   │
│    ]))                                           │
│  - Extract userId from JWT (@CurrentUser)        │
│  - Validate CreateApplicationDto (text fields)   │
│  - Validate file sizes & MIME types              │
└────────────┬─────────────────────────────────────┘
             │
             ▼
┌──────────────────────────────────────────────────┐
│  AdmissionService.submitApplication(userId, dto) │
│                                                  │
│  STEP 1: Payment Gate Check                      │
│  ┌────────────────────────────────────────────┐  │
│  │ Query PaymentService or directly:          │  │
│  │ - Find Invoice where userId = X            │  │
│  │   AND feeType = "Application Fee"          │  │
│  │   AND status = "PAID"                      │  │
│  │ - Not found → 403 "Application fee not     │  │
│  │   paid. Please pay ₦10,000 first."         │  │
│  └────────────────────────────────────────────┘  │
│                                                  │
│  STEP 2: Duplicate Check                         │
│  ┌────────────────────────────────────────────┐  │
│  │ Find existing application for this userId  │  │
│  │ - Found → 409 "Application already exists" │  │
│  └────────────────────────────────────────────┘  │
│                                                  │
│  STEP 3: Conditional Validation                  │
│  ┌────────────────────────────────────────────┐  │
│  │ Enforce conditional rules:                 │  │
│  │ - has_disability=true → disability required│  │
│  │ - has_sponsor=true → all sponsor fields    │  │
│  │ - awaiting_result=false → exam sittings    │  │
│  │ - combined_result="combined_result"        │  │
│  │   → second sitting fields required         │  │
│  │ - agreeToTerms must be true                │  │
│  └────────────────────────────────────────────┘  │
│                                                  │
│  STEP 4: Create Records (Transaction)            │
│  ┌────────────────────────────────────────────┐  │
│  │ 1. Generate applicationNumber              │  │
│  │    ("APP-{year}-{sequence}")                │  │
│  │ 2. Create AdmissionApplication record      │  │
│  │    - userId from JWT                       │  │
│  │    - All form fields mapped to columns     │  │
│  │    - status = "SUBMITTED"                  │  │
│  │ 3. Upload files to object storage          │  │
│  │ 4. Create ApplicationDocument records      │  │
│  │    for each uploaded file                  │  │
│  └────────────────────────────────────────────┘  │
│                                                  │
│  STEP 5: Post-submit                             │
│  - Send notification (application received)      │
│  - Log via AuditService                          │
│  - Return application summary                    │
└──────────────────────────────────────────────────┘
```

### Conditional Validation Matrix

```
has_disability=true
  └── disability: required, non-empty, not "None"

has_sponsor=true
  ├── sponsor_name: required
  ├── sponsor_relationship: required
  ├── sponsor_email: required, valid email
  ├── sponsor_contact_address: required, min 10 chars
  └── sponsor_phone_number: required, min 10 chars

awaiting_result=false
  ├── combined_result: required ("single_result" | "combined_result")
  ├── first_sitting_type: required
  ├── first_sitting_year: required
  ├── first_sitting_exam_number: required
  └── first_sitting_result: required (file)

combined_result="combined_result"
  ├── second_sitting_type: required
  ├── second_sitting_year: required
  ├── second_sitting_exam_number: required
  └── second_sitting_result: required (file)

Always:
  └── agreeToTerms: must be true
```

---

## 2. Application Review Flow

**Endpoint:** `PATCH /admissions/applications/:id/review`

```
Admin/Staff submits review decision
         │
         ▼
┌──────────────────────────────────────────────────┐
│  AdmissionService.reviewApplication(id, dto)     │
│  1. Find application by ID                       │
│     - Not found → 404                            │
│  2. Validate current status allows review:       │
│     - SUBMITTED → can move to UNDER_REVIEW       │
│     - UNDER_REVIEW → can move to ADMITTED/REJECTED│
│     - ADMITTED/REJECTED → 400 "Already decided"  │
│  3. Update application:                          │
│     - status = dto.status                        │
│     - reviewedBy = admin userId                  │
│     - reviewedAt = now                           │
│     - reviewComments = dto.reviewComments         │
│  4. Send notification to applicant               │
│  5. Log via AuditService                         │
└──────────────────────────────────────────────────┘
```

### Status Transition Rules

```
SUBMITTED ──────▶ UNDER_REVIEW     (admin starts reviewing)
UNDER_REVIEW ───▶ ADMITTED         (admin approves)
UNDER_REVIEW ───▶ REJECTED         (admin rejects)

Invalid transitions (→ 400 Bad Request):
SUBMITTED ──────▶ ADMITTED         (cannot skip review)
SUBMITTED ──────▶ REJECTED         (cannot skip review)
ADMITTED ───────▶ anything         (final state)
REJECTED ───────▶ anything         (final state)
```

---

## 3. Admission Offer Creation

**Endpoint:** `POST /admissions`

```
Admin creates offer for ADMITTED application
         │
         ▼
┌──────────────────────────────────────────────────┐
│  AdmissionService.createOffer(dto)               │
│  1. Find application by dto.applicationId        │
│     - Not found → 404                            │
│     - Status !== ADMITTED → 400                  │
│  2. Check no existing Admission for this app     │
│     - Found → 409                                │
│  3. Create Admission record:                     │
│     - applicationId, admissionNumber             │
│     - programId, levelId, sessionId              │
│     - admissionDate, admissionType               │
│     - status = "OFFERED"                         │
│     - expiryDate (if set)                        │
│  4. Send notification: "You've been offered..."  │
│  5. Log via AuditService                         │
└──────────────────────────────────────────────────┘
```

**Note:** `matricNumber` is NOT assigned here. It is generated later when the Student record is created after tuition payment.

---

## 4. Accept/Decline Offer

**Endpoint:** `PATCH /admissions/:id/accept`

```
Applicant accepts the admission offer
         │
         ▼
┌──────────────────────────────────────────────────┐
│  AdmissionService.acceptOffer(admissionId, userId)│
│  1. Find Admission by ID                         │
│     - Verify admission.application.userId = JWT  │
│  2. Check status === "OFFERED"                   │
│     - DECLINED/EXPIRED → 400                     │
│  3. Check expiryDate (if set)                    │
│     - Past expiry → auto-set EXPIRED, return 400 │
│  4. Update Admission status = "ACCEPTED"         │
│  5. Trigger: create acceptance fee invoice        │
│     (₦30,000) via PaymentModule                  │
│  6. Send notification: "Acceptance confirmed..."  │
│  7. Return response with next steps message       │
└──────────────────────────────────────────────────┘
```

---

## 5. File Upload Strategy

```
Files arrive as multipart/form-data
         │
         ▼
┌──────────────────────────────────────────────────┐
│  For each uploaded file:                         │
│  1. Validate:                                    │
│     - Size ≤ 5MB                                 │
│     - MIME type ∈ allowed list                   │
│  2. Generate unique filename:                    │
│     - "{userId}_{timestamp}_{originalName}"      │
│  3. Upload to object storage (S3/MinIO):         │
│     - Path: "admissions/{applicationId}/{file}"  │
│  4. Create ApplicationDocument record:           │
│     - applicationId                              │
│     - documentType (passport, o_level, etc.)     │
│     - fileName (original)                        │
│     - filePath (storage path)                    │
│     - fileSize, mimeType                         │
└──────────────────────────────────────────────────┘
```

---

## 6. ApplicationNumber Generation

```ts
// Pattern: APP-{YEAR}-{5-digit-sequence}
// Example: APP-2025-00001, APP-2025-00002

async generateApplicationNumber(): Promise<string> {
  const year = new Date().getFullYear();
  const prefix = `APP-${year}-`;

  // Find the last application number for this year
  const last = await this.prisma.admissionApplication.findFirst({
    where: { applicationNumber: { startsWith: prefix } },
    orderBy: { applicationNumber: 'desc' },
    select: { applicationNumber: true },
  });

  const nextSeq = last
    ? parseInt(last.applicationNumber.split('-')[2]) + 1
    : 1;

  return `${prefix}${String(nextSeq).padStart(5, '0')}`;
}
```

---

## 7. Files to Create

```
src/modules/admission/
├── admission.module.ts
├── admission.controller.ts         # All admission routes
├── admission.service.ts            # Business logic
├── dto/
│   ├── create-application.dto.ts   # All form fields + conditional validation
│   ├── review-application.dto.ts   # { status, reviewComments }
│   ├── create-admission.dto.ts     # Admission offer DTO
│   └── query-application.dto.ts    # Filters + pagination
└── admission.service.spec.ts
```

---

## 8. Cross-Module Integration Points

| Direction | Module | Interaction |
| --------- | ------ | ----------- |
| **Consumes** | PaymentModule | Check if application fee is paid before form access |
| **Consumes** | AuthModule | JWT userId extraction, guards |
| **Consumes** | NotificationModule | Send status update emails |
| **Consumed by** | PaymentModule | Read application/admission data when creating Student record |
| **Consumed by** | UserModule | Student bio-data source (read from AdmissionApplication) |
