Compare commits
No commits in common. "bdfda741675afead8c011c7f3d280467391f650d" and "e03049a861cc73787f07e50f94192d9620142438" have entirely different histories.
bdfda74167
...
e03049a861
@ -1,2 +1,2 @@
|
||||
import{a as s}from"./index-x1JLuWho.js";import"./radix-vendor-DIkYAdWy.js";import"./charts-vendor-Bme4E5cb.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DbB0YGPu.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B1UBYWWO.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-DX2Gwh6C.js.map
|
||||
import{a as s}from"./index-7JN9lLwu.js";import"./radix-vendor-DIkYAdWy.js";import"./charts-vendor-Bme4E5cb.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DbB0YGPu.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B1UBYWWO.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-CMghC3Jo.js.map
|
||||
@ -1 +1 @@
|
||||
{"version":3,"file":"conclusionApi-DX2Gwh6C.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 * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,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,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
||||
{"version":3,"file":"conclusionApi-CMghC3Jo.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 * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,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,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
build/assets/index-B-mLDzJe.css
Normal file
1
build/assets/index-B-mLDzJe.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
@ -52,7 +52,7 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-x1JLuWho.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-7JN9lLwu.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-Bme4E5cb.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-DIkYAdWy.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DNMmNUQL.js">
|
||||
@ -60,7 +60,7 @@
|
||||
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B1UBYWWO.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CNlPctO6.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B-mLDzJe.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -3,16 +3,6 @@ import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
// 1. Debugging: Print what the app actually sees
|
||||
console.log('--- Database Config Debug ---');
|
||||
console.log(`DB_HOST: ${process.env.DB_HOST}`);
|
||||
console.log(`DB_SSL (Raw): '${process.env.DB_SSL}`); // Quotes help see trailing spaces
|
||||
|
||||
// 2. Fix: Trim whitespace to ensure "true " becomes "true"
|
||||
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
|
||||
console.log(`SSL Enabled: ${isSSL}`);
|
||||
console.log('---------------------------');
|
||||
|
||||
const sequelize = new Sequelize({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432', 10),
|
||||
@ -20,7 +10,7 @@ const sequelize = new Sequelize({
|
||||
username: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
dialect: 'postgres',
|
||||
logging: false,
|
||||
logging: false, // Disable SQL query logging for cleaner console output
|
||||
pool: {
|
||||
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
||||
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
|
||||
@ -28,8 +18,7 @@ const sequelize = new Sequelize({
|
||||
idle: 10000,
|
||||
},
|
||||
dialectOptions: {
|
||||
// 3. Use the robust boolean we calculated above
|
||||
ssl: isSSL ? {
|
||||
ssl: process.env.DB_SSL === 'true' ? {
|
||||
require: true,
|
||||
rejectUnauthorized: false,
|
||||
} : false,
|
||||
|
||||
@ -236,7 +236,6 @@ export class WorkflowController {
|
||||
priority: validated.priority as Priority,
|
||||
approvalLevels: enrichedApprovalLevels,
|
||||
participants: autoGeneratedParticipants,
|
||||
isDraft: parsed.isDraft === true, // Submit by default unless isDraft is explicitly true
|
||||
} as any;
|
||||
|
||||
const requestMeta = getRequestMetadata(req);
|
||||
@ -683,10 +682,7 @@ export class WorkflowController {
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
const validated = validateUpdateWorkflow(parsed);
|
||||
const updateData: UpdateWorkflowRequest = {
|
||||
...validated,
|
||||
isDraft: parsed.isDraft !== undefined ? (parsed.isDraft === true) : undefined
|
||||
} as any;
|
||||
const updateData: UpdateWorkflowRequest = { ...validated } as any;
|
||||
if (validated.priority) {
|
||||
updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
// DB constants moved inside functions to ensure secrets are loaded first
|
||||
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
|
||||
|
||||
async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
const DB_HOST = process.env.DB_HOST || 'localhost';
|
||||
const DB_PORT = parseInt(process.env.DB_PORT || '5432');
|
||||
@ -36,7 +36,6 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
user: DB_USER,
|
||||
password: DB_PASSWORD,
|
||||
database: 'postgres', // Connect to default postgres database
|
||||
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
|
||||
});
|
||||
|
||||
try {
|
||||
@ -65,7 +64,6 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
||||
user: DB_USER,
|
||||
password: DB_PASSWORD,
|
||||
database: DB_NAME,
|
||||
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
|
||||
});
|
||||
|
||||
await newDbClient.connect();
|
||||
|
||||
@ -337,7 +337,7 @@ class GoogleSecretManagerService {
|
||||
private getDefaultSecretNames(): string[] {
|
||||
return [
|
||||
// Database
|
||||
'DB_PASSWORD',
|
||||
//'DB_PASSWORD',
|
||||
|
||||
// JWT & Session
|
||||
'JWT_SECRET',
|
||||
|
||||
@ -2450,9 +2450,6 @@ export class WorkflowService {
|
||||
try {
|
||||
const requestNumber = await generateRequestNumber();
|
||||
const totalTatHours = workflowData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0);
|
||||
const isDraftRequested = workflowData.isDraft === true;
|
||||
const initialStatus = isDraftRequested ? WorkflowStatus.DRAFT : WorkflowStatus.PENDING;
|
||||
const now = new Date();
|
||||
|
||||
const workflow = await WorkflowRequest.create({
|
||||
requestNumber,
|
||||
@ -2464,10 +2461,9 @@ export class WorkflowService {
|
||||
currentLevel: 1,
|
||||
totalLevels: workflowData.approvalLevels.length,
|
||||
totalTatHours,
|
||||
status: initialStatus,
|
||||
isDraft: isDraftRequested,
|
||||
isDeleted: false,
|
||||
submissionDate: isDraftRequested ? undefined : now
|
||||
status: WorkflowStatus.DRAFT,
|
||||
isDraft: true,
|
||||
isDeleted: false
|
||||
});
|
||||
|
||||
// Create approval levels
|
||||
@ -2553,18 +2549,15 @@ export class WorkflowService {
|
||||
type: 'created',
|
||||
user: { userId: initiatorId, name: initiatorName },
|
||||
timestamp: new Date().toISOString(),
|
||||
action: isDraftRequested ? 'Draft request created' : 'Initial request submitted',
|
||||
details: isDraftRequested
|
||||
? `Draft request "${workflowData.title}" created by ${initiatorName}`
|
||||
: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
|
||||
action: 'Initial request submitted',
|
||||
details: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
|
||||
ipAddress: requestMetadata?.ipAddress || undefined,
|
||||
userAgent: requestMetadata?.userAgent || undefined
|
||||
});
|
||||
|
||||
// If not a draft, initiate the workflow (approvals, notifications, etc.)
|
||||
if (!isDraftRequested) {
|
||||
return await this.internalSubmitWorkflow(workflow, now);
|
||||
}
|
||||
// NOTE: Notifications are NOT sent here because workflows are created as DRAFTS
|
||||
// Notifications will be sent in submitWorkflow() when the draft is actually submitted
|
||||
// This prevents approvers from being notified about draft requests
|
||||
|
||||
return workflow;
|
||||
} catch (error) {
|
||||
@ -3119,9 +3112,6 @@ export class WorkflowService {
|
||||
// Only allow full updates (approval levels, participants) for DRAFT workflows
|
||||
const isDraft = (workflow as any).status === WorkflowStatus.DRAFT || (workflow as any).isDraft;
|
||||
|
||||
// Determine if this is a transition from draft to submitted
|
||||
const isTransitioningToSubmitted = updateData.isDraft === false && (workflow as any).isDraft;
|
||||
|
||||
// Update basic workflow fields
|
||||
const basicUpdate: any = {};
|
||||
if (updateData.title) basicUpdate.title = updateData.title;
|
||||
@ -3129,13 +3119,6 @@ export class WorkflowService {
|
||||
if (updateData.priority) basicUpdate.priority = updateData.priority;
|
||||
if (updateData.status) basicUpdate.status = updateData.status;
|
||||
if (updateData.conclusionRemark !== undefined) basicUpdate.conclusionRemark = updateData.conclusionRemark;
|
||||
if (updateData.isDraft !== undefined) basicUpdate.isDraft = updateData.isDraft;
|
||||
|
||||
// If transitioning, ensure status and submissionDate are set
|
||||
if (isTransitioningToSubmitted) {
|
||||
basicUpdate.status = WorkflowStatus.PENDING;
|
||||
basicUpdate.submissionDate = new Date();
|
||||
}
|
||||
|
||||
await workflow.update(basicUpdate);
|
||||
|
||||
@ -3284,13 +3267,8 @@ export class WorkflowService {
|
||||
logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`);
|
||||
}
|
||||
|
||||
// If transitioning, call the internal submission logic (notifications, TAT, etc.)
|
||||
if (isTransitioningToSubmitted) {
|
||||
logger.info(`[Workflow] Transitioning draft ${actualRequestId} to submitted state`);
|
||||
return await this.internalSubmitWorkflow(workflow, (workflow as any).submissionDate);
|
||||
}
|
||||
|
||||
// Reload the workflow instance to get latest data
|
||||
// Reload the workflow instance to get latest data (without associations to avoid the error)
|
||||
// The associations issue occurs when trying to include them, so we skip that
|
||||
const refreshed = await WorkflowRequest.findByPk(actualRequestId);
|
||||
return refreshed;
|
||||
} catch (error) {
|
||||
@ -3312,24 +3290,12 @@ export class WorkflowService {
|
||||
const workflow = await this.findWorkflowByIdentifier(requestId);
|
||||
if (!workflow) return null;
|
||||
|
||||
const now = new Date();
|
||||
return await this.internalSubmitWorkflow(workflow, now);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to submit workflow ${requestId}:`, error);
|
||||
throw new Error('Failed to submit workflow');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to handle workflow submission logic (status update, notifications, TAT scheduling)
|
||||
* Centralized here to support both direct creation-submission and draft-to-submission flows.
|
||||
*/
|
||||
private async internalSubmitWorkflow(workflow: WorkflowRequest, now: Date): Promise<WorkflowRequest> {
|
||||
// Get the actual requestId (UUID) - handle both UUID and requestNumber cases
|
||||
const actualRequestId = (workflow as any).getDataValue
|
||||
? (workflow as any).getDataValue('requestId')
|
||||
: (workflow as any).requestId;
|
||||
|
||||
const now = new Date();
|
||||
const updated = await workflow.update({
|
||||
status: WorkflowStatus.PENDING,
|
||||
isDraft: false,
|
||||
@ -3474,7 +3440,10 @@ export class WorkflowService {
|
||||
logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError);
|
||||
// Don't fail the submission if spectator notifications fail
|
||||
}
|
||||
|
||||
return updated;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to submit workflow ${requestId}:`, error);
|
||||
throw new Error('Failed to submit workflow');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,6 @@ export interface CreateWorkflowRequest {
|
||||
priority: Priority;
|
||||
approvalLevels: CreateApprovalLevel[];
|
||||
participants?: CreateParticipant[];
|
||||
isDraft?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateWorkflowRequest {
|
||||
@ -43,7 +42,6 @@ export interface UpdateWorkflowRequest {
|
||||
participants?: CreateParticipant[];
|
||||
// Document updates (add new documents via multipart, delete via IDs)
|
||||
deleteDocumentIds?: string[];
|
||||
isDraft?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateApprovalLevel {
|
||||
|
||||
@ -44,7 +44,6 @@ export const createWorkflowSchema = z.object({
|
||||
priorityUi: z.string().optional(),
|
||||
templateId: z.string().optional(),
|
||||
ccList: z.array(z.any()).optional(),
|
||||
isDraft: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const updateWorkflowSchema = z.object({
|
||||
@ -74,7 +73,6 @@ export const updateWorkflowSchema = z.object({
|
||||
notificationEnabled: z.boolean().optional(),
|
||||
})).optional(),
|
||||
deleteDocumentIds: z.array(z.string().uuid()).optional(),
|
||||
isDraft: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Helper to validate UUID or requestNumber format
|
||||
|
||||
Loading…
Reference in New Issue
Block a user