filter stat issue resolved on stat and checking automation
This commit is contained in:
parent
97ff8a2058
commit
29f7421cd3
BIN
build/assets/Re_Logo-By51taPS.png
Normal file
BIN
build/assets/Re_Logo-By51taPS.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@ -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};
|
||||
//# sourceMappingURL=conclusionApi-BOHzpMlV.js.map
|
||||
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-Bc2W59QD.js.map
|
||||
@ -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"}
|
||||
43
build/assets/index-B6Vs1HfP.js
Normal file
43
build/assets/index-B6Vs1HfP.js
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-B6Vs1HfP.js.map
Normal file
1
build/assets/index-B6Vs1HfP.js.map
Normal file
File diff suppressed because one or more lines are too long
1
build/assets/index-Cnw6_NIY.css
Normal file
1
build/assets/index-Cnw6_NIY.css
Normal file
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
@ -2,7 +2,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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" />
|
||||
<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" />
|
||||
@ -51,14 +52,14 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
</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/radix-vendor-CbkudDDo.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/socket-vendor-TjCxX7sJ.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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
|
||||
"main": "dist/server.js",
|
||||
"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:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
|
||||
"build": "tsc && tsc-alias",
|
||||
|
||||
@ -43,8 +43,21 @@ export class DashboardController {
|
||||
const dateRange = req.query.dateRange as string | undefined;
|
||||
const startDate = req.query.startDate 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({
|
||||
success: true,
|
||||
|
||||
@ -154,7 +154,22 @@ export class WorkflowController {
|
||||
try {
|
||||
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 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');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
@ -119,13 +119,48 @@ export class DashboardService {
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Check if user is admin or management (has broader access)
|
||||
const user = await User.findByPk(userId);
|
||||
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
|
||||
// Personal Level: Regular users see only requests they INITIATED
|
||||
// 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.submission_date IS NOT NULL
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
${filterConditions}
|
||||
`;
|
||||
|
||||
let whereClauseForAllPending = `
|
||||
@ -143,6 +179,7 @@ export class DashboardService {
|
||||
AND (wf.is_deleted IS NULL OR wf.is_deleted = false)
|
||||
AND (wf.status = 'PENDING' OR wf.status = 'IN_PROGRESS')
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
${filterConditions}
|
||||
`;
|
||||
|
||||
// Get total, approved, rejected, and closed requests created in date range
|
||||
@ -155,7 +192,7 @@ export class DashboardService {
|
||||
FROM workflow_requests wf
|
||||
${whereClauseForDateRange}
|
||||
`, {
|
||||
replacements: { start: range.start, end: range.end, userId },
|
||||
replacements,
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
@ -167,18 +204,18 @@ export class DashboardService {
|
||||
FROM workflow_requests wf
|
||||
${whereClauseForAllPending}
|
||||
`, {
|
||||
replacements: { userId },
|
||||
replacements,
|
||||
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(`
|
||||
SELECT COUNT(*)::int AS draft_count
|
||||
FROM workflow_requests wf
|
||||
WHERE wf.is_draft = true
|
||||
${!isAdmin ? `AND wf.initiator_id = :userId` : ''}
|
||||
${draftWhereClause}
|
||||
`, {
|
||||
replacements: { userId },
|
||||
replacements,
|
||||
type: QueryTypes.SELECT
|
||||
});
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { Op, QueryTypes } from 'sequelize';
|
||||
import { sequelize } from '@config/database';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dayjs from 'dayjs';
|
||||
import { notificationService } from './notification.service';
|
||||
import { activityService } from './activity.service';
|
||||
import { tatSchedulerService } from './tatScheduler.service';
|
||||
@ -453,9 +454,107 @@ export class WorkflowService {
|
||||
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;
|
||||
|
||||
// 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({
|
||||
where,
|
||||
offset,
|
||||
limit,
|
||||
order: [['createdAt', 'DESC']],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user