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};
|
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-DX2Gwh6C.js.map
|
//# 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;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</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/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-CNlPctO6.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-B-mLDzJe.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -3,16 +3,6 @@ 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),
|
||||||
@ -20,7 +10,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,
|
logging: false, // Disable SQL query logging for cleaner console output
|
||||||
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),
|
||||||
@ -28,8 +18,7 @@ const sequelize = new Sequelize({
|
|||||||
idle: 10000,
|
idle: 10000,
|
||||||
},
|
},
|
||||||
dialectOptions: {
|
dialectOptions: {
|
||||||
// 3. Use the robust boolean we calculated above
|
ssl: process.env.DB_SSL === 'true' ? {
|
||||||
ssl: isSSL ? {
|
|
||||||
require: true,
|
require: true,
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
} : false,
|
} : false,
|
||||||
|
|||||||
@ -236,7 +236,6 @@ 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);
|
||||||
@ -683,10 +682,7 @@ export class WorkflowController {
|
|||||||
}
|
}
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
const validated = validateUpdateWorkflow(parsed);
|
const validated = validateUpdateWorkflow(parsed);
|
||||||
const updateData: UpdateWorkflowRequest = {
|
const updateData: UpdateWorkflowRequest = { ...validated } as any;
|
||||||
...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,7 +36,6 @@ 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 {
|
||||||
@ -65,7 +64,6 @@ 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,9 +2450,6 @@ 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,
|
||||||
@ -2464,10 +2461,9 @@ export class WorkflowService {
|
|||||||
currentLevel: 1,
|
currentLevel: 1,
|
||||||
totalLevels: workflowData.approvalLevels.length,
|
totalLevels: workflowData.approvalLevels.length,
|
||||||
totalTatHours,
|
totalTatHours,
|
||||||
status: initialStatus,
|
status: WorkflowStatus.DRAFT,
|
||||||
isDraft: isDraftRequested,
|
isDraft: true,
|
||||||
isDeleted: false,
|
isDeleted: false
|
||||||
submissionDate: isDraftRequested ? undefined : now
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create approval levels
|
// Create approval levels
|
||||||
@ -2553,18 +2549,15 @@ 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: isDraftRequested ? 'Draft request created' : 'Initial request submitted',
|
action: 'Initial request submitted',
|
||||||
details: isDraftRequested
|
details: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
|
||||||
? `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
|
||||||
});
|
});
|
||||||
|
|
||||||
// If not a draft, initiate the workflow (approvals, notifications, etc.)
|
// NOTE: Notifications are NOT sent here because workflows are created as DRAFTS
|
||||||
if (!isDraftRequested) {
|
// Notifications will be sent in submitWorkflow() when the draft is actually submitted
|
||||||
return await this.internalSubmitWorkflow(workflow, now);
|
// This prevents approvers from being notified about draft requests
|
||||||
}
|
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -3119,9 +3112,6 @@ 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;
|
||||||
@ -3129,13 +3119,6 @@ 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);
|
||||||
|
|
||||||
@ -3284,13 +3267,8 @@ 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)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If transitioning, call the internal submission logic (notifications, TAT, etc.)
|
// Reload the workflow instance to get latest data (without associations to avoid the error)
|
||||||
if (isTransitioningToSubmitted) {
|
// The associations issue occurs when trying to include them, so we skip that
|
||||||
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) {
|
||||||
@ -3312,24 +3290,12 @@ 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,
|
||||||
@ -3474,7 +3440,10 @@ 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,7 +29,6 @@ export interface CreateWorkflowRequest {
|
|||||||
priority: Priority;
|
priority: Priority;
|
||||||
approvalLevels: CreateApprovalLevel[];
|
approvalLevels: CreateApprovalLevel[];
|
||||||
participants?: CreateParticipant[];
|
participants?: CreateParticipant[];
|
||||||
isDraft?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkflowRequest {
|
export interface UpdateWorkflowRequest {
|
||||||
@ -43,7 +42,6 @@ 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,7 +44,6 @@ 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({
|
||||||
@ -74,7 +73,6 @@ 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