filter stat issue resolved on stat and checking automation

This commit is contained in:
laxmanhalaki 2025-11-24 15:17:44 +05:30
parent 97ff8a2058
commit 29f7421cd3
15 changed files with 227 additions and 62 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,2 +1,2 @@
import{a as t}from"./index-Leqyafa0.js";import"./radix-vendor-CbkudDDo.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-i7LKlA3D.js";import"./socket-vendor-TjCxX7sJ.js";import"./router-vendor-1fSSvDCY.js";async function l(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function m(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function d(n){return(await t.get(`/conclusions/${n}`)).data.data}export{m as finalizeConclusion,l as generateConclusion,d as getConclusion}; import{a as t}from"./index-B6Vs1HfP.js";import"./radix-vendor-CbkudDDo.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-i7LKlA3D.js";import"./socket-vendor-TjCxX7sJ.js";import"./router-vendor-1fSSvDCY.js";async function l(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function m(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function d(n){return(await t.get(`/conclusions/${n}`)).data.data}export{m as finalizeConclusion,l as generateConclusion,d as getConclusion};
//# sourceMappingURL=conclusionApi-BOHzpMlV.js.map //# sourceMappingURL=conclusionApi-Bc2W59QD.js.map

View File

@ -1 +1 @@
{"version":3,"file":"conclusionApi-BOHzpMlV.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"0PAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"} {"version":3,"file":"conclusionApi-Bc2W59QD.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"0PAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,8 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data: https: blob:; connect-src 'self' blob: data:; frame-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self';" /> <!-- CSP: Allows blob URLs for file previews and cross-origin API calls during development -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'; img-src 'self' data: https: blob:; connect-src 'self' blob: data: http://localhost:5000 http://localhost:3000 ws://localhost:5000 ws://localhost:3000 wss://localhost:5000 wss://localhost:3000; frame-src 'self' blob:; font-src 'self' https://fonts.gstatic.com data:; object-src 'none'; base-uri 'self'; form-action 'self';" />
<link rel="icon" type="image/svg+xml" href="/royal_enfield_logo.svg" /> <link rel="icon" type="image/svg+xml" href="/royal_enfield_logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Royal Enfield Approval & Request Management Portal - Streamlined approval workflows for enterprise operations" /> <meta name="description" content="Royal Enfield Approval & Request Management Portal - Streamlined approval workflows for enterprise operations" />
@ -51,14 +52,14 @@
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
</style> </style>
<script type="module" crossorigin src="/assets/index-Leqyafa0.js"></script> <script type="module" crossorigin src="/assets/index-B6Vs1HfP.js"></script>
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-Cji9-Yri.js"> <link rel="modulepreload" crossorigin href="/assets/charts-vendor-Cji9-Yri.js">
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CbkudDDo.js"> <link rel="modulepreload" crossorigin href="/assets/radix-vendor-CbkudDDo.js">
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DHm03ykU.js"> <link rel="modulepreload" crossorigin href="/assets/utils-vendor-DHm03ykU.js">
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-i7LKlA3D.js"> <link rel="modulepreload" crossorigin href="/assets/ui-vendor-i7LKlA3D.js">
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js"> <link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
<link rel="modulepreload" crossorigin href="/assets/router-vendor-1fSSvDCY.js"> <link rel="modulepreload" crossorigin href="/assets/router-vendor-1fSSvDCY.js">
<link rel="stylesheet" crossorigin href="/assets/index-DI8aVCLa.css"> <link rel="stylesheet" crossorigin href="/assets/index-Cnw6_NIY.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -4,7 +4,7 @@
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)", "description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
"main": "dist/server.js", "main": "dist/server.js",
"scripts": { "scripts": {
"start": "node dist/server.js", "start": "npm run build && npm run start:prod",
"dev": "npm run setup && nodemon --exec ts-node -r tsconfig-paths/register src/server.ts", "dev": "npm run setup && nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
"dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts", "dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
"build": "tsc && tsc-alias", "build": "tsc && tsc-alias",

View File

@ -43,8 +43,21 @@ export class DashboardController {
const dateRange = req.query.dateRange as string | undefined; const dateRange = req.query.dateRange as string | undefined;
const startDate = req.query.startDate as string | undefined; const startDate = req.query.startDate as string | undefined;
const endDate = req.query.endDate as string | undefined; const endDate = req.query.endDate as string | undefined;
const status = req.query.status as string | undefined; // Status filter (not used in stats - stats show all statuses)
const priority = req.query.priority as string | undefined;
const department = req.query.department as string | undefined;
const initiator = req.query.initiator as string | undefined;
const stats = await this.dashboardService.getRequestStats(userId, dateRange, startDate, endDate); const stats = await this.dashboardService.getRequestStats(
userId,
dateRange,
startDate,
endDate,
status,
priority,
department,
initiator
);
res.json({ res.json({
success: true, success: true,

View File

@ -154,7 +154,22 @@ export class WorkflowController {
try { try {
const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1); const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1);
const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100); const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100);
const result = await workflowService.listWorkflows(page, limit);
// Extract filter parameters
const filters = {
search: req.query.search as string | undefined,
status: req.query.status as string | undefined,
priority: req.query.priority as string | undefined,
department: req.query.department as string | undefined,
initiator: req.query.initiator as string | undefined,
approver: req.query.approver as string | undefined,
slaCompliance: req.query.slaCompliance as string | undefined,
dateRange: req.query.dateRange as string | undefined,
startDate: req.query.startDate as string | undefined,
endDate: req.query.endDate as string | undefined,
};
const result = await workflowService.listWorkflows(page, limit, filters);
ResponseHandler.success(res, result, 'Workflows fetched'); ResponseHandler.success(res, result, 'Workflows fetched');
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'; const errorMessage = error instanceof Error ? error.message : 'Unknown error';

View File

@ -119,13 +119,48 @@ export class DashboardService {
/** /**
* Get request volume and status statistics * Get request volume and status statistics
*/ */
async getRequestStats(userId: string, dateRange?: string, startDate?: string, endDate?: string) { async getRequestStats(
userId: string,
dateRange?: string,
startDate?: string,
endDate?: string,
status?: string,
priority?: string,
department?: string,
initiator?: string
) {
const range = this.parseDateRange(dateRange, startDate, endDate); const range = this.parseDateRange(dateRange, startDate, endDate);
// Check if user is admin or management (has broader access) // Check if user is admin or management (has broader access)
const user = await User.findByPk(userId); const user = await User.findByPk(userId);
const isAdmin = user?.hasManagementAccess() || false; const isAdmin = user?.hasManagementAccess() || false;
// Build filter conditions (excluding status - stats should show all statuses)
let filterConditions = '';
const replacements: any = { start: range.start, end: range.end, userId };
// Priority filter
if (priority && priority !== 'all') {
filterConditions += ` AND wf.priority = :priority`;
replacements.priority = priority.toUpperCase();
}
// Department filter (through initiator)
if (department && department !== 'all') {
filterConditions += ` AND EXISTS (
SELECT 1 FROM users u
WHERE u.user_id = wf.initiator_id
AND u.department = :department
)`;
replacements.department = department;
}
// Initiator filter
if (initiator && initiator !== 'all') {
filterConditions += ` AND wf.initiator_id = :initiatorId`;
replacements.initiatorId = initiator;
}
// Organization Level: Admin/Management see ALL requests across organization // Organization Level: Admin/Management see ALL requests across organization
// Personal Level: Regular users see only requests they INITIATED // Personal Level: Regular users see only requests they INITIATED
// Note: For pending/open requests, count ALL pending requests regardless of creation date // Note: For pending/open requests, count ALL pending requests regardless of creation date
@ -136,6 +171,7 @@ export class DashboardService {
AND (wf.is_deleted IS NULL OR wf.is_deleted = false) AND (wf.is_deleted IS NULL OR wf.is_deleted = false)
AND wf.submission_date IS NOT NULL AND wf.submission_date IS NOT NULL
${!isAdmin ? `AND wf.initiator_id = :userId` : ''} ${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
${filterConditions}
`; `;
let whereClauseForAllPending = ` let whereClauseForAllPending = `
@ -143,6 +179,7 @@ export class DashboardService {
AND (wf.is_deleted IS NULL OR wf.is_deleted = false) AND (wf.is_deleted IS NULL OR wf.is_deleted = false)
AND (wf.status = 'PENDING' OR wf.status = 'IN_PROGRESS') AND (wf.status = 'PENDING' OR wf.status = 'IN_PROGRESS')
${!isAdmin ? `AND wf.initiator_id = :userId` : ''} ${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
${filterConditions}
`; `;
// Get total, approved, rejected, and closed requests created in date range // Get total, approved, rejected, and closed requests created in date range
@ -155,7 +192,7 @@ export class DashboardService {
FROM workflow_requests wf FROM workflow_requests wf
${whereClauseForDateRange} ${whereClauseForDateRange}
`, { `, {
replacements: { start: range.start, end: range.end, userId }, replacements,
type: QueryTypes.SELECT type: QueryTypes.SELECT
}); });
@ -167,18 +204,18 @@ export class DashboardService {
FROM workflow_requests wf FROM workflow_requests wf
${whereClauseForAllPending} ${whereClauseForAllPending}
`, { `, {
replacements: { userId }, replacements,
type: QueryTypes.SELECT type: QueryTypes.SELECT
}); });
// Get draft count separately // Get draft count separately (with filters)
let draftWhereClause = `WHERE wf.is_draft = true ${!isAdmin ? `AND wf.initiator_id = :userId` : ''} ${filterConditions}`;
const draftResult = await sequelize.query(` const draftResult = await sequelize.query(`
SELECT COUNT(*)::int AS draft_count SELECT COUNT(*)::int AS draft_count
FROM workflow_requests wf FROM workflow_requests wf
WHERE wf.is_draft = true ${draftWhereClause}
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
`, { `, {
replacements: { userId }, replacements,
type: QueryTypes.SELECT type: QueryTypes.SELECT
}); });

View File

@ -14,6 +14,7 @@ import { Op, QueryTypes } from 'sequelize';
import { sequelize } from '@config/database'; import { sequelize } from '@config/database';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import dayjs from 'dayjs';
import { notificationService } from './notification.service'; import { notificationService } from './notification.service';
import { activityService } from './activity.service'; import { activityService } from './activity.service';
import { tatSchedulerService } from './tatScheduler.service'; import { tatSchedulerService } from './tatScheduler.service';
@ -453,9 +454,107 @@ export class WorkflowService {
throw error; throw error;
} }
} }
async listWorkflows(page: number, limit: number) { async listWorkflows(page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; department?: string; initiator?: string; approver?: string; slaCompliance?: string; dateRange?: string; startDate?: string; endDate?: string }) {
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
// Build where clause with filters
const whereConditions: any[] = [];
// Exclude drafts
whereConditions.push({ isDraft: false });
// Apply status filter
if (filters?.status && filters.status !== 'all') {
const statusUpper = filters.status.toUpperCase();
if (statusUpper === 'PENDING') {
whereConditions.push({
[Op.or]: [
{ status: 'PENDING' },
{ status: 'IN_PROGRESS' }
]
});
} else {
whereConditions.push({ status: statusUpper });
}
}
// Apply priority filter
if (filters?.priority && filters.priority !== 'all') {
whereConditions.push({ priority: filters.priority.toUpperCase() });
}
// Apply search filter (title, description, or requestNumber)
if (filters?.search && filters.search.trim()) {
whereConditions.push({
[Op.or]: [
{ title: { [Op.iLike]: `%${filters.search.trim()}%` } },
{ description: { [Op.iLike]: `%${filters.search.trim()}%` } },
{ requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
]
});
}
// Apply department filter (through initiator)
if (filters?.department && filters.department !== 'all') {
whereConditions.push({
'$initiator.department$': filters.department
});
}
// Apply initiator filter
if (filters?.initiator && filters.initiator !== 'all') {
whereConditions.push({ initiatorId: filters.initiator });
}
// Apply date range filter
if (filters?.dateRange || filters?.startDate || filters?.endDate) {
let dateStart: Date | null = null;
let dateEnd: Date | null = null;
if (filters.dateRange === 'custom' && filters.startDate && filters.endDate) {
dateStart = dayjs(filters.startDate).startOf('day').toDate();
dateEnd = dayjs(filters.endDate).endOf('day').toDate();
} else if (filters.startDate && filters.endDate) {
dateStart = dayjs(filters.startDate).startOf('day').toDate();
dateEnd = dayjs(filters.endDate).endOf('day').toDate();
} else if (filters.dateRange) {
const now = dayjs();
switch (filters.dateRange) {
case 'today':
dateStart = now.startOf('day').toDate();
dateEnd = now.endOf('day').toDate();
break;
case 'week':
dateStart = now.startOf('week').toDate();
dateEnd = now.endOf('week').toDate();
break;
case 'month':
dateStart = now.startOf('month').toDate();
dateEnd = now.endOf('month').toDate();
break;
}
}
if (dateStart && dateEnd) {
whereConditions.push({
[Op.or]: [
{ submissionDate: { [Op.between]: [dateStart, dateEnd] } },
// Fallback to createdAt if submissionDate is null
{
[Op.and]: [
{ submissionDate: null },
{ createdAt: { [Op.between]: [dateStart, dateEnd] } }
]
}
]
});
}
}
const where = whereConditions.length > 0 ? { [Op.and]: whereConditions } : {};
const { rows, count } = await WorkflowRequest.findAndCountAll({ const { rows, count } = await WorkflowRequest.findAndCountAll({
where,
offset, offset,
limit, limit,
order: [['createdAt', 'DESC']], order: [['createdAt', 'DESC']],