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};
//# sourceMappingURL=conclusionApi-DS6rROxV.js.map
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-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;
}
</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/radix-vendor-BP4rDxsU.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/socket-vendor-TjCxX7sJ.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>
<body>
<div id="root"></div>

View File

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

View File

@ -130,7 +130,8 @@ export class DashboardService {
initiator?: string,
approver?: string,
approverType?: 'current' | 'any',
search?: string
search?: string,
slaCompliance?: string
) {
const range = this.parseDateRange(dateRange, startDate, endDate);
@ -197,6 +198,29 @@ export class DashboardService {
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
// Personal Level: Regular users see only requests they INITIATED
// Note: For pending/open requests, count ALL pending requests regardless of creation date

View File

@ -426,30 +426,25 @@ export class SummaryService {
});
if (!user) {
// User doesn't exist in database, try to ensure they exist
// The userIdOrOktaId is the Okta ID, we need to fetch user info and create them
// 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 directly from Okta
try {
// Search for the user - we'll need to search by the Okta ID
// Since searchUsers might return Okta users, we need to handle this
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;
});
// First, try to fetch user directly from Okta by ID
const oktaUser = await userService.fetchUserFromOktaById(userIdOrOktaId);
if (oktaUser) {
if (oktaUser && oktaUser.status === 'ACTIVE') {
// Ensure user exists in database
const ensuredUser = await userService.ensureUserExists({
userId: (oktaUser as any).oktaSub || (oktaUser as any).userId || userIdOrOktaId,
email: (oktaUser as any).email,
displayName: (oktaUser as any).displayName,
firstName: (oktaUser as any).firstName,
lastName: (oktaUser as any).lastName,
department: (oktaUser as any).department,
phone: (oktaUser as any).phone
userId: oktaUser.id,
email: oktaUser.profile.email || oktaUser.profile.login,
displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
firstName: oktaUser.profile.firstName,
lastName: oktaUser.profile.lastName,
department: oktaUser.profile.department,
phone: oktaUser.profile.mobilePhone
});
internalUserIds.push(ensuredUser.userId);
logger.info(`[Summary] Created user ${ensuredUser.userId} from Okta ID ${userIdOrOktaId} for sharing`);
} else {
// Try to find by email if userIdOrOktaId looks like an email
if (userIdOrOktaId.includes('@')) {
@ -459,13 +454,13 @@ export class SummaryService {
if (user) {
internalUserIds.push(user.userId);
} 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 {
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);
// Try to find by email if userIdOrOktaId looks like an email
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)
* Used when tagging users from Okta search results

View File

@ -1443,7 +1443,8 @@ export class WorkflowService {
const whereConditions: any[] = [{ initiatorId: userId }];
// 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
if (filters?.status && filters.status !== 'all') {
@ -1455,6 +1456,9 @@ export class WorkflowService {
{ status: 'IN_PROGRESS' }
]
});
} else if (statusUpper === 'DRAFT') {
// Draft status - filter by isDraft flag
whereConditions.push({ isDraft: true });
} else {
whereConditions.push({ status: statusUpper });
}