Compare commits
2 Commits
e03049a861
...
bdfda74167
| Author | SHA1 | Date | |
|---|---|---|---|
| bdfda74167 | |||
| b925ee5217 |
@ -1,2 +1,2 @@
|
|||||||
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};
|
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-CMghC3Jo.js.map
|
//# sourceMappingURL=conclusionApi-DX2Gwh6C.js.map
|
||||||
@ -1 +1 @@
|
|||||||
{"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"}
|
{"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"}
|
||||||
File diff suppressed because one or more lines are too long
1
build/assets/index-CNlPctO6.css
Normal file
1
build/assets/index-CNlPctO6.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
File diff suppressed because one or more lines are too long
@ -52,7 +52,7 @@
|
|||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-7JN9lLwu.js"></script>
|
<script type="module" crossorigin src="/assets/index-x1JLuWho.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-Bme4E5cb.js">
|
<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/radix-vendor-DIkYAdWy.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DNMmNUQL.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/socket-vendor-TjCxX7sJ.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B1UBYWWO.js">
|
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B1UBYWWO.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-B-mLDzJe.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CNlPctO6.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -3,6 +3,16 @@ import dotenv from 'dotenv';
|
|||||||
|
|
||||||
dotenv.config();
|
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({
|
const sequelize = new Sequelize({
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: parseInt(process.env.DB_PORT || '5432', 10),
|
port: parseInt(process.env.DB_PORT || '5432', 10),
|
||||||
@ -10,7 +20,7 @@ const sequelize = new Sequelize({
|
|||||||
username: process.env.DB_USER || 'postgres',
|
username: process.env.DB_USER || 'postgres',
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
logging: false, // Disable SQL query logging for cleaner console output
|
logging: false,
|
||||||
pool: {
|
pool: {
|
||||||
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
min: parseInt(process.env.DB_POOL_MIN || '2', 10),
|
||||||
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
|
max: parseInt(process.env.DB_POOL_MAX || '10', 10),
|
||||||
@ -18,7 +28,8 @@ const sequelize = new Sequelize({
|
|||||||
idle: 10000,
|
idle: 10000,
|
||||||
},
|
},
|
||||||
dialectOptions: {
|
dialectOptions: {
|
||||||
ssl: process.env.DB_SSL === 'true' ? {
|
// 3. Use the robust boolean we calculated above
|
||||||
|
ssl: isSSL ? {
|
||||||
require: true,
|
require: true,
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
} : false,
|
} : false,
|
||||||
|
|||||||
@ -236,6 +236,7 @@ export class WorkflowController {
|
|||||||
priority: validated.priority as Priority,
|
priority: validated.priority as Priority,
|
||||||
approvalLevels: enrichedApprovalLevels,
|
approvalLevels: enrichedApprovalLevels,
|
||||||
participants: autoGeneratedParticipants,
|
participants: autoGeneratedParticipants,
|
||||||
|
isDraft: parsed.isDraft === true, // Submit by default unless isDraft is explicitly true
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const requestMeta = getRequestMetadata(req);
|
const requestMeta = getRequestMetadata(req);
|
||||||
@ -682,7 +683,10 @@ export class WorkflowController {
|
|||||||
}
|
}
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
const validated = validateUpdateWorkflow(parsed);
|
const validated = validateUpdateWorkflow(parsed);
|
||||||
const updateData: UpdateWorkflowRequest = { ...validated } as any;
|
const updateData: UpdateWorkflowRequest = {
|
||||||
|
...validated,
|
||||||
|
isDraft: parsed.isDraft !== undefined ? (parsed.isDraft === true) : undefined
|
||||||
|
} as any;
|
||||||
if (validated.priority) {
|
if (validated.priority) {
|
||||||
updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD;
|
updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
|||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
// DB constants moved inside functions to ensure secrets are loaded first
|
// DB constants moved inside functions to ensure secrets are loaded first
|
||||||
|
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
|
||||||
async function checkAndCreateDatabase(): Promise<boolean> {
|
async function checkAndCreateDatabase(): Promise<boolean> {
|
||||||
const DB_HOST = process.env.DB_HOST || 'localhost';
|
const DB_HOST = process.env.DB_HOST || 'localhost';
|
||||||
const DB_PORT = parseInt(process.env.DB_PORT || '5432');
|
const DB_PORT = parseInt(process.env.DB_PORT || '5432');
|
||||||
@ -36,6 +36,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
|||||||
user: DB_USER,
|
user: DB_USER,
|
||||||
password: DB_PASSWORD,
|
password: DB_PASSWORD,
|
||||||
database: 'postgres', // Connect to default postgres database
|
database: 'postgres', // Connect to default postgres database
|
||||||
|
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -64,6 +65,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
|
|||||||
user: DB_USER,
|
user: DB_USER,
|
||||||
password: DB_PASSWORD,
|
password: DB_PASSWORD,
|
||||||
database: DB_NAME,
|
database: DB_NAME,
|
||||||
|
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
await newDbClient.connect();
|
await newDbClient.connect();
|
||||||
|
|||||||
@ -337,7 +337,7 @@ class GoogleSecretManagerService {
|
|||||||
private getDefaultSecretNames(): string[] {
|
private getDefaultSecretNames(): string[] {
|
||||||
return [
|
return [
|
||||||
// Database
|
// Database
|
||||||
//'DB_PASSWORD',
|
'DB_PASSWORD',
|
||||||
|
|
||||||
// JWT & Session
|
// JWT & Session
|
||||||
'JWT_SECRET',
|
'JWT_SECRET',
|
||||||
|
|||||||
@ -2450,6 +2450,9 @@ export class WorkflowService {
|
|||||||
try {
|
try {
|
||||||
const requestNumber = await generateRequestNumber();
|
const requestNumber = await generateRequestNumber();
|
||||||
const totalTatHours = workflowData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0);
|
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({
|
const workflow = await WorkflowRequest.create({
|
||||||
requestNumber,
|
requestNumber,
|
||||||
@ -2461,9 +2464,10 @@ export class WorkflowService {
|
|||||||
currentLevel: 1,
|
currentLevel: 1,
|
||||||
totalLevels: workflowData.approvalLevels.length,
|
totalLevels: workflowData.approvalLevels.length,
|
||||||
totalTatHours,
|
totalTatHours,
|
||||||
status: WorkflowStatus.DRAFT,
|
status: initialStatus,
|
||||||
isDraft: true,
|
isDraft: isDraftRequested,
|
||||||
isDeleted: false
|
isDeleted: false,
|
||||||
|
submissionDate: isDraftRequested ? undefined : now
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create approval levels
|
// Create approval levels
|
||||||
@ -2549,15 +2553,18 @@ export class WorkflowService {
|
|||||||
type: 'created',
|
type: 'created',
|
||||||
user: { userId: initiatorId, name: initiatorName },
|
user: { userId: initiatorId, name: initiatorName },
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
action: 'Initial request submitted',
|
action: isDraftRequested ? 'Draft request created' : 'Initial request submitted',
|
||||||
details: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
|
details: isDraftRequested
|
||||||
|
? `Draft request "${workflowData.title}" created by ${initiatorName}`
|
||||||
|
: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
|
||||||
ipAddress: requestMetadata?.ipAddress || undefined,
|
ipAddress: requestMetadata?.ipAddress || undefined,
|
||||||
userAgent: requestMetadata?.userAgent || undefined
|
userAgent: requestMetadata?.userAgent || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Notifications are NOT sent here because workflows are created as DRAFTS
|
// If not a draft, initiate the workflow (approvals, notifications, etc.)
|
||||||
// Notifications will be sent in submitWorkflow() when the draft is actually submitted
|
if (!isDraftRequested) {
|
||||||
// This prevents approvers from being notified about draft requests
|
return await this.internalSubmitWorkflow(workflow, now);
|
||||||
|
}
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -3112,6 +3119,9 @@ export class WorkflowService {
|
|||||||
// Only allow full updates (approval levels, participants) for DRAFT workflows
|
// Only allow full updates (approval levels, participants) for DRAFT workflows
|
||||||
const isDraft = (workflow as any).status === WorkflowStatus.DRAFT || (workflow as any).isDraft;
|
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
|
// Update basic workflow fields
|
||||||
const basicUpdate: any = {};
|
const basicUpdate: any = {};
|
||||||
if (updateData.title) basicUpdate.title = updateData.title;
|
if (updateData.title) basicUpdate.title = updateData.title;
|
||||||
@ -3119,6 +3129,13 @@ export class WorkflowService {
|
|||||||
if (updateData.priority) basicUpdate.priority = updateData.priority;
|
if (updateData.priority) basicUpdate.priority = updateData.priority;
|
||||||
if (updateData.status) basicUpdate.status = updateData.status;
|
if (updateData.status) basicUpdate.status = updateData.status;
|
||||||
if (updateData.conclusionRemark !== undefined) basicUpdate.conclusionRemark = updateData.conclusionRemark;
|
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);
|
await workflow.update(basicUpdate);
|
||||||
|
|
||||||
@ -3267,8 +3284,13 @@ export class WorkflowService {
|
|||||||
logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`);
|
logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the workflow instance to get latest data (without associations to avoid the error)
|
// If transitioning, call the internal submission logic (notifications, TAT, etc.)
|
||||||
// The associations issue occurs when trying to include them, so we skip that
|
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
|
||||||
const refreshed = await WorkflowRequest.findByPk(actualRequestId);
|
const refreshed = await WorkflowRequest.findByPk(actualRequestId);
|
||||||
return refreshed;
|
return refreshed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -3290,12 +3312,24 @@ export class WorkflowService {
|
|||||||
const workflow = await this.findWorkflowByIdentifier(requestId);
|
const workflow = await this.findWorkflowByIdentifier(requestId);
|
||||||
if (!workflow) return null;
|
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
|
// Get the actual requestId (UUID) - handle both UUID and requestNumber cases
|
||||||
const actualRequestId = (workflow as any).getDataValue
|
const actualRequestId = (workflow as any).getDataValue
|
||||||
? (workflow as any).getDataValue('requestId')
|
? (workflow as any).getDataValue('requestId')
|
||||||
: (workflow as any).requestId;
|
: (workflow as any).requestId;
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const updated = await workflow.update({
|
const updated = await workflow.update({
|
||||||
status: WorkflowStatus.PENDING,
|
status: WorkflowStatus.PENDING,
|
||||||
isDraft: false,
|
isDraft: false,
|
||||||
@ -3440,10 +3474,7 @@ export class WorkflowService {
|
|||||||
logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError);
|
logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError);
|
||||||
// Don't fail the submission if spectator notifications fail
|
// Don't fail the submission if spectator notifications fail
|
||||||
}
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Failed to submit workflow ${requestId}:`, error);
|
|
||||||
throw new Error('Failed to submit workflow');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export interface CreateWorkflowRequest {
|
|||||||
priority: Priority;
|
priority: Priority;
|
||||||
approvalLevels: CreateApprovalLevel[];
|
approvalLevels: CreateApprovalLevel[];
|
||||||
participants?: CreateParticipant[];
|
participants?: CreateParticipant[];
|
||||||
|
isDraft?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkflowRequest {
|
export interface UpdateWorkflowRequest {
|
||||||
@ -42,6 +43,7 @@ export interface UpdateWorkflowRequest {
|
|||||||
participants?: CreateParticipant[];
|
participants?: CreateParticipant[];
|
||||||
// Document updates (add new documents via multipart, delete via IDs)
|
// Document updates (add new documents via multipart, delete via IDs)
|
||||||
deleteDocumentIds?: string[];
|
deleteDocumentIds?: string[];
|
||||||
|
isDraft?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateApprovalLevel {
|
export interface CreateApprovalLevel {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const createWorkflowSchema = z.object({
|
|||||||
priorityUi: z.string().optional(),
|
priorityUi: z.string().optional(),
|
||||||
templateId: z.string().optional(),
|
templateId: z.string().optional(),
|
||||||
ccList: z.array(z.any()).optional(),
|
ccList: z.array(z.any()).optional(),
|
||||||
|
isDraft: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateWorkflowSchema = z.object({
|
export const updateWorkflowSchema = z.object({
|
||||||
@ -73,6 +74,7 @@ export const updateWorkflowSchema = z.object({
|
|||||||
notificationEnabled: z.boolean().optional(),
|
notificationEnabled: z.boolean().optional(),
|
||||||
})).optional(),
|
})).optional(),
|
||||||
deleteDocumentIds: z.array(z.string().uuid()).optional(),
|
deleteDocumentIds: z.array(z.string().uuid()).optional(),
|
||||||
|
isDraft: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper to validate UUID or requestNumber format
|
// Helper to validate UUID or requestNumber format
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user