unecessary files removed
This commit is contained in:
parent
17485a9cca
commit
34ea547adf
@ -1,538 +0,0 @@
|
|||||||
# Simplified Workflow API - Postman Guide
|
|
||||||
|
|
||||||
## ✅ Updated Simplified Format
|
|
||||||
|
|
||||||
The API has been updated to make workflow creation much simpler. You now only need to provide **email** and **tatHours** for approvers, and **email** for spectators. The backend automatically handles:
|
|
||||||
|
|
||||||
- User lookup/creation from Okta/Azure AD
|
|
||||||
- Fetching user details (name, department, designation)
|
|
||||||
- Auto-generating level names based on designation/department
|
|
||||||
- Auto-detecting final approver (last level)
|
|
||||||
- Proper validation with clear error messages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
### Login
|
|
||||||
```http
|
|
||||||
POST {{baseUrl}}/auth/login
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"email": "your-email@example.com",
|
|
||||||
"password": "your-password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
"user": { "userId": "...", "email": "...", ... }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Create Workflow - Simplified Format
|
|
||||||
|
|
||||||
### Example 1: Simple Workflow (JSON)
|
|
||||||
|
|
||||||
**POST** `{{baseUrl}}/workflows`
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Content-Type: application/json
|
|
||||||
Authorization: Bearer <your_token>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Purchase Order Approval - Office Equipment",
|
|
||||||
"description": "Approval needed for purchasing new office equipment including laptops and monitors. Total budget: $50,000",
|
|
||||||
"priority": "STANDARD",
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "manager@royalenfield.com",
|
|
||||||
"tatHours": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "director@royalenfield.com",
|
|
||||||
"tatHours": 48
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "cfo@royalenfield.com",
|
|
||||||
"tatHours": 72
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"spectators": [
|
|
||||||
{
|
|
||||||
"email": "hr@royalenfield.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "finance@royalenfield.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Example 2: Express Priority with Final Approver Flag
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Urgent: Server Infrastructure Upgrade",
|
|
||||||
"description": "Critical server infrastructure upgrade required immediately",
|
|
||||||
"priority": "EXPRESS",
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "it-manager@royalenfield.com",
|
|
||||||
"tatHours": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "cto@royalenfield.com",
|
|
||||||
"tatHours": 16,
|
|
||||||
"isFinalApprover": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Example 3: With Custom Level Names
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Vendor Contract Approval",
|
|
||||||
"description": "New vendor contract for manufacturing components",
|
|
||||||
"priority": "STANDARD",
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "procurement@royalenfield.com",
|
|
||||||
"tatHours": 24,
|
|
||||||
"levelName": "Procurement Review"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "legal@royalenfield.com",
|
|
||||||
"tatHours": 48,
|
|
||||||
"levelName": "Legal Compliance"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "vp@royalenfield.com",
|
|
||||||
"tatHours": 72,
|
|
||||||
"levelName": "Executive Approval",
|
|
||||||
"isFinalApprover": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Example 4: Multipart with Files
|
|
||||||
|
|
||||||
**POST** `{{baseUrl}}/workflows/multipart`
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Authorization: Bearer <your_token>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Body (form-data):**
|
|
||||||
| Key | Type | Value |
|
|
||||||
|-----|------|-------|
|
|
||||||
| `payload` | Text | `{"templateType":"CUSTOM","title":"Budget Request 2025","description":"Annual budget request","priority":"STANDARD","approvalLevels":[{"email":"finance-manager@royalenfield.com","tatHours":48},{"email":"cfo@royalenfield.com","tatHours":72}]}` |
|
|
||||||
| `files` | File | Select PDF/Excel file(s) |
|
|
||||||
| `category` | Text | `SUPPORTING` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Field Reference
|
|
||||||
|
|
||||||
### Required Fields
|
|
||||||
|
|
||||||
| Field | Type | Description | Example |
|
|
||||||
|-------|------|-------------|---------|
|
|
||||||
| `templateType` | string | Workflow type | `"CUSTOM"` or `"TEMPLATE"` |
|
|
||||||
| `title` | string | Request title (max 500 chars) | `"Purchase Order Approval"` |
|
|
||||||
| `description` | string | Detailed description | `"Approval needed for..."` |
|
|
||||||
| `priority` | string | Request priority | `"STANDARD"` or `"EXPRESS"` |
|
|
||||||
| `approvalLevels` | array | List of approvers (min 1, max 10) | See below |
|
|
||||||
|
|
||||||
### Approval Level Fields
|
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
|
||||||
|-------|------|----------|-------------|
|
|
||||||
| `email` | string | ✅ Yes | Approver's email address |
|
|
||||||
| `tatHours` | number | ✅ Yes | Turn-around time in hours (positive number) |
|
|
||||||
| `isFinalApprover` | boolean | ❌ No | Explicitly mark as final approver (auto-detected if last level) |
|
|
||||||
| `levelName` | string | ❌ No | Custom level name (auto-generated if not provided) |
|
|
||||||
|
|
||||||
**Auto-generated `levelName` logic:**
|
|
||||||
- If approver has **designation**: `"{Designation} Approval"` (e.g., "Manager Approval")
|
|
||||||
- If approver has **department**: `"{Department} Approval"` (e.g., "Finance Approval")
|
|
||||||
- Otherwise: `"Level {N} Approval"` (e.g., "Level 1 Approval")
|
|
||||||
|
|
||||||
### Spectator Fields
|
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
|
||||||
|-------|------|----------|-------------|
|
|
||||||
| `email` | string | ✅ Yes | Spectator's email address |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Validation & Error Handling
|
|
||||||
|
|
||||||
The backend automatically validates and provides clear error messages:
|
|
||||||
|
|
||||||
### ✅ Successful Response
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Workflow created successfully",
|
|
||||||
"data": {
|
|
||||||
"requestId": "uuid",
|
|
||||||
"requestNumber": "REQ-2025-12-0001",
|
|
||||||
"title": "...",
|
|
||||||
"status": "PENDING",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Error: Invalid Email
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to create workflow",
|
|
||||||
"details": "Approver email 'invalid@example.com' not found in organization directory. Please verify the email address."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Error: Duplicate Email
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to create workflow",
|
|
||||||
"details": "Duplicate approver email found: manager@example.com. Each approver must have a unique email."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Error: Invalid Initiator
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to create workflow",
|
|
||||||
"details": "Invalid initiator: User with ID '...' not found. Please ensure you are logged in with a valid account."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Error: Validation Failed
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Validation failed",
|
|
||||||
"details": "approvalLevels.0.email: Valid email is required; approvalLevels.0.tatHours: TAT hours must be positive"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What Happens Behind the Scenes
|
|
||||||
|
|
||||||
When you create a workflow, the backend:
|
|
||||||
|
|
||||||
1. **Validates Initiator**: Ensures the logged-in user exists
|
|
||||||
2. **Enriches Approval Levels**:
|
|
||||||
- Searches for each approver in the local database
|
|
||||||
- If not found, fetches from Okta/Azure AD
|
|
||||||
- Creates user record if they exist in AD but not in DB
|
|
||||||
- Extracts: `userId`, `displayName`, `designation`, `department`
|
|
||||||
- Auto-generates `levelName` if not provided
|
|
||||||
- Auto-detects `isFinalApprover` (last level = true)
|
|
||||||
3. **Enriches Spectators**:
|
|
||||||
- Same lookup/creation process as approvers
|
|
||||||
- Sets default permissions (view + comment, no download)
|
|
||||||
4. **Creates Workflow**:
|
|
||||||
- Saves workflow request
|
|
||||||
- Creates approval levels
|
|
||||||
- Creates participants
|
|
||||||
- Sends notifications
|
|
||||||
- Logs activity
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Migration from Old Format
|
|
||||||
|
|
||||||
### ❌ Old Format (No Longer Required)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"levelNumber": 1,
|
|
||||||
"levelName": "Manager Approval",
|
|
||||||
"approverId": "uuid-123",
|
|
||||||
"approverEmail": "manager@example.com",
|
|
||||||
"approverName": "John Doe",
|
|
||||||
"tatHours": 24,
|
|
||||||
"isFinalApprover": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ New Simplified Format
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "manager@example.com",
|
|
||||||
"tatHours": 24
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**The backend handles everything else automatically!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tips & Best Practices
|
|
||||||
|
|
||||||
1. **Use Valid Email Addresses**: Ensure all approver/spectator emails exist in your Okta/Azure AD
|
|
||||||
2. **TAT Hours**: Set realistic turn-around times based on priority:
|
|
||||||
- STANDARD: 24-72 hours per level
|
|
||||||
- EXPRESS: 8-24 hours per level
|
|
||||||
3. **Final Approver**: Last level is automatically marked as final approver (you can override with `isFinalApprover: true` on any level)
|
|
||||||
4. **Level Names**: Let the system auto-generate based on designation/department, or provide custom names
|
|
||||||
5. **Spectators**: Add users who need visibility but not approval authority
|
|
||||||
6. **Documents**: Use `/multipart` endpoint for file uploads
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing in Postman
|
|
||||||
|
|
||||||
1. **Set Environment Variables**:
|
|
||||||
- `baseUrl`: `http://localhost:5000/api/v1`
|
|
||||||
- `token`: Your auth token from login
|
|
||||||
|
|
||||||
2. **Login First**:
|
|
||||||
- Call `POST /auth/login`
|
|
||||||
- Copy the `token` from response
|
|
||||||
- Set as environment variable
|
|
||||||
|
|
||||||
3. **Create Workflow**:
|
|
||||||
- Use simplified format
|
|
||||||
- Only provide email + tatHours
|
|
||||||
- Backend handles the rest
|
|
||||||
|
|
||||||
4. **Check Response**:
|
|
||||||
- Verify `requestNumber` is generated
|
|
||||||
- Check `approvalLevels` are enriched with user data
|
|
||||||
- Confirm `participants` includes spectators
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Add Approver/Spectator After Request Creation
|
|
||||||
|
|
||||||
These endpoints allow adding approvers or spectators to an existing request. They follow the same simplified pattern - just provide email, and the backend handles user lookup/creation.
|
|
||||||
|
|
||||||
### Add Approver at Specific Level
|
|
||||||
|
|
||||||
**POST** `{{baseUrl}}/workflows/:requestId/approvers/at-level`
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Authorization: Bearer <your_token>
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "newapprover@royalenfield.com",
|
|
||||||
"tatHours": 24,
|
|
||||||
"level": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Happens:**
|
|
||||||
- ✅ Finds user by email in DB, or syncs from Okta/AD if not found
|
|
||||||
- ✅ Auto-generates levelName based on designation/department
|
|
||||||
- ✅ Shifts existing levels if needed
|
|
||||||
- ✅ Updates final approver flag
|
|
||||||
- ✅ Sends notification to new approver
|
|
||||||
- ✅ Logs activity
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Approver added successfully",
|
|
||||||
"data": {
|
|
||||||
"levelId": "uuid",
|
|
||||||
"levelNumber": 2,
|
|
||||||
"levelName": "Manager Approval",
|
|
||||||
"approverId": "uuid",
|
|
||||||
"approverEmail": "newapprover@royalenfield.com",
|
|
||||||
"approverName": "John Doe",
|
|
||||||
"tatHours": 24,
|
|
||||||
"status": "PENDING"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Add Simple Approver (General)
|
|
||||||
|
|
||||||
**POST** `{{baseUrl}}/workflows/:requestId/participants/approver`
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Authorization: Bearer <your_token>
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "approver@royalenfield.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Note: This adds them as a general approver participant, not at a specific level.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Add Spectator
|
|
||||||
|
|
||||||
**POST** `{{baseUrl}}/workflows/:requestId/participants/spectator`
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
```
|
|
||||||
Authorization: Bearer <your_token>
|
|
||||||
Content-Type: application/json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "spectator@royalenfield.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Happens:**
|
|
||||||
- ✅ Finds user by email in DB, or syncs from Okta/AD if not found
|
|
||||||
- ✅ Sets spectator permissions (view + comment, no download)
|
|
||||||
- ✅ Sends notification to new spectator
|
|
||||||
- ✅ Logs activity
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"participantId": "uuid",
|
|
||||||
"userId": "uuid",
|
|
||||||
"userEmail": "spectator@royalenfield.com",
|
|
||||||
"userName": "Jane Doe",
|
|
||||||
"participantType": "SPECTATOR",
|
|
||||||
"canComment": true,
|
|
||||||
"canViewDocuments": true,
|
|
||||||
"canDownloadDocuments": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Error Handling for Add Operations
|
|
||||||
|
|
||||||
**❌ User Not Found in AD:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to add approver",
|
|
||||||
"details": "Approver email 'invalid@example.com' not found in organization directory. Please verify the email address."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**❌ User Already a Participant:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to add spectator",
|
|
||||||
"details": "User is already a participant in this request"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**❌ Invalid Level:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": false,
|
|
||||||
"error": "Failed to add approver at level",
|
|
||||||
"details": "Cannot add approver at level 1 - level has already been completed"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Flow Example
|
|
||||||
|
|
||||||
### 1. Login
|
|
||||||
```bash
|
|
||||||
POST /api/v1/auth/login
|
|
||||||
Body: { "email": "user@example.com", "password": "pass" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Create Workflow (Simplified)
|
|
||||||
```bash
|
|
||||||
POST /api/v1/workflows
|
|
||||||
Body: {
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Purchase Order",
|
|
||||||
"description": "Office equipment",
|
|
||||||
"priority": "STANDARD",
|
|
||||||
"approvalLevels": [
|
|
||||||
{ "email": "manager@example.com", "tatHours": 24 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Add Additional Approver (After Creation)
|
|
||||||
```bash
|
|
||||||
POST /api/v1/workflows/:requestId/approvers/at-level
|
|
||||||
Body: {
|
|
||||||
"email": "director@example.com",
|
|
||||||
"tatHours": 48,
|
|
||||||
"level": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Add Spectator
|
|
||||||
```bash
|
|
||||||
POST /api/v1/workflows/:requestId/participants/spectator
|
|
||||||
Body: { "email": "hr@example.com" }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
If you encounter any issues:
|
|
||||||
1. Check the error message - it will tell you exactly what's wrong
|
|
||||||
2. Verify emails exist in your organization directory
|
|
||||||
3. Ensure you're logged in with a valid token
|
|
||||||
4. Check backend logs for detailed error information
|
|
||||||
|
|
||||||
@ -1,535 +0,0 @@
|
|||||||
# Data Collection Analysis - What We Have vs What We're Collecting
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This document compares the database structure with what we're currently collecting and recommends what we should start collecting for the Detailed Reports.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. ACTIVITIES TABLE
|
|
||||||
|
|
||||||
### ✅ **Database Fields Available:**
|
|
||||||
```sql
|
|
||||||
- activity_id (PK)
|
|
||||||
- request_id (FK) ✅ COLLECTING
|
|
||||||
- user_id (FK) ✅ COLLECTING
|
|
||||||
- user_name ✅ COLLECTING
|
|
||||||
- activity_type ✅ COLLECTING
|
|
||||||
- activity_description ✅ COLLECTING
|
|
||||||
- activity_category ❌ NOT COLLECTING (set to NULL)
|
|
||||||
- severity ❌ NOT COLLECTING (set to NULL)
|
|
||||||
- metadata ✅ COLLECTING (partially)
|
|
||||||
- is_system_event ✅ COLLECTING
|
|
||||||
- ip_address ❌ NOT COLLECTING (set to NULL)
|
|
||||||
- user_agent ❌ NOT COLLECTING (set to NULL)
|
|
||||||
- created_at ✅ COLLECTING
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔴 **Currently NOT Collecting (But Should):**
|
|
||||||
|
|
||||||
1. **IP Address** (`ip_address`)
|
|
||||||
- **Status:** Field exists, but always set to `null`
|
|
||||||
- **Impact:** Cannot show IP in User Activity Log Report
|
|
||||||
- **Fix:** Extract from `req.ip` or `req.headers['x-forwarded-for']` in controllers
|
|
||||||
- **Priority:** HIGH (needed for security/audit)
|
|
||||||
|
|
||||||
2. **User Agent** (`user_agent`)
|
|
||||||
- **Status:** Field exists, but always set to `null`
|
|
||||||
- **Impact:** Cannot show device/browser info in reports
|
|
||||||
- **Fix:** Extract from `req.headers['user-agent']` in controllers
|
|
||||||
- **Priority:** MEDIUM (nice to have for analytics)
|
|
||||||
|
|
||||||
3. **Activity Category** (`activity_category`)
|
|
||||||
- **Status:** Field exists, but always set to `null`
|
|
||||||
- **Impact:** Cannot categorize activities (e.g., "AUTHENTICATION", "WORKFLOW", "DOCUMENT")
|
|
||||||
- **Fix:** Map `activity_type` to category:
|
|
||||||
- `created`, `approval`, `rejection`, `status_change` → "WORKFLOW"
|
|
||||||
- `comment` → "COLLABORATION"
|
|
||||||
- `document_added` → "DOCUMENT"
|
|
||||||
- `sla_warning` → "SYSTEM"
|
|
||||||
- **Priority:** MEDIUM (helps with filtering/reporting)
|
|
||||||
|
|
||||||
4. **Severity** (`severity`)
|
|
||||||
- **Status:** Field exists, but always set to `null`
|
|
||||||
- **Impact:** Cannot prioritize critical activities
|
|
||||||
- **Fix:** Map based on activity type:
|
|
||||||
- `rejection`, `sla_warning` → "WARNING"
|
|
||||||
- `approval`, `closed` → "INFO"
|
|
||||||
- `status_change` → "INFO"
|
|
||||||
- **Priority:** LOW (optional enhancement)
|
|
||||||
|
|
||||||
### 📝 **Recommendation:**
|
|
||||||
**Update `activity.service.ts` to accept and store:**
|
|
||||||
```typescript
|
|
||||||
async log(entry: ActivityEntry & {
|
|
||||||
ipAddress?: string;
|
|
||||||
userAgent?: string;
|
|
||||||
category?: string;
|
|
||||||
severity?: string;
|
|
||||||
}) {
|
|
||||||
// ... existing code ...
|
|
||||||
const activityData = {
|
|
||||||
// ... existing fields ...
|
|
||||||
ipAddress: entry.ipAddress || null,
|
|
||||||
userAgent: entry.userAgent || null,
|
|
||||||
activityCategory: entry.category || this.inferCategory(entry.type),
|
|
||||||
severity: entry.severity || this.inferSeverity(entry.type),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Update all controller calls to pass IP and User Agent:**
|
|
||||||
```typescript
|
|
||||||
activityService.log({
|
|
||||||
// ... existing fields ...
|
|
||||||
ipAddress: req.ip || req.headers['x-forwarded-for'] || null,
|
|
||||||
userAgent: req.headers['user-agent'] || null,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. APPROVAL_LEVELS TABLE
|
|
||||||
|
|
||||||
### ✅ **Database Fields Available:**
|
|
||||||
```sql
|
|
||||||
- level_id (PK)
|
|
||||||
- request_id (FK) ✅ COLLECTING
|
|
||||||
- level_number ✅ COLLECTING
|
|
||||||
- level_name ❌ OPTIONAL (may not be set)
|
|
||||||
- approver_id (FK) ✅ COLLECTING
|
|
||||||
- approver_email ✅ COLLECTING
|
|
||||||
- approver_name ✅ COLLECTING
|
|
||||||
- tat_hours ✅ COLLECTING
|
|
||||||
- tat_days ✅ COLLECTING (auto-calculated)
|
|
||||||
- status ✅ COLLECTING
|
|
||||||
- level_start_time ✅ COLLECTING
|
|
||||||
- level_end_time ✅ COLLECTING
|
|
||||||
- action_date ✅ COLLECTING
|
|
||||||
- comments ✅ COLLECTING
|
|
||||||
- rejection_reason ✅ COLLECTING
|
|
||||||
- is_final_approver ✅ COLLECTING
|
|
||||||
- elapsed_hours ✅ COLLECTING
|
|
||||||
- remaining_hours ✅ COLLECTING
|
|
||||||
- tat_percentage_used ✅ COLLECTING
|
|
||||||
- tat50_alert_sent ✅ COLLECTING
|
|
||||||
- tat75_alert_sent ✅ COLLECTING
|
|
||||||
- tat_breached ✅ COLLECTING
|
|
||||||
- tat_start_time ✅ COLLECTING
|
|
||||||
- created_at ✅ COLLECTING
|
|
||||||
- updated_at ✅ COLLECTING
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔴 **Currently NOT Collecting (But Should):**
|
|
||||||
|
|
||||||
1. **Level Name** (`level_name`)
|
|
||||||
- **Status:** Field exists, but may be NULL
|
|
||||||
- **Impact:** Cannot show stage name in reports (only level number)
|
|
||||||
- **Fix:** When creating approval levels, prompt for or auto-generate level names:
|
|
||||||
- "Department Head Review"
|
|
||||||
- "Finance Approval"
|
|
||||||
- "Final Approval"
|
|
||||||
- **Priority:** MEDIUM (improves report readability)
|
|
||||||
|
|
||||||
### 📝 **Recommendation:**
|
|
||||||
**Ensure level_name is set when creating approval levels:**
|
|
||||||
```typescript
|
|
||||||
await ApprovalLevel.create({
|
|
||||||
// ... existing fields ...
|
|
||||||
levelName: levelData.levelName || `Level ${levelNumber}`,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. USER_SESSIONS TABLE
|
|
||||||
|
|
||||||
### ✅ **Database Fields Available:**
|
|
||||||
```sql
|
|
||||||
- session_id (PK)
|
|
||||||
- user_id (FK)
|
|
||||||
- session_token ✅ COLLECTING
|
|
||||||
- refresh_token ✅ COLLECTING
|
|
||||||
- ip_address ❓ CHECK IF COLLECTING
|
|
||||||
- user_agent ❓ CHECK IF COLLECTING
|
|
||||||
- device_type ❓ CHECK IF COLLECTING
|
|
||||||
- browser ❓ CHECK IF COLLECTING
|
|
||||||
- os ❓ CHECK IF COLLECTING
|
|
||||||
- login_at ✅ COLLECTING
|
|
||||||
- last_activity_at ✅ COLLECTING
|
|
||||||
- logout_at ❓ CHECK IF COLLECTING
|
|
||||||
- expires_at ✅ COLLECTING
|
|
||||||
- is_active ✅ COLLECTING
|
|
||||||
- logout_reason ❓ CHECK IF COLLECTING
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔴 **Missing for Login Activity Tracking:**
|
|
||||||
|
|
||||||
1. **Login Activities in Activities Table**
|
|
||||||
- **Status:** Login events are NOT logged in `activities` table
|
|
||||||
- **Impact:** Cannot show login activities in User Activity Log Report
|
|
||||||
- **Fix:** Add login activity logging in auth middleware/controller:
|
|
||||||
```typescript
|
|
||||||
// After successful login
|
|
||||||
await activityService.log({
|
|
||||||
requestId: 'SYSTEM_LOGIN', // Special request ID for system events
|
|
||||||
type: 'login',
|
|
||||||
user: { userId, name: user.displayName },
|
|
||||||
ipAddress: req.ip,
|
|
||||||
userAgent: req.headers['user-agent'],
|
|
||||||
category: 'AUTHENTICATION',
|
|
||||||
severity: 'INFO',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
action: 'User Login',
|
|
||||||
details: `User logged in from ${req.ip}`
|
|
||||||
});
|
|
||||||
```
|
|
||||||
- **Priority:** HIGH (needed for security audit)
|
|
||||||
|
|
||||||
2. **Device/Browser Parsing**
|
|
||||||
- **Status:** Fields exist but may not be populated
|
|
||||||
- **Impact:** Cannot show device type in reports
|
|
||||||
- **Fix:** Parse user agent to extract:
|
|
||||||
- `device_type`: "WEB", "MOBILE"
|
|
||||||
- `browser`: "Chrome", "Firefox", "Safari"
|
|
||||||
- `os`: "Windows", "macOS", "iOS", "Android"
|
|
||||||
- **Priority:** MEDIUM (nice to have)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. WORKFLOW_REQUESTS TABLE
|
|
||||||
|
|
||||||
### ✅ **All Fields Are Being Collected:**
|
|
||||||
- All fields in `workflow_requests` are properly collected
|
|
||||||
- No missing data here
|
|
||||||
|
|
||||||
### 📝 **Note:**
|
|
||||||
- `submission_date` vs `created_at`: Use `submission_date` for "days open" calculation
|
|
||||||
- `closure_date`: Available for completed requests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. TAT_TRACKING TABLE
|
|
||||||
|
|
||||||
### ✅ **Database Fields Available:**
|
|
||||||
```sql
|
|
||||||
- tracking_id (PK)
|
|
||||||
- request_id (FK)
|
|
||||||
- level_id (FK)
|
|
||||||
- tracking_type ✅ COLLECTING
|
|
||||||
- tat_status ✅ COLLECTING
|
|
||||||
- total_tat_hours ✅ COLLECTING
|
|
||||||
- elapsed_hours ✅ COLLECTING
|
|
||||||
- remaining_hours ✅ COLLECTING
|
|
||||||
- percentage_used ✅ COLLECTING
|
|
||||||
- threshold_50_breached ✅ COLLECTING
|
|
||||||
- threshold_50_alerted_at ✅ COLLECTING
|
|
||||||
- threshold_80_breached ✅ COLLECTING
|
|
||||||
- threshold_80_alerted_at ✅ COLLECTING
|
|
||||||
- threshold_100_breached ✅ COLLECTING
|
|
||||||
- threshold_100_alerted_at ✅ COLLECTING
|
|
||||||
- alert_count ✅ COLLECTING
|
|
||||||
- last_calculated_at ✅ COLLECTING
|
|
||||||
```
|
|
||||||
|
|
||||||
### ✅ **All Fields Are Being Collected:**
|
|
||||||
- TAT tracking appears to be fully implemented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. AUDIT_LOGS TABLE
|
|
||||||
|
|
||||||
### ✅ **Database Fields Available:**
|
|
||||||
```sql
|
|
||||||
- audit_id (PK)
|
|
||||||
- user_id (FK)
|
|
||||||
- entity_type
|
|
||||||
- entity_id
|
|
||||||
- action
|
|
||||||
- action_category
|
|
||||||
- old_values (JSONB)
|
|
||||||
- new_values (JSONB)
|
|
||||||
- changes_summary
|
|
||||||
- ip_address
|
|
||||||
- user_agent
|
|
||||||
- session_id
|
|
||||||
- request_method
|
|
||||||
- request_url
|
|
||||||
- response_status
|
|
||||||
- execution_time_ms
|
|
||||||
- created_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔴 **Status:**
|
|
||||||
- **Audit logging may not be fully implemented**
|
|
||||||
- **Impact:** Cannot track all system changes for audit purposes
|
|
||||||
- **Priority:** MEDIUM (for compliance/security)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SUMMARY: What to Start Collecting
|
|
||||||
|
|
||||||
### 🔴 **HIGH PRIORITY (Must Have for Reports):**
|
|
||||||
|
|
||||||
1. **IP Address in Activities** ✅ Field exists, just need to populate
|
|
||||||
- Extract from `req.ip` or `req.headers['x-forwarded-for']`
|
|
||||||
- Update `activity.service.ts` to accept IP
|
|
||||||
- Update all controller calls
|
|
||||||
|
|
||||||
2. **User Agent in Activities** ✅ Field exists, just need to populate
|
|
||||||
- Extract from `req.headers['user-agent']`
|
|
||||||
- Update `activity.service.ts` to accept user agent
|
|
||||||
- Update all controller calls
|
|
||||||
|
|
||||||
3. **Login Activities** ❌ Not currently logged
|
|
||||||
- Add login activity logging in auth controller
|
|
||||||
- Use special `requestId: 'SYSTEM_LOGIN'` for system events
|
|
||||||
- Include IP and user agent
|
|
||||||
|
|
||||||
### 🟡 **MEDIUM PRIORITY (Nice to Have):**
|
|
||||||
|
|
||||||
4. **Activity Category** ✅ Field exists, just need to populate
|
|
||||||
- Auto-infer from `activity_type`
|
|
||||||
- Helps with filtering and reporting
|
|
||||||
|
|
||||||
5. **Level Names** ✅ Field exists, ensure it's set
|
|
||||||
- Improve readability in reports
|
|
||||||
- Auto-generate if not provided
|
|
||||||
|
|
||||||
6. **Severity** ✅ Field exists, just need to populate
|
|
||||||
- Auto-infer from `activity_type`
|
|
||||||
- Helps prioritize critical activities
|
|
||||||
|
|
||||||
### 🟢 **LOW PRIORITY (Future Enhancement):**
|
|
||||||
|
|
||||||
7. **Device/Browser Parsing**
|
|
||||||
- Parse user agent to extract device type, browser, OS
|
|
||||||
- Store in `user_sessions` table
|
|
||||||
|
|
||||||
8. **Audit Logging**
|
|
||||||
- Implement comprehensive audit logging
|
|
||||||
- Track all system changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. BUSINESS DAYS CALCULATION FOR WORKFLOW AGING
|
|
||||||
|
|
||||||
### ✅ **Available:**
|
|
||||||
- `calculateElapsedWorkingHours()` - Calculates working hours (excludes weekends/holidays)
|
|
||||||
- Working hours configuration (9 AM - 6 PM, Mon-Fri)
|
|
||||||
- Holiday support (from database)
|
|
||||||
- Priority-based calculation (express vs standard)
|
|
||||||
|
|
||||||
### ❌ **Missing:**
|
|
||||||
1. **Business Days Count Function**
|
|
||||||
- Need a function to calculate business days (not hours)
|
|
||||||
- For Workflow Aging Report: "Days Open" should be business days
|
|
||||||
- Currently only have working hours calculation
|
|
||||||
|
|
||||||
2. **TAT Processor Using Wrong Calculation**
|
|
||||||
- `tatProcessor.ts` uses simple calendar hours:
|
|
||||||
```typescript
|
|
||||||
const elapsedMs = now.getTime() - new Date(levelStartTime).getTime();
|
|
||||||
const elapsedHours = elapsedMs / (1000 * 60 * 60);
|
|
||||||
```
|
|
||||||
- Should use `calculateElapsedWorkingHours()` instead
|
|
||||||
- This causes incorrect TAT breach calculations
|
|
||||||
|
|
||||||
### 🔧 **What Needs to be Built:**
|
|
||||||
|
|
||||||
1. **Add Business Days Calculation Function:**
|
|
||||||
```typescript
|
|
||||||
// In tatTimeUtils.ts
|
|
||||||
export async function calculateBusinessDays(
|
|
||||||
startDate: Date | string,
|
|
||||||
endDate: Date | string = new Date(),
|
|
||||||
priority: string = 'standard'
|
|
||||||
): Promise<number> {
|
|
||||||
await loadWorkingHoursCache();
|
|
||||||
await loadHolidaysCache();
|
|
||||||
|
|
||||||
let start = dayjs(startDate);
|
|
||||||
const end = dayjs(endDate);
|
|
||||||
const config = workingHoursCache || { /* defaults */ };
|
|
||||||
|
|
||||||
let businessDays = 0;
|
|
||||||
let current = start.startOf('day');
|
|
||||||
|
|
||||||
while (current.isBefore(end) || current.isSame(end, 'day')) {
|
|
||||||
const dayOfWeek = current.day();
|
|
||||||
const dateStr = current.format('YYYY-MM-DD');
|
|
||||||
|
|
||||||
const isWorkingDay = priority === 'express'
|
|
||||||
? true
|
|
||||||
: (dayOfWeek >= config.startDay && dayOfWeek <= config.endDay);
|
|
||||||
const isNotHoliday = !holidaysCache.has(dateStr);
|
|
||||||
|
|
||||||
if (isWorkingDay && isNotHoliday) {
|
|
||||||
businessDays++;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.add(1, 'day');
|
|
||||||
}
|
|
||||||
|
|
||||||
return businessDays;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Fix TAT Processor:**
|
|
||||||
- Replace calendar hours calculation with `calculateElapsedWorkingHours()`
|
|
||||||
- This will fix TAT breach alerts to use proper working hours
|
|
||||||
|
|
||||||
3. **Update Workflow Aging Report:**
|
|
||||||
- Use `calculateBusinessDays()` instead of calendar days
|
|
||||||
- Filter by business days threshold
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## IMPLEMENTATION CHECKLIST
|
|
||||||
|
|
||||||
### Phase 1: Quick Wins (Fields Exist, Just Need to Populate)
|
|
||||||
- [ ] Update `activity.service.ts` to accept `ipAddress` and `userAgent`
|
|
||||||
- [ ] Update all controller calls to pass IP and user agent
|
|
||||||
- [ ] Add activity category inference
|
|
||||||
- [ ] Add severity inference
|
|
||||||
|
|
||||||
### Phase 2: Fix TAT Calculations (CRITICAL)
|
|
||||||
- [x] Fix `tatProcessor.ts` to use `calculateElapsedWorkingHours()` instead of calendar hours ✅
|
|
||||||
- [x] Add `calculateBusinessDays()` function to `tatTimeUtils.ts` ✅
|
|
||||||
- [ ] Test TAT breach calculations with working hours
|
|
||||||
|
|
||||||
### Phase 3: New Functionality
|
|
||||||
- [x] Add login activity logging ✅ (Implemented in auth.controller.ts for SSO and token exchange)
|
|
||||||
- [x] Ensure level names are set when creating approval levels ✅ (levelName set in workflow.service.ts)
|
|
||||||
- [x] Add device/browser parsing for user sessions ✅ (userAgentParser.ts utility created - can be used for parsing user agent strings)
|
|
||||||
|
|
||||||
### Phase 4: Enhanced Reporting
|
|
||||||
- [x] Build report endpoints using collected data ✅ (getLifecycleReport, getActivityLogReport, getWorkflowAgingReport)
|
|
||||||
- [x] Add filtering by category, severity ✅ (Filtering by category and severity added to getActivityLogReport, frontend UI added)
|
|
||||||
- [x] Add IP/user agent to activity log reports ✅ (IP and user agent captured and displayed)
|
|
||||||
- [x] Use business days in Workflow Aging Report ✅ (calculateBusinessDays implemented and used)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CODE CHANGES NEEDED
|
|
||||||
|
|
||||||
### 1. Update Activity Service (`activity.service.ts`)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export type ActivityEntry = {
|
|
||||||
requestId: string;
|
|
||||||
type: 'created' | 'assignment' | 'approval' | 'rejection' | 'status_change' | 'comment' | 'reminder' | 'document_added' | 'sla_warning' | 'ai_conclusion_generated' | 'closed' | 'login';
|
|
||||||
user?: { userId: string; name?: string; email?: string };
|
|
||||||
timestamp: string;
|
|
||||||
action: string;
|
|
||||||
details: string;
|
|
||||||
metadata?: any;
|
|
||||||
ipAddress?: string; // NEW
|
|
||||||
userAgent?: string; // NEW
|
|
||||||
category?: string; // NEW
|
|
||||||
severity?: string; // NEW
|
|
||||||
};
|
|
||||||
|
|
||||||
class ActivityService {
|
|
||||||
private inferCategory(type: string): string {
|
|
||||||
const categoryMap: Record<string, string> = {
|
|
||||||
'created': 'WORKFLOW',
|
|
||||||
'approval': 'WORKFLOW',
|
|
||||||
'rejection': 'WORKFLOW',
|
|
||||||
'status_change': 'WORKFLOW',
|
|
||||||
'assignment': 'WORKFLOW',
|
|
||||||
'comment': 'COLLABORATION',
|
|
||||||
'document_added': 'DOCUMENT',
|
|
||||||
'sla_warning': 'SYSTEM',
|
|
||||||
'reminder': 'SYSTEM',
|
|
||||||
'ai_conclusion_generated': 'SYSTEM',
|
|
||||||
'closed': 'WORKFLOW',
|
|
||||||
'login': 'AUTHENTICATION'
|
|
||||||
};
|
|
||||||
return categoryMap[type] || 'OTHER';
|
|
||||||
}
|
|
||||||
|
|
||||||
private inferSeverity(type: string): string {
|
|
||||||
const severityMap: Record<string, string> = {
|
|
||||||
'rejection': 'WARNING',
|
|
||||||
'sla_warning': 'WARNING',
|
|
||||||
'approval': 'INFO',
|
|
||||||
'closed': 'INFO',
|
|
||||||
'status_change': 'INFO',
|
|
||||||
'login': 'INFO',
|
|
||||||
'created': 'INFO',
|
|
||||||
'comment': 'INFO',
|
|
||||||
'document_added': 'INFO'
|
|
||||||
};
|
|
||||||
return severityMap[type] || 'INFO';
|
|
||||||
}
|
|
||||||
|
|
||||||
async log(entry: ActivityEntry) {
|
|
||||||
// ... existing code ...
|
|
||||||
const activityData = {
|
|
||||||
requestId: entry.requestId,
|
|
||||||
userId: entry.user?.userId || null,
|
|
||||||
userName: entry.user?.name || entry.user?.email || null,
|
|
||||||
activityType: entry.type,
|
|
||||||
activityDescription: entry.details,
|
|
||||||
activityCategory: entry.category || this.inferCategory(entry.type),
|
|
||||||
severity: entry.severity || this.inferSeverity(entry.type),
|
|
||||||
metadata: entry.metadata || null,
|
|
||||||
isSystemEvent: !entry.user,
|
|
||||||
ipAddress: entry.ipAddress || null, // NEW
|
|
||||||
userAgent: entry.userAgent || null, // NEW
|
|
||||||
};
|
|
||||||
// ... rest of code ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Update Controller Calls (Example)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// In workflow.controller.ts, approval.controller.ts, etc.
|
|
||||||
activityService.log({
|
|
||||||
requestId: workflow.requestId,
|
|
||||||
type: 'created',
|
|
||||||
user: { userId, name: user.displayName },
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
action: 'Request Created',
|
|
||||||
details: `Request ${workflow.requestNumber} created`,
|
|
||||||
ipAddress: req.ip || req.headers['x-forwarded-for'] || null, // NEW
|
|
||||||
userAgent: req.headers['user-agent'] || null, // NEW
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Add Login Activity Logging
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// In auth.controller.ts after successful login
|
|
||||||
await activityService.log({
|
|
||||||
requestId: 'SYSTEM_LOGIN', // Special ID for system events
|
|
||||||
type: 'login',
|
|
||||||
user: { userId: user.userId, name: user.displayName },
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
action: 'User Login',
|
|
||||||
details: `User logged in successfully`,
|
|
||||||
ipAddress: req.ip || req.headers['x-forwarded-for'] || null,
|
|
||||||
userAgent: req.headers['user-agent'] || null,
|
|
||||||
category: 'AUTHENTICATION',
|
|
||||||
severity: 'INFO'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CONCLUSION
|
|
||||||
|
|
||||||
**Good News:** Most fields already exist in the database! We just need to:
|
|
||||||
1. Populate existing fields (IP, user agent, category, severity)
|
|
||||||
2. Add login activity logging
|
|
||||||
3. Ensure level names are set
|
|
||||||
|
|
||||||
**Estimated Effort:**
|
|
||||||
- Phase 1 (Quick Wins): 2-4 hours
|
|
||||||
- Phase 2 (New Functionality): 4-6 hours
|
|
||||||
- Phase 3 (Enhanced Reporting): 8-12 hours
|
|
||||||
|
|
||||||
**Total: ~14-22 hours of development work**
|
|
||||||
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
========================================
|
|
||||||
REDIS SETUP FOR TAT NOTIFICATIONS
|
|
||||||
========================================
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
OPTION 1: UPSTASH (★ RECOMMENDED ★)
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
✅ NO INSTALLATION NEEDED
|
|
||||||
✅ 100% FREE FOR DEVELOPMENT
|
|
||||||
✅ WORKS ON WINDOWS, MAC, LINUX
|
|
||||||
✅ PRODUCTION READY
|
|
||||||
|
|
||||||
SETUP (2 MINUTES):
|
|
||||||
|
|
||||||
1. Go to: https://console.upstash.com/
|
|
||||||
|
|
||||||
2. Sign up (GitHub/Google/Email)
|
|
||||||
|
|
||||||
3. Click "Create Database"
|
|
||||||
- Name: redis-tat-dev
|
|
||||||
- Type: Regional
|
|
||||||
- Region: Choose closest to you
|
|
||||||
- Click "Create"
|
|
||||||
|
|
||||||
4. Copy the Redis URL (looks like):
|
|
||||||
rediss://default:AbC123...@us1-mighty-12345.upstash.io:6379
|
|
||||||
|
|
||||||
5. Add to Re_Backend/.env:
|
|
||||||
REDIS_URL=rediss://default:AbC123...@us1-mighty-12345.upstash.io:6379
|
|
||||||
TAT_TEST_MODE=true
|
|
||||||
|
|
||||||
6. Restart backend:
|
|
||||||
cd Re_Backend
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
7. ✅ Done! Look for: "[TAT Queue] Connected to Redis"
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
OPTION 2: DOCKER (IF YOU PREFER LOCAL)
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
If you have Docker Desktop:
|
|
||||||
|
|
||||||
1. Run Redis container:
|
|
||||||
docker run -d --name redis-tat -p 6379:6379 redis:latest
|
|
||||||
|
|
||||||
2. Add to Re_Backend/.env:
|
|
||||||
REDIS_URL=redis://localhost:6379
|
|
||||||
TAT_TEST_MODE=true
|
|
||||||
|
|
||||||
3. Restart backend
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
OPTION 3: PRODUCTION (LINUX SERVER)
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
For Ubuntu/Debian servers:
|
|
||||||
|
|
||||||
1. Install Redis:
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install redis-server -y
|
|
||||||
|
|
||||||
2. Enable and start:
|
|
||||||
sudo systemctl enable redis-server
|
|
||||||
sudo systemctl start redis-server
|
|
||||||
|
|
||||||
3. Verify:
|
|
||||||
redis-cli ping
|
|
||||||
# → PONG
|
|
||||||
|
|
||||||
4. Add to .env on server:
|
|
||||||
REDIS_URL=redis://localhost:6379
|
|
||||||
TAT_TEST_MODE=false
|
|
||||||
|
|
||||||
✅ FREE, NO LICENSE, PRODUCTION READY
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
VERIFY CONNECTION
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
After setup, check backend logs for:
|
|
||||||
✅ [TAT Queue] Connected to Redis
|
|
||||||
✅ [TAT Worker] Initialized and listening
|
|
||||||
|
|
||||||
Or test manually:
|
|
||||||
|
|
||||||
For Upstash:
|
|
||||||
- Use Upstash Console → CLI tab
|
|
||||||
- Type: PING → Should return PONG
|
|
||||||
|
|
||||||
For Local/Docker:
|
|
||||||
Test-NetConnection localhost -Port 6379
|
|
||||||
# Should show: TcpTestSucceeded : True
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
RESTART BACKEND
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
After Redis is running:
|
|
||||||
cd Re_Backend
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
You should see:
|
|
||||||
✅ [TAT Queue] Connected to Redis
|
|
||||||
✅ [TAT Worker] Initialized and listening
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
TEST TAT NOTIFICATIONS
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
1. Create a new workflow request
|
|
||||||
2. Set a short TAT (e.g., 2 hours for testing)
|
|
||||||
3. Submit the request
|
|
||||||
4. Check logs for:
|
|
||||||
- "TAT jobs scheduled"
|
|
||||||
- Notifications at 50%, 75%, 100%
|
|
||||||
|
|
||||||
For testing, you can modify working hours in:
|
|
||||||
Re_Backend/src/utils/tatTimeUtils.ts
|
|
||||||
|
|
||||||
-----------------------------------------
|
|
||||||
CURRENT STATUS
|
|
||||||
-----------------------------------------
|
|
||||||
|
|
||||||
❌ Redis: NOT RUNNING
|
|
||||||
❌ TAT Notifications: DISABLED
|
|
||||||
|
|
||||||
After installing Redis:
|
|
||||||
✅ Redis: RUNNING
|
|
||||||
✅ TAT Notifications: ENABLED
|
|
||||||
|
|
||||||
========================================
|
|
||||||
|
|
||||||
@ -1,266 +0,0 @@
|
|||||||
# Postman Collection Updates - Simplified API
|
|
||||||
|
|
||||||
## ✅ Updated Endpoints
|
|
||||||
|
|
||||||
The Postman collection has been updated to use the **simplified API format**. Here's what changed:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **1. Create Workflow (JSON) - Simplified** ✨
|
|
||||||
|
|
||||||
**Old Format (REMOVED):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"requestTitle": "...",
|
|
||||||
"requestDescription": "...",
|
|
||||||
"requestingDepartment": "IT",
|
|
||||||
"requestCategory": "PURCHASE_ORDER",
|
|
||||||
"approvers": [
|
|
||||||
{ "email": "...", "tatHours": 24, "level": 1 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**New Simplified Format:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Purchase Order Approval for Office Equipment",
|
|
||||||
"description": "Approval needed for purchasing new office equipment...",
|
|
||||||
"priority": "STANDARD",
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "manager@royalenfield.com",
|
|
||||||
"tatHours": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "director@royalenfield.com",
|
|
||||||
"tatHours": 48
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "cfo@royalenfield.com",
|
|
||||||
"tatHours": 72
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"spectators": [
|
|
||||||
{
|
|
||||||
"email": "hr@royalenfield.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"email": "finance@royalenfield.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Backend Does Automatically:**
|
|
||||||
- ✅ Finds/creates users from Okta/AD
|
|
||||||
- ✅ Generates level names from designation/department
|
|
||||||
- ✅ Auto-detects final approver (last level)
|
|
||||||
- ✅ Sets proper permissions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **2. Create Workflow (Multipart with Files) - Simplified** ✨
|
|
||||||
|
|
||||||
**Updated Form Data:**
|
|
||||||
| Key | Value |
|
|
||||||
|-----|-------|
|
|
||||||
| `payload` | `{"templateType":"CUSTOM","title":"...","description":"...","priority":"STANDARD","approvalLevels":[{"email":"manager@royalenfield.com","tatHours":24}],"spectators":[{"email":"hr@royalenfield.com"}]}` |
|
|
||||||
| `files` | Select file(s) |
|
|
||||||
| `category` | `SUPPORTING` (optional) |
|
|
||||||
|
|
||||||
**Changes:**
|
|
||||||
- ❌ Removed: `requestTitle`, `requestDescription`, `requestingDepartment`, `requestCategory`
|
|
||||||
- ❌ Removed: Complex approver format with level numbers
|
|
||||||
- ✅ Added: Single `payload` field with simplified JSON
|
|
||||||
- ✅ Simplified: Only `email` and `tatHours` per approver
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **3. Add Approver at Level - Simplified** 🆕
|
|
||||||
|
|
||||||
**NEW Endpoint Added!**
|
|
||||||
|
|
||||||
**Method:** `POST`
|
|
||||||
**URL:** `{{baseUrl}}/workflows/:id/approvers/at-level`
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "newapprover@royalenfield.com",
|
|
||||||
"tatHours": 24,
|
|
||||||
"level": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Backend Does:**
|
|
||||||
- ✅ Finds/creates user from Okta/AD
|
|
||||||
- ✅ Generates smart level name
|
|
||||||
- ✅ Shifts existing levels if needed
|
|
||||||
- ✅ Updates final approver flag
|
|
||||||
- ✅ Sends notifications
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **4. Add Spectator - Simplified** 🆕
|
|
||||||
|
|
||||||
**NEW Endpoint Added!**
|
|
||||||
|
|
||||||
**Method:** `POST`
|
|
||||||
**URL:** `{{baseUrl}}/workflows/:id/participants/spectator`
|
|
||||||
|
|
||||||
**Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "spectator@royalenfield.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**What Backend Does:**
|
|
||||||
- ✅ Finds/creates user from Okta/AD
|
|
||||||
- ✅ Sets spectator permissions (view + comment)
|
|
||||||
- ✅ Sends notification
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Complete Workflow Example
|
|
||||||
|
|
||||||
### Step 1: Login
|
|
||||||
```http
|
|
||||||
POST {{baseUrl}}/auth/login
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"email": "user@royalenfield.com",
|
|
||||||
"password": "your-password"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:** Save the `token` from response
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 2: Create Workflow (Simplified)
|
|
||||||
```http
|
|
||||||
POST {{baseUrl}}/workflows
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"templateType": "CUSTOM",
|
|
||||||
"title": "Purchase Order - Office Equipment",
|
|
||||||
"description": "Approval for office equipment purchase",
|
|
||||||
"priority": "STANDARD",
|
|
||||||
"approvalLevels": [
|
|
||||||
{
|
|
||||||
"email": "manager@royalenfield.com",
|
|
||||||
"tatHours": 24
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:** Save the `requestId` or `requestNumber`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3: Add Additional Approver
|
|
||||||
```http
|
|
||||||
POST {{baseUrl}}/workflows/REQ-2024-0001/approvers/at-level
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"email": "director@royalenfield.com",
|
|
||||||
"tatHours": 48,
|
|
||||||
"level": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 4: Add Spectator
|
|
||||||
```http
|
|
||||||
POST {{baseUrl}}/workflows/REQ-2024-0001/participants/spectator
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"email": "hr@royalenfield.com"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Key Benefits
|
|
||||||
|
|
||||||
### Before (Old Format):
|
|
||||||
- ❌ Required user IDs, names manually
|
|
||||||
- ❌ Complex payload structure
|
|
||||||
- ❌ Manual level naming
|
|
||||||
- ❌ Manual final approver detection
|
|
||||||
|
|
||||||
### After (New Simplified Format):
|
|
||||||
- ✅ Only email required
|
|
||||||
- ✅ Simple, clean JSON
|
|
||||||
- ✅ Auto-generated level names
|
|
||||||
- ✅ Auto-detected final approver
|
|
||||||
- ✅ Auto user creation from Okta/AD
|
|
||||||
- ✅ Clear error messages
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Environment Variables
|
|
||||||
|
|
||||||
Make sure to set these in Postman:
|
|
||||||
|
|
||||||
| Variable | Value | Example |
|
|
||||||
|----------|-------|---------|
|
|
||||||
| `baseUrl` | Backend API URL | `http://localhost:5000/api/v1` |
|
|
||||||
| `token` | Auth token from login | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Notes
|
|
||||||
|
|
||||||
1. **Backward Compatible:** The backend still accepts the old format, but the new format is recommended
|
|
||||||
2. **Auto User Creation:** If a user exists in Okta/AD but not in the database, they will be created automatically
|
|
||||||
3. **Smart Level Names:** Level names are generated from:
|
|
||||||
- User's designation (e.g., "Manager Approval")
|
|
||||||
- User's department (e.g., "Finance Approval")
|
|
||||||
- Fallback: "Level N Approval"
|
|
||||||
4. **Final Approver:** Last approval level is automatically marked as final approver
|
|
||||||
5. **Error Messages:** Clear, actionable error messages for invalid emails or users not found in AD
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ❓ Troubleshooting
|
|
||||||
|
|
||||||
### Error: "User not found in organization directory"
|
|
||||||
- **Cause:** Email doesn't exist in Okta/AD
|
|
||||||
- **Solution:** Verify email address is correct and user has an active account
|
|
||||||
|
|
||||||
### Error: "Duplicate approver email found"
|
|
||||||
- **Cause:** Same email used for multiple approval levels
|
|
||||||
- **Solution:** Each approver must have a unique email
|
|
||||||
|
|
||||||
### Error: "Invalid initiator"
|
|
||||||
- **Cause:** Auth token is invalid or user doesn't exist
|
|
||||||
- **Solution:** Re-login to get a fresh token
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
1. **Import Collection:** Import `Royal_Enfield_API_Collection.postman_collection.json` into Postman
|
|
||||||
2. **Set Environment:** Configure `baseUrl` and `token` variables
|
|
||||||
3. **Login:** Call the login endpoint to get your token
|
|
||||||
4. **Create Workflow:** Use the simplified "Create Workflow (JSON) - Simplified" endpoint
|
|
||||||
5. **Test:** Try adding approvers and spectators using the new simplified endpoints
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Updated:** December 2, 2025
|
|
||||||
**Version:** 2.0 - Simplified API Format
|
|
||||||
|
|
||||||
@ -1,239 +0,0 @@
|
|||||||
# User Notification Preferences
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Individual users can now control their notification preferences across three channels: **Email**, **Push**, and **In-App** notifications.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Features Implemented
|
|
||||||
|
|
||||||
### ✅ Backend
|
|
||||||
|
|
||||||
1. **Database Schema**
|
|
||||||
- Added three boolean fields to `users` table:
|
|
||||||
- `email_notifications_enabled` (default: true)
|
|
||||||
- `push_notifications_enabled` (default: true)
|
|
||||||
- `in_app_notifications_enabled` (default: true)
|
|
||||||
- Migration file: `20251203-add-user-notification-preferences.ts`
|
|
||||||
|
|
||||||
2. **User Model Updates**
|
|
||||||
- `Re_Backend/src/models/User.ts`
|
|
||||||
- Added fields: `emailNotificationsEnabled`, `pushNotificationsEnabled`, `inAppNotificationsEnabled`
|
|
||||||
|
|
||||||
3. **API Endpoints**
|
|
||||||
- **GET** `/api/v1/user/preferences/notifications` - Get current user's preferences
|
|
||||||
- **PUT** `/api/v1/user/preferences/notifications` - Update preferences
|
|
||||||
- Controller: `Re_Backend/src/controllers/userPreference.controller.ts`
|
|
||||||
- Routes: `Re_Backend/src/routes/userPreference.routes.ts`
|
|
||||||
- Validator: `Re_Backend/src/validators/userPreference.validator.ts`
|
|
||||||
|
|
||||||
4. **Notification Service Enhancement**
|
|
||||||
- `Re_Backend/src/services/notification.service.ts`
|
|
||||||
- Now checks user preferences before sending notifications
|
|
||||||
- Respects individual channel settings (email, push, in-app)
|
|
||||||
|
|
||||||
### ✅ Frontend
|
|
||||||
|
|
||||||
1. **API Service**
|
|
||||||
- `Re_Figma_Code/src/services/userPreferenceApi.ts`
|
|
||||||
- Functions: `getNotificationPreferences()`, `updateNotificationPreferences()`
|
|
||||||
|
|
||||||
2. **UI Component**
|
|
||||||
- `Re_Figma_Code/src/components/settings/NotificationPreferences.tsx`
|
|
||||||
- Beautiful card-based UI with toggle switches
|
|
||||||
- Real-time updates with loading states
|
|
||||||
- Success/error feedback
|
|
||||||
|
|
||||||
3. **Settings Page Integration**
|
|
||||||
- `Re_Figma_Code/src/pages/Settings/Settings.tsx`
|
|
||||||
- Full-width notification preferences card
|
|
||||||
- Separate browser push registration button
|
|
||||||
- Available for both admin and regular users
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### User Experience
|
|
||||||
|
|
||||||
1. **Navigate to Settings**
|
|
||||||
- User clicks on Settings in the navigation
|
|
||||||
|
|
||||||
2. **View Notification Preferences**
|
|
||||||
- Top card shows three toggle switches:
|
|
||||||
- 📧 **Email Notifications** - Receive notifications via email
|
|
||||||
- 🔔 **Push Notifications** - Receive browser push notifications
|
|
||||||
- 💬 **In-App Notifications** - Show notifications within the application
|
|
||||||
|
|
||||||
3. **Toggle Preferences**
|
|
||||||
- Click any switch to enable/disable that channel
|
|
||||||
- Changes are saved immediately
|
|
||||||
- Success message confirms the update
|
|
||||||
|
|
||||||
4. **Register Browser for Push** (separate card)
|
|
||||||
- One-time setup per browser/device
|
|
||||||
- Requests browser permission
|
|
||||||
- Registers the browser endpoint for push notifications
|
|
||||||
|
|
||||||
### System Behavior
|
|
||||||
|
|
||||||
**When sending notifications:**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// System checks user preferences
|
|
||||||
if (user.inAppNotificationsEnabled) {
|
|
||||||
// Create in-app notification in database
|
|
||||||
// Emit socket event for real-time delivery
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.pushNotificationsEnabled) {
|
|
||||||
// Send browser push notification (if browser is registered)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.emailNotificationsEnabled) {
|
|
||||||
// Send email notification (when implemented)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- ✅ Users have full control over notification channels
|
|
||||||
- ✅ Reduces notification fatigue
|
|
||||||
- ✅ Improves user experience
|
|
||||||
- ✅ Respects user preferences while ensuring critical alerts are delivered
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Documentation
|
|
||||||
|
|
||||||
### Get Notification Preferences
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```http
|
|
||||||
GET /api/v1/user/preferences/notifications
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"emailNotificationsEnabled": true,
|
|
||||||
"pushNotificationsEnabled": true,
|
|
||||||
"inAppNotificationsEnabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Notification Preferences
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
```http
|
|
||||||
PUT /api/v1/user/preferences/notifications
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"emailNotificationsEnabled": false,
|
|
||||||
"pushNotificationsEnabled": true,
|
|
||||||
"inAppNotificationsEnabled": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"message": "Notification preferences updated successfully",
|
|
||||||
"data": {
|
|
||||||
"emailNotificationsEnabled": false,
|
|
||||||
"pushNotificationsEnabled": true,
|
|
||||||
"inAppNotificationsEnabled": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Database Migration
|
|
||||||
|
|
||||||
To apply the migration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd Re_Backend
|
|
||||||
npm run migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
This will add the three notification preference columns to the `users` table with default value `true` for all existing users.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Admin Configuration vs User Preferences
|
|
||||||
|
|
||||||
### Two Levels of Control:
|
|
||||||
|
|
||||||
1. **System-Wide (Admin Only)**
|
|
||||||
- Settings → Configuration → Notification Rules
|
|
||||||
- `ENABLE_EMAIL_NOTIFICATIONS` - Master switch for email
|
|
||||||
- `ENABLE_PUSH_NOTIFICATIONS` - Master switch for push
|
|
||||||
- If admin disables a channel, it's disabled for ALL users
|
|
||||||
|
|
||||||
2. **User-Level (Individual Users)**
|
|
||||||
- Settings → User Settings → Notification Preferences
|
|
||||||
- Users can disable channels for themselves
|
|
||||||
- User preferences are respected only if admin has enabled the channel
|
|
||||||
|
|
||||||
### Logic:
|
|
||||||
```
|
|
||||||
Notification Sent = Admin Enabled AND User Enabled
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
- [ ] Email notification implementation (currently just a preference toggle)
|
|
||||||
- [ ] SMS notifications support
|
|
||||||
- [ ] Granular notification types (e.g., only approval requests, only TAT alerts)
|
|
||||||
- [ ] Quiet hours / Do Not Disturb schedules
|
|
||||||
- [ ] Notification digest/batching preferences
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
- [x] User can view their notification preferences
|
|
||||||
- [x] User can toggle email notifications on/off
|
|
||||||
- [x] User can toggle push notifications on/off
|
|
||||||
- [x] User can toggle in-app notifications on/off
|
|
||||||
- [x] Notification service respects user preferences
|
|
||||||
- [x] In-app notifications are not created if disabled
|
|
||||||
- [x] Push notifications are not sent if disabled
|
|
||||||
- [x] UI shows loading states during updates
|
|
||||||
- [x] UI shows success/error messages
|
|
||||||
- [x] Migration adds columns with correct defaults
|
|
||||||
- [x] API endpoints require authentication
|
|
||||||
- [x] Changes persist after logout/login
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified/Created
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- ✅ `src/models/User.ts` - Added notification preference fields
|
|
||||||
- ✅ `src/migrations/20251203-add-user-notification-preferences.ts` - Migration
|
|
||||||
- ✅ `src/controllers/userPreference.controller.ts` - New controller
|
|
||||||
- ✅ `src/routes/userPreference.routes.ts` - New routes
|
|
||||||
- ✅ `src/validators/userPreference.validator.ts` - New validator
|
|
||||||
- ✅ `src/routes/index.ts` - Registered new routes
|
|
||||||
- ✅ `src/services/notification.service.ts` - Updated to respect preferences
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- ✅ `src/services/userPreferenceApi.ts` - New API service
|
|
||||||
- ✅ `src/components/settings/NotificationPreferences.tsx` - New component
|
|
||||||
- ✅ `src/pages/Settings/Settings.tsx` - Integrated new component
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Implementation Complete! 🎉**
|
|
||||||
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
-- Debug script to check TAT alerts
|
|
||||||
-- Run this to see if alerts are being created
|
|
||||||
|
|
||||||
-- 1. Check if tat_alerts table exists
|
|
||||||
SELECT
|
|
||||||
table_name,
|
|
||||||
column_name,
|
|
||||||
data_type
|
|
||||||
FROM information_schema.columns
|
|
||||||
WHERE table_name = 'tat_alerts'
|
|
||||||
ORDER BY ordinal_position;
|
|
||||||
|
|
||||||
-- 2. Count total TAT alerts
|
|
||||||
SELECT COUNT(*) as total_alerts FROM tat_alerts;
|
|
||||||
|
|
||||||
-- 3. Show recent TAT alerts (if any)
|
|
||||||
SELECT
|
|
||||||
alert_id,
|
|
||||||
threshold_percentage,
|
|
||||||
alert_sent_at,
|
|
||||||
alert_message,
|
|
||||||
metadata
|
|
||||||
FROM tat_alerts
|
|
||||||
ORDER BY alert_sent_at DESC
|
|
||||||
LIMIT 5;
|
|
||||||
|
|
||||||
-- 4. Check approval levels with TAT status
|
|
||||||
SELECT
|
|
||||||
level_id,
|
|
||||||
request_id,
|
|
||||||
level_number,
|
|
||||||
approver_name,
|
|
||||||
tat_hours,
|
|
||||||
status,
|
|
||||||
tat50_alert_sent,
|
|
||||||
tat75_alert_sent,
|
|
||||||
tat_breached,
|
|
||||||
tat_start_time
|
|
||||||
FROM approval_levels
|
|
||||||
WHERE tat_start_time IS NOT NULL
|
|
||||||
ORDER BY tat_start_time DESC
|
|
||||||
LIMIT 5;
|
|
||||||
|
|
||||||
-- 5. Check if Redis is needed (are there any pending/in-progress levels?)
|
|
||||||
SELECT
|
|
||||||
w.request_number,
|
|
||||||
al.level_number,
|
|
||||||
al.approver_name,
|
|
||||||
al.status,
|
|
||||||
al.level_start_time,
|
|
||||||
al.tat_hours
|
|
||||||
FROM approval_levels al
|
|
||||||
JOIN workflow_requests w ON al.request_id = w.request_id
|
|
||||||
WHERE al.status IN ('PENDING', 'IN_PROGRESS')
|
|
||||||
ORDER BY al.level_start_time DESC;
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user