delaer sla tracker functionalities enhanced
This commit is contained in:
parent
4de9cddb6b
commit
8dc7fd3307
423
docs/DMS_INTEGRATION_API.md
Normal file
423
docs/DMS_INTEGRATION_API.md
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
# DMS Integration API Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the data exchange between the Royal Enfield Workflow System and the DMS (Document Management System) for:
|
||||||
|
1. **E-Invoice Generation** - Submitting claim data to DMS for e-invoice creation
|
||||||
|
2. **Credit Note Generation** - Fetching/Generating credit note from DMS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. E-Invoice Generation (DMS Push)
|
||||||
|
|
||||||
|
### When It's Called
|
||||||
|
|
||||||
|
This API is called when:
|
||||||
|
- **Step 6** of the claim management workflow is approved (Requestor approves the claim)
|
||||||
|
- User manually pushes claim data to DMS via the "Push to DMS" action
|
||||||
|
- System auto-generates e-invoice after claim approval
|
||||||
|
|
||||||
|
### Request Details
|
||||||
|
|
||||||
|
**Endpoint:** `POST {DMS_BASE_URL}/api/invoices/generate`
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
```http
|
||||||
|
Authorization: Bearer {DMS_API_KEY}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_number": "REQ-2025-001234",
|
||||||
|
"dealer_code": "DLR001",
|
||||||
|
"dealer_name": "ABC Motors",
|
||||||
|
"amount": 150000.00,
|
||||||
|
"description": "E-Invoice for claim request REQ-2025-001234",
|
||||||
|
"io_number": "IO-2025-001",
|
||||||
|
"tax_details": {
|
||||||
|
"cgst": 0,
|
||||||
|
"sgst": 0,
|
||||||
|
"igst": 0,
|
||||||
|
"total_tax": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Field Descriptions
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `request_number` | string | Yes | Unique request number from RE Workflow System (e.g., "REQ-2025-001234") |
|
||||||
|
| `dealer_code` | string | Yes | Dealer's unique code/identifier |
|
||||||
|
| `dealer_name` | string | Yes | Dealer's business name |
|
||||||
|
| `amount` | number | Yes | Total invoice amount (in INR, decimal format) |
|
||||||
|
| `description` | string | Yes | Description of the invoice/claim |
|
||||||
|
| `io_number` | string | No | Internal Order (IO) number if available |
|
||||||
|
| `tax_details` | object | No | Tax breakdown (CGST, SGST, IGST, Total Tax) |
|
||||||
|
|
||||||
|
### Expected Response
|
||||||
|
|
||||||
|
**Success Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"e_invoice_number": "EINV-2025-001234",
|
||||||
|
"dms_number": "DMS-2025-001234",
|
||||||
|
"invoice_date": "2025-12-17T10:30:00.000Z",
|
||||||
|
"invoice_url": "https://dms.example.com/invoices/EINV-2025-001234"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Field Descriptions
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `success` | boolean | Indicates if the request was successful |
|
||||||
|
| `e_invoice_number` | string | Generated e-invoice number (unique identifier) |
|
||||||
|
| `dms_number` | string | DMS internal tracking number |
|
||||||
|
| `invoice_date` | string (ISO 8601) | Date when the invoice was generated |
|
||||||
|
| `invoice_url` | string | URL to view/download the invoice document |
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
**Error Response (400/500):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "Error message describing what went wrong",
|
||||||
|
"error_code": "INVALID_DEALER_CODE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Scenarios
|
||||||
|
|
||||||
|
| Error Code | Description | Possible Causes |
|
||||||
|
|------------|-------------|-----------------|
|
||||||
|
| `INVALID_DEALER_CODE` | Dealer code not found in DMS | Dealer not registered in DMS |
|
||||||
|
| `INVALID_AMOUNT` | Amount validation failed | Negative amount or invalid format |
|
||||||
|
| `IO_NOT_FOUND` | IO number not found | Invalid or non-existent IO number |
|
||||||
|
| `DMS_SERVICE_ERROR` | DMS internal error | DMS system unavailable or processing error |
|
||||||
|
|
||||||
|
### Example cURL Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://dms.example.com/api/invoices/generate" \
|
||||||
|
-H "Authorization: Bearer YOUR_DMS_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"request_number": "REQ-2025-001234",
|
||||||
|
"dealer_code": "DLR001",
|
||||||
|
"dealer_name": "ABC Motors",
|
||||||
|
"amount": 150000.00,
|
||||||
|
"description": "E-Invoice for claim request REQ-2025-001234",
|
||||||
|
"io_number": "IO-2025-001"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Credit Note Generation (DMS Fetch)
|
||||||
|
|
||||||
|
### When It's Called
|
||||||
|
|
||||||
|
This API is called when:
|
||||||
|
- **Step 8** of the claim management workflow is initiated (Credit Note Confirmation)
|
||||||
|
- User requests to generate/fetch credit note from DMS
|
||||||
|
- System auto-generates credit note after e-invoice is confirmed
|
||||||
|
|
||||||
|
### Request Details
|
||||||
|
|
||||||
|
**Endpoint:** `POST {DMS_BASE_URL}/api/credit-notes/generate`
|
||||||
|
|
||||||
|
**Headers:**
|
||||||
|
```http
|
||||||
|
Authorization: Bearer {DMS_API_KEY}
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_number": "REQ-2025-001234",
|
||||||
|
"e_invoice_number": "EINV-2025-001234",
|
||||||
|
"dealer_code": "DLR001",
|
||||||
|
"dealer_name": "ABC Motors",
|
||||||
|
"amount": 150000.00,
|
||||||
|
"reason": "Claim settlement",
|
||||||
|
"description": "Credit note for claim request REQ-2025-001234"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Field Descriptions
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `request_number` | string | Yes | Original request number from RE Workflow System |
|
||||||
|
| `e_invoice_number` | string | Yes | E-invoice number that was generated earlier (must exist in DMS) |
|
||||||
|
| `dealer_code` | string | Yes | Dealer's unique code/identifier (must match invoice) |
|
||||||
|
| `dealer_name` | string | Yes | Dealer's business name |
|
||||||
|
| `amount` | number | Yes | Credit note amount (in INR, decimal format) - typically matches invoice amount |
|
||||||
|
| `reason` | string | Yes | Reason for credit note (e.g., "Claim settlement", "Return", "Adjustment") |
|
||||||
|
| `description` | string | No | Additional description/details about the credit note |
|
||||||
|
|
||||||
|
### Expected Response
|
||||||
|
|
||||||
|
**Success Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"credit_note_number": "CN-2025-001234",
|
||||||
|
"credit_note_date": "2025-12-17T10:30:00.000Z",
|
||||||
|
"credit_note_amount": 150000.00,
|
||||||
|
"credit_note_url": "https://dms.example.com/credit-notes/CN-2025-001234"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Field Descriptions
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `success` | boolean | Indicates if the request was successful |
|
||||||
|
| `credit_note_number` | string | Generated credit note number (unique identifier) |
|
||||||
|
| `credit_note_date` | string (ISO 8601) | Date when the credit note was generated |
|
||||||
|
| `credit_note_amount` | number | Credit note amount (should match request amount) |
|
||||||
|
| `credit_note_url` | string | URL to view/download the credit note document |
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
**Error Response (400/500):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "Error message describing what went wrong",
|
||||||
|
"error_code": "INVOICE_NOT_FOUND"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Scenarios
|
||||||
|
|
||||||
|
| Error Code | Description | Possible Causes |
|
||||||
|
|------------|-------------|-----------------|
|
||||||
|
| `INVOICE_NOT_FOUND` | E-invoice number not found in DMS | Invoice was not generated or invalid invoice number |
|
||||||
|
| `INVALID_AMOUNT` | Amount validation failed | Amount mismatch with invoice or invalid format |
|
||||||
|
| `DEALER_MISMATCH` | Dealer code/name doesn't match invoice | Different dealer code than original invoice |
|
||||||
|
| `CREDIT_NOTE_EXISTS` | Credit note already generated for this invoice | Duplicate request for same invoice |
|
||||||
|
| `DMS_SERVICE_ERROR` | DMS internal error | DMS system unavailable or processing error |
|
||||||
|
|
||||||
|
### Example cURL Request
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://dms.example.com/api/credit-notes/generate" \
|
||||||
|
-H "Authorization: Bearer YOUR_DMS_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"request_number": "REQ-2025-001234",
|
||||||
|
"e_invoice_number": "EINV-2025-001234",
|
||||||
|
"dealer_code": "DLR001",
|
||||||
|
"dealer_name": "ABC Motors",
|
||||||
|
"amount": 150000.00,
|
||||||
|
"reason": "Claim settlement",
|
||||||
|
"description": "Credit note for claim request REQ-2025-001234"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
The following environment variables need to be configured in the RE Workflow System:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# DMS Integration Configuration
|
||||||
|
DMS_BASE_URL=https://dms.example.com
|
||||||
|
DMS_API_KEY=your_dms_api_key_here
|
||||||
|
|
||||||
|
# Alternative: Username/Password Authentication
|
||||||
|
DMS_USERNAME=your_dms_username
|
||||||
|
DMS_PASSWORD=your_dms_password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Methods
|
||||||
|
|
||||||
|
DMS supports two authentication methods:
|
||||||
|
|
||||||
|
1. **API Key Authentication** (Recommended)
|
||||||
|
- Set `DMS_API_KEY` in environment variables
|
||||||
|
- Header: `Authorization: Bearer {DMS_API_KEY}`
|
||||||
|
|
||||||
|
2. **Username/Password Authentication**
|
||||||
|
- Set `DMS_USERNAME` and `DMS_PASSWORD` in environment variables
|
||||||
|
- Use Basic Auth or custom authentication as per DMS requirements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Flow
|
||||||
|
|
||||||
|
### E-Invoice Generation Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ RE Workflow │
|
||||||
|
│ System │
|
||||||
|
│ (Step 6) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
│ POST /api/invoices/generate
|
||||||
|
│ { request_number, dealer_code, amount, ... }
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ DMS System │
|
||||||
|
│ │
|
||||||
|
│ - Validates │
|
||||||
|
│ - Generates │
|
||||||
|
│ E-Invoice │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
│ Response: { e_invoice_number, dms_number, ... }
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ RE Workflow │
|
||||||
|
│ System │
|
||||||
|
│ │
|
||||||
|
│ - Stores │
|
||||||
|
│ invoice data │
|
||||||
|
│ - Updates │
|
||||||
|
│ workflow │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Credit Note Generation Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ RE Workflow │
|
||||||
|
│ System │
|
||||||
|
│ (Step 8) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
│ POST /api/credit-notes/generate
|
||||||
|
│ { e_invoice_number, request_number, amount, ... }
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ DMS System │
|
||||||
|
│ │
|
||||||
|
│ - Validates │
|
||||||
|
│ invoice │
|
||||||
|
│ - Generates │
|
||||||
|
│ Credit Note │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
│ Response: { credit_note_number, credit_note_date, ... }
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ RE Workflow │
|
||||||
|
│ System │
|
||||||
|
│ │
|
||||||
|
│ - Stores │
|
||||||
|
│ credit note │
|
||||||
|
│ - Updates │
|
||||||
|
│ workflow │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Mapping
|
||||||
|
|
||||||
|
### RE Workflow System → DMS
|
||||||
|
|
||||||
|
| RE Workflow Field | DMS Request Field | Notes |
|
||||||
|
|-------------------|-------------------|-------|
|
||||||
|
| `request.requestNumber` | `request_number` | Direct mapping |
|
||||||
|
| `claimDetails.dealerCode` | `dealer_code` | Direct mapping |
|
||||||
|
| `claimDetails.dealerName` | `dealer_name` | Direct mapping |
|
||||||
|
| `budgetTracking.closedExpenses` | `amount` | Total claim amount |
|
||||||
|
| `internalOrder.ioNumber` | `io_number` | Optional, if available |
|
||||||
|
| `claimInvoice.invoiceNumber` | `e_invoice_number` | For credit note only |
|
||||||
|
|
||||||
|
### DMS → RE Workflow System
|
||||||
|
|
||||||
|
| DMS Response Field | RE Workflow Field | Notes |
|
||||||
|
|-------------------|-------------------|-------|
|
||||||
|
| `e_invoice_number` | `ClaimInvoice.invoiceNumber` | Stored in database |
|
||||||
|
| `dms_number` | `ClaimInvoice.dmsNumber` | Stored in database |
|
||||||
|
| `invoice_date` | `ClaimInvoice.invoiceDate` | Converted to Date object |
|
||||||
|
| `credit_note_number` | `ClaimCreditNote.creditNoteNumber` | Stored in database |
|
||||||
|
| `credit_note_date` | `ClaimCreditNote.creditNoteDate` | Converted to Date object |
|
||||||
|
| `credit_note_amount` | `ClaimCreditNote.creditNoteAmount` | Stored in database |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Mock Mode
|
||||||
|
|
||||||
|
When DMS is not configured, the system operates in **mock mode**:
|
||||||
|
- Returns mock invoice/credit note numbers
|
||||||
|
- Logs warnings instead of making actual API calls
|
||||||
|
- Useful for development and testing
|
||||||
|
|
||||||
|
### Test Data
|
||||||
|
|
||||||
|
**E-Invoice Test Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_number": "REQ-TEST-001",
|
||||||
|
"dealer_code": "TEST-DLR-001",
|
||||||
|
"dealer_name": "Test Dealer",
|
||||||
|
"amount": 10000.00,
|
||||||
|
"description": "Test invoice generation"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Credit Note Test Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"request_number": "REQ-TEST-001",
|
||||||
|
"e_invoice_number": "EINV-TEST-001",
|
||||||
|
"dealer_code": "TEST-DLR-001",
|
||||||
|
"dealer_name": "Test Dealer",
|
||||||
|
"amount": 10000.00,
|
||||||
|
"reason": "Test credit note",
|
||||||
|
"description": "Test credit note generation"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
1. **Idempotency**: Both APIs should be idempotent - calling them multiple times with the same data should not create duplicates.
|
||||||
|
|
||||||
|
2. **Amount Validation**: DMS should validate that credit note amount matches or is less than the original invoice amount.
|
||||||
|
|
||||||
|
3. **Invoice Dependency**: Credit note generation requires a valid e-invoice to exist in DMS first.
|
||||||
|
|
||||||
|
4. **Error Handling**: RE Workflow System handles DMS errors gracefully and allows manual entry if DMS is unavailable.
|
||||||
|
|
||||||
|
5. **Retry Logic**: Consider implementing retry logic for transient DMS failures.
|
||||||
|
|
||||||
|
6. **Webhooks** (Optional): DMS can send webhooks to notify RE Workflow System when invoice/credit note status changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions regarding DMS integration:
|
||||||
|
- **Backend Team**: Check logs in `Re_Backend/src/services/dmsIntegration.service.ts`
|
||||||
|
- **DMS Team**: Contact DMS support for API-related issues
|
||||||
|
- **Documentation**: Refer to DMS API documentation for latest updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** December 17, 2025
|
||||||
|
**Version:** 1.0
|
||||||
|
|
||||||
@ -89,6 +89,7 @@ export class DealerClaimService {
|
|||||||
// Now create workflow request (manager is validated)
|
// Now create workflow request (manager is validated)
|
||||||
// For claim management, requests are submitted immediately (not drafts)
|
// For claim management, requests are submitted immediately (not drafts)
|
||||||
// Step 1 will be active for dealer to submit proposal
|
// Step 1 will be active for dealer to submit proposal
|
||||||
|
const now = new Date();
|
||||||
const workflowRequest = await WorkflowRequest.create({
|
const workflowRequest = await WorkflowRequest.create({
|
||||||
initiatorId: userId,
|
initiatorId: userId,
|
||||||
requestNumber,
|
requestNumber,
|
||||||
@ -103,6 +104,7 @@ export class DealerClaimService {
|
|||||||
totalTatHours: 0, // Will be calculated from approval levels
|
totalTatHours: 0, // Will be calculated from approval levels
|
||||||
isDraft: false, // Not a draft - submitted and ready for workflow
|
isDraft: false, // Not a draft - submitted and ready for workflow
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
|
submissionDate: now, // Set submission date for SLA tracking (required for overall SLA calculation)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create claim details
|
// Create claim details
|
||||||
@ -133,6 +135,34 @@ export class DealerClaimService {
|
|||||||
// Pass the already-resolved departmentLead to avoid re-searching
|
// Pass the already-resolved departmentLead to avoid re-searching
|
||||||
await this.createClaimApprovalLevels(workflowRequest.requestId, userId, claimData.dealerEmail, claimData.selectedManagerEmail, departmentLead);
|
await this.createClaimApprovalLevels(workflowRequest.requestId, userId, claimData.dealerEmail, claimData.selectedManagerEmail, departmentLead);
|
||||||
|
|
||||||
|
// Schedule TAT jobs for Step 1 (Dealer Proposal Submission) - first active step
|
||||||
|
// This ensures SLA tracking starts immediately from request creation
|
||||||
|
const { tatSchedulerService } = await import('./tatScheduler.service');
|
||||||
|
const dealerLevel = await ApprovalLevel.findOne({
|
||||||
|
where: {
|
||||||
|
requestId: workflowRequest.requestId,
|
||||||
|
levelNumber: 1 // Step 1: Dealer Proposal Submission
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dealerLevel && dealerLevel.approverId && dealerLevel.levelStartTime) {
|
||||||
|
try {
|
||||||
|
const workflowPriority = (workflowRequest as any)?.priority || 'STANDARD';
|
||||||
|
await tatSchedulerService.scheduleTatJobs(
|
||||||
|
workflowRequest.requestId,
|
||||||
|
(dealerLevel as any).levelId,
|
||||||
|
dealerLevel.approverId,
|
||||||
|
Number(dealerLevel.tatHours || 0),
|
||||||
|
dealerLevel.levelStartTime,
|
||||||
|
workflowPriority
|
||||||
|
);
|
||||||
|
logger.info(`[DealerClaimService] TAT jobs scheduled for Step 1 (Dealer Proposal Submission) - Priority: ${workflowPriority}`);
|
||||||
|
} catch (tatError) {
|
||||||
|
logger.error(`[DealerClaimService] Failed to schedule TAT jobs for Step 1:`, tatError);
|
||||||
|
// Don't fail request creation if TAT scheduling fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create participants (initiator, dealer, department lead, finance - exclude system)
|
// Create participants (initiator, dealer, department lead, finance - exclude system)
|
||||||
await this.createClaimParticipants(workflowRequest.requestId, userId, claimData.dealerEmail);
|
await this.createClaimParticipants(workflowRequest.requestId, userId, claimData.dealerEmail);
|
||||||
|
|
||||||
@ -164,13 +194,7 @@ export class DealerClaimService {
|
|||||||
// Step 1: Dealer Proposal Submission (first active step - log assignment at creation)
|
// Step 1: Dealer Proposal Submission (first active step - log assignment at creation)
|
||||||
// Subsequent steps will have assignment logged when they become active (via approval service)
|
// Subsequent steps will have assignment logged when they become active (via approval service)
|
||||||
|
|
||||||
// Notify Step 1 (Dealer) - this is the first active step, so log assignment now
|
// Notify Step 1 (Dealer) - dealerLevel was already fetched above for TAT scheduling
|
||||||
const dealerLevel = await ApprovalLevel.findOne({
|
|
||||||
where: {
|
|
||||||
requestId: workflowRequest.requestId,
|
|
||||||
levelNumber: 1 // Step 1: Dealer Proposal Submission
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dealerLevel && dealerLevel.approverId) {
|
if (dealerLevel && dealerLevel.approverId) {
|
||||||
// Skip notifications for system processes
|
// Skip notifications for system processes
|
||||||
@ -400,6 +424,11 @@ export class DealerClaimService {
|
|||||||
approverName = approverName || 'Unknown Approver';
|
approverName = approverName || 'Unknown Approver';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 1 is the first active step - set levelStartTime immediately so SLA tracking starts
|
||||||
|
// For other steps, levelStartTime will be set when they become active
|
||||||
|
const now = new Date();
|
||||||
|
const isStep1 = step.level === 1;
|
||||||
|
|
||||||
await ApprovalLevel.create({
|
await ApprovalLevel.create({
|
||||||
requestId,
|
requestId,
|
||||||
levelNumber: step.level,
|
levelNumber: step.level,
|
||||||
@ -418,6 +447,10 @@ export class DealerClaimService {
|
|||||||
elapsedHours: 0,
|
elapsedHours: 0,
|
||||||
remainingHours: step.tatHours,
|
remainingHours: step.tatHours,
|
||||||
tatPercentageUsed: 0,
|
tatPercentageUsed: 0,
|
||||||
|
// CRITICAL: Set levelStartTime for Step 1 immediately so SLA tracking starts from request creation
|
||||||
|
// This ensures dealers see SLA tracker in Open Requests even before they take any action
|
||||||
|
levelStartTime: isStep1 ? now : undefined,
|
||||||
|
tatStartTime: isStep1 ? now : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2754,10 +2754,16 @@ export class WorkflowService {
|
|||||||
const status = (approval.status || '').toString().toUpperCase();
|
const status = (approval.status || '').toString().toUpperCase();
|
||||||
const approvalData = approval.toJSON();
|
const approvalData = approval.toJSON();
|
||||||
const isPausedLevel = status === 'PAUSED' || approval.isPaused;
|
const isPausedLevel = status === 'PAUSED' || approval.isPaused;
|
||||||
|
const approvalLevelNumber = approval.levelNumber || 0;
|
||||||
|
const workflowCurrentLevelNumber = currentLevel ? (currentLevel as any).levelNumber : ((workflow as any).currentLevel || 1);
|
||||||
|
|
||||||
// Calculate SLA for active approvals (pending/in-progress/paused)
|
// Calculate SLA ONLY for the CURRENT active level (matching currentLevel)
|
||||||
|
// This ensures that when in step 1, only step 1 has elapsed time, others have 0
|
||||||
// Include PAUSED so we show SLA for the paused approver, not the next one
|
// Include PAUSED so we show SLA for the paused approver, not the next one
|
||||||
if (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED') {
|
const isCurrentLevel = approvalLevelNumber === workflowCurrentLevelNumber;
|
||||||
|
const shouldCalculateSLA = isCurrentLevel && (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED');
|
||||||
|
|
||||||
|
if (shouldCalculateSLA) {
|
||||||
const levelStartTime = approval.levelStartTime || approval.tatStartTime || approval.createdAt;
|
const levelStartTime = approval.levelStartTime || approval.tatStartTime || approval.createdAt;
|
||||||
const tatHours = Number(approval.tatHours || 0);
|
const tatHours = Number(approval.tatHours || 0);
|
||||||
|
|
||||||
@ -2814,23 +2820,39 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For waiting levels (future levels that haven't started), set elapsedHours to 0
|
||||||
|
// This ensures that when in step 1, steps 2-8 show elapsedHours = 0
|
||||||
|
if (approvalLevelNumber > workflowCurrentLevelNumber && status !== 'APPROVED' && status !== 'REJECTED') {
|
||||||
|
return {
|
||||||
|
...approvalData,
|
||||||
|
elapsedHours: 0,
|
||||||
|
remainingHours: Number(approval.tatHours || 0),
|
||||||
|
tatPercentageUsed: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// For completed/rejected levels, return as-is (already has final values from database)
|
// For completed/rejected levels, return as-is (already has final values from database)
|
||||||
return approvalData;
|
return approvalData;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Calculate overall request SLA based on cumulative elapsed hours from all levels
|
// Calculate overall request SLA based on cumulative elapsed hours from all levels
|
||||||
// This correctly accounts for pause periods since each level's elapsedHours is pause-adjusted
|
// This correctly accounts for pause periods since each level's elapsedHours is pause-adjusted
|
||||||
const submissionDate = (workflow as any).submissionDate;
|
// Use submissionDate if available, otherwise fallback to createdAt for SLA calculation
|
||||||
|
const submissionDate = (workflow as any).submissionDate || (workflow as any).createdAt;
|
||||||
const totalTatHours = updatedApprovals.reduce((sum, a) => sum + Number(a.tatHours || 0), 0);
|
const totalTatHours = updatedApprovals.reduce((sum, a) => sum + Number(a.tatHours || 0), 0);
|
||||||
let overallSLA = null;
|
let overallSLA = null;
|
||||||
|
|
||||||
if (submissionDate && totalTatHours > 0) {
|
if (submissionDate && totalTatHours > 0) {
|
||||||
// Calculate total elapsed hours by summing elapsed hours from all levels
|
// Calculate total elapsed hours by summing elapsed hours from all levels
|
||||||
// This ensures pause periods are correctly excluded from the overall calculation
|
// CRITICAL: Only count elapsed hours from completed levels + current active level
|
||||||
|
// Waiting levels (future steps) should contribute 0 elapsed hours
|
||||||
|
// This ensures that when in step 1, only step 1's elapsed hours are counted
|
||||||
let totalElapsedHours = 0;
|
let totalElapsedHours = 0;
|
||||||
|
const workflowCurrentLevelNumber = currentLevel ? (currentLevel as any).levelNumber : ((workflow as any).currentLevel || 1);
|
||||||
|
|
||||||
for (const approval of updatedApprovals) {
|
for (const approval of updatedApprovals) {
|
||||||
const status = (approval.status || '').toString().toUpperCase();
|
const status = (approval.status || '').toString().toUpperCase();
|
||||||
|
const approvalLevelNumber = approval.levelNumber || 0;
|
||||||
|
|
||||||
if (status === 'APPROVED' || status === 'REJECTED') {
|
if (status === 'APPROVED' || status === 'REJECTED') {
|
||||||
// For completed levels, use the stored elapsedHours (already pause-adjusted from when level was completed)
|
// For completed levels, use the stored elapsedHours (already pause-adjusted from when level was completed)
|
||||||
@ -2839,6 +2861,11 @@ export class WorkflowService {
|
|||||||
// Skipped levels don't contribute to elapsed time
|
// Skipped levels don't contribute to elapsed time
|
||||||
continue;
|
continue;
|
||||||
} else if (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED') {
|
} else if (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED') {
|
||||||
|
// CRITICAL: Only count elapsed hours for the CURRENT active level
|
||||||
|
// Waiting levels (future steps) should NOT contribute elapsed hours
|
||||||
|
// This ensures request-level elapsed time matches the current step's elapsed time
|
||||||
|
const isCurrentLevel = approvalLevelNumber === workflowCurrentLevelNumber;
|
||||||
|
if (isCurrentLevel) {
|
||||||
// For active/paused levels, use the SLA-calculated elapsedHours (pause-adjusted)
|
// For active/paused levels, use the SLA-calculated elapsedHours (pause-adjusted)
|
||||||
if (approval.sla?.elapsedHours !== undefined) {
|
if (approval.sla?.elapsedHours !== undefined) {
|
||||||
totalElapsedHours += Number(approval.sla.elapsedHours);
|
totalElapsedHours += Number(approval.sla.elapsedHours);
|
||||||
@ -2846,6 +2873,8 @@ export class WorkflowService {
|
|||||||
totalElapsedHours += Number(approval.elapsedHours || 0);
|
totalElapsedHours += Number(approval.elapsedHours || 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Waiting levels (approvalLevelNumber > workflowCurrentLevelNumber) contribute 0 elapsed hours
|
||||||
|
}
|
||||||
// WAITING levels haven't started yet, so no elapsed time
|
// WAITING levels haven't started yet, so no elapsed time
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2865,7 +2894,7 @@ export class WorkflowService {
|
|||||||
overallStatus = 'approaching';
|
overallStatus = 'approaching';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format time display
|
// Format time display (simple format - frontend will handle detailed formatting)
|
||||||
const formatTime = (hours: number) => {
|
const formatTime = (hours: number) => {
|
||||||
if (hours < 1) return `${Math.round(hours * 60)}m`;
|
if (hours < 1) return `${Math.round(hours * 60)}m`;
|
||||||
const wholeHours = Math.floor(hours);
|
const wholeHours = Math.floor(hours);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user