draft issue omy request resolved along with that designation mapped in share request detail screen

This commit is contained in:
laxmanhalaki 2025-11-24 21:11:49 +05:30
parent 8c602fef24
commit b8b6663add
12 changed files with 111 additions and 43 deletions

View File

@ -1,2 +1,2 @@
import{a as t}from"./index-I97Fx9AA.js";import"./radix-vendor-BP4rDxsU.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-CHsmPIhP.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-D8zdG6sn.js";import"./radix-vendor-BP4rDxsU.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-CHsmPIhP.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-DS6rROxV.js.map //# sourceMappingURL=conclusionApi-CEyw043E.js.map

View File

@ -1 +1 @@
{"version":3,"file":"conclusionApi-DS6rROxV.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-CEyw043E.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

View File

@ -52,14 +52,14 @@
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
</style> </style>
<script type="module" crossorigin src="/assets/index-I97Fx9AA.js"></script> <script type="module" crossorigin src="/assets/index-D8zdG6sn.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-BP4rDxsU.js"> <link rel="modulepreload" crossorigin href="/assets/radix-vendor-BP4rDxsU.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-CHsmPIhP.js"> <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CHsmPIhP.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-Cnw6_NIY.css"> <link rel="stylesheet" crossorigin href="/assets/index-DMP7vjzn.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -50,6 +50,7 @@ export class DashboardController {
const approver = req.query.approver as string | undefined; const approver = req.query.approver as string | undefined;
const approverType = req.query.approverType as 'current' | 'any' | undefined; const approverType = req.query.approverType as 'current' | 'any' | undefined;
const search = req.query.search as string | undefined; const search = req.query.search as string | undefined;
const slaCompliance = req.query.slaCompliance as string | undefined;
const stats = await this.dashboardService.getRequestStats( const stats = await this.dashboardService.getRequestStats(
userId, userId,
@ -62,7 +63,8 @@ export class DashboardController {
initiator, initiator,
approver, approver,
approverType, approverType,
search search,
slaCompliance
); );
res.json({ res.json({

View File

@ -130,7 +130,8 @@ export class DashboardService {
initiator?: string, initiator?: string,
approver?: string, approver?: string,
approverType?: 'current' | 'any', approverType?: 'current' | 'any',
search?: string search?: string,
slaCompliance?: string
) { ) {
const range = this.parseDateRange(dateRange, startDate, endDate); const range = this.parseDateRange(dateRange, startDate, endDate);
@ -197,6 +198,29 @@ export class DashboardService {
replacements.approverId = approver; replacements.approverId = approver;
} }
// SLA Compliance filter
if (slaCompliance && slaCompliance !== 'all') {
if (slaCompliance === 'breached') {
filterConditions += ` AND EXISTS (
SELECT 1 FROM tat_alerts ta
INNER JOIN approval_levels al ON ta.level_id = al.level_id
WHERE ta.request_id = wf.request_id
AND ta.is_breached = true
)`;
} else if (slaCompliance === 'compliant') {
// Compliant: completed requests that are not breached
filterConditions += ` AND wf.status IN ('APPROVED', 'REJECTED', 'CLOSED')
AND NOT EXISTS (
SELECT 1 FROM tat_alerts ta
INNER JOIN approval_levels al ON ta.level_id = al.level_id
WHERE ta.request_id = wf.request_id
AND ta.is_breached = true
)`;
}
// Note: on_track, approaching, critical are calculated dynamically
// For stats, we only filter by breached/compliant as these are stored in DB
}
// 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

View File

@ -426,30 +426,25 @@ export class SummaryService {
}); });
if (!user) { if (!user) {
// User doesn't exist in database, try to ensure they exist // User doesn't exist in database, try to fetch from Okta and create them
// The userIdOrOktaId is the Okta ID, we need to fetch user info and create them // The userIdOrOktaId is the Okta ID, we need to fetch user info directly from Okta
try { try {
// Search for the user - we'll need to search by the Okta ID // First, try to fetch user directly from Okta by ID
// Since searchUsers might return Okta users, we need to handle this const oktaUser = await userService.fetchUserFromOktaById(userIdOrOktaId);
const oktaUsers = await userService.searchUsers(userIdOrOktaId, 10);
const oktaUser = oktaUsers.find((u: any) => {
// Check if this is the user we're looking for
const userOktaId = (u as any).oktaSub || (u as any).userId;
return userOktaId === userIdOrOktaId;
});
if (oktaUser) { if (oktaUser && oktaUser.status === 'ACTIVE') {
// Ensure user exists in database // Ensure user exists in database
const ensuredUser = await userService.ensureUserExists({ const ensuredUser = await userService.ensureUserExists({
userId: (oktaUser as any).oktaSub || (oktaUser as any).userId || userIdOrOktaId, userId: oktaUser.id,
email: (oktaUser as any).email, email: oktaUser.profile.email || oktaUser.profile.login,
displayName: (oktaUser as any).displayName, displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
firstName: (oktaUser as any).firstName, firstName: oktaUser.profile.firstName,
lastName: (oktaUser as any).lastName, lastName: oktaUser.profile.lastName,
department: (oktaUser as any).department, department: oktaUser.profile.department,
phone: (oktaUser as any).phone phone: oktaUser.profile.mobilePhone
}); });
internalUserIds.push(ensuredUser.userId); internalUserIds.push(ensuredUser.userId);
logger.info(`[Summary] Created user ${ensuredUser.userId} from Okta ID ${userIdOrOktaId} for sharing`);
} else { } else {
// Try to find by email if userIdOrOktaId looks like an email // Try to find by email if userIdOrOktaId looks like an email
if (userIdOrOktaId.includes('@')) { if (userIdOrOktaId.includes('@')) {
@ -459,13 +454,13 @@ export class SummaryService {
if (user) { if (user) {
internalUserIds.push(user.userId); internalUserIds.push(user.userId);
} else { } else {
logger.warn(`[Summary] User with email ${userIdOrOktaId} not found, skipping`); logger.warn(`[Summary] User with email ${userIdOrOktaId} not found in database or Okta, skipping`);
} }
} else { } else {
logger.warn(`[Summary] User with Okta ID ${userIdOrOktaId} not found, skipping`); logger.warn(`[Summary] User with Okta ID ${userIdOrOktaId} not found in Okta or is inactive, skipping`);
} }
} }
} catch (oktaError) { } catch (oktaError: any) {
logger.error(`[Summary] Failed to fetch user from Okta for ${userIdOrOktaId}:`, oktaError); logger.error(`[Summary] Failed to fetch user from Okta for ${userIdOrOktaId}:`, oktaError);
// Try to find by email if userIdOrOktaId looks like an email // Try to find by email if userIdOrOktaId looks like an email
if (userIdOrOktaId.includes('@')) { if (userIdOrOktaId.includes('@')) {

View File

@ -189,6 +189,37 @@ export class UserService {
}); });
} }
/**
* Fetch a user directly from Okta by their Okta ID
* Used when we have an Okta ID and need to get user details
*/
async fetchUserFromOktaById(oktaId: string): Promise<OktaUser | null> {
try {
const oktaDomain = process.env.OKTA_DOMAIN;
const oktaApiToken = process.env.OKTA_API_TOKEN;
if (!oktaDomain || !oktaApiToken) {
return null;
}
const response = await axios.get(`${oktaDomain}/api/v1/users/${oktaId}`, {
headers: {
'Authorization': `SSWS ${oktaApiToken}`,
'Accept': 'application/json'
},
timeout: 5000
});
return response.data as OktaUser;
} catch (error: any) {
if (error.response?.status === 404) {
// User not found in Okta
return null;
}
throw error;
}
}
/** /**
* Ensure user exists in database (create if not exists) * Ensure user exists in database (create if not exists)
* Used when tagging users from Okta search results * Used when tagging users from Okta search results

View File

@ -1443,7 +1443,8 @@ export class WorkflowService {
const whereConditions: any[] = [{ initiatorId: userId }]; const whereConditions: any[] = [{ initiatorId: userId }];
// Exclude drafts // Exclude drafts
whereConditions.push({ isDraft: false }); // Include drafts in "My Requests" - users may keep drafts for some time
// whereConditions.push({ isDraft: false }); // Removed to include drafts
// Apply status filter // Apply status filter
if (filters?.status && filters.status !== 'all') { if (filters?.status && filters.status !== 'all') {
@ -1455,6 +1456,9 @@ export class WorkflowService {
{ status: 'IN_PROGRESS' } { status: 'IN_PROGRESS' }
] ]
}); });
} else if (statusUpper === 'DRAFT') {
// Draft status - filter by isDraft flag
whereConditions.push({ isDraft: true });
} else { } else {
whereConditions.push({ status: statusUpper }); whereConditions.push({ status: statusUpper });
} }