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};
|
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
|
||||||
@ -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;
|
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>
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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('@')) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user