draft issue omy request resolved along with that designation mapped in share request detail screen
This commit is contained in:
parent
8c602fef24
commit
b8b6663add
@ -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
|
||||
@ -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
1
build/assets/index-D8zdG6sn.js.map
Normal file
1
build/assets/index-D8zdG6sn.js.map
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
@ -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>
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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('@')) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user