418 lines
9.7 KiB
JavaScript
418 lines
9.7 KiB
JavaScript
/**
|
|
* Copyright (c) 2026 Niolla
|
|
* All rights reserved.
|
|
*
|
|
* This source code is proprietary and confidential.
|
|
* Unauthorized copying, modification, distribution, or use
|
|
* of this file, via any medium, is strictly prohibited.
|
|
*/
|
|
|
|
// app/controllers/generateDocument.controller.js
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const documentQueue = require("../queues/document.queue");
|
|
const { createDocumentData } = require("../utils/document.utill");
|
|
const {
|
|
generateId,
|
|
generateDocumentReferenceNo,
|
|
} = require("../utils/idGen.util");
|
|
|
|
const { GetObjectCommand, DeleteObjectCommand } = require("@aws-sdk/client-s3");
|
|
|
|
const {log} = require("../utils/consoleLog.utill");
|
|
|
|
const s3 = require("../config/s3.config");
|
|
const db = require("../models");
|
|
const Document = db.Document;
|
|
const DocumentType = db.DocumentType;
|
|
|
|
const normalizeDocumentData = (value) => {
|
|
if (!value) {
|
|
return {};
|
|
}
|
|
|
|
if (typeof value === "string") {
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Get available document types
|
|
*/
|
|
exports.getAvailableDocumentTypes = async (req, res) => {
|
|
try {
|
|
|
|
const doc_types = await DocumentType.findAll({
|
|
attributes: ["doc_type_name"],
|
|
});
|
|
|
|
const types = doc_types.map((t) => t.doc_type_name);
|
|
|
|
return res.json({
|
|
success: true,
|
|
availableDocumentTypes: types,
|
|
note: "Use these document type names in your requests (case-insensitive)",
|
|
});
|
|
|
|
} catch (error) {
|
|
log("Error fetching document types:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get Saved documents
|
|
exports.getSavedDocuments = async (req, res) => {
|
|
try {
|
|
const { documentType } = req.body;
|
|
|
|
if (!documentType) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "documentType query parameter is required",
|
|
});
|
|
}
|
|
|
|
// Fetch saved documents based on documentType
|
|
const savedDocuments = await Document.findAll({
|
|
where: { doc_type: documentType.toUpperCase() },
|
|
order: [["createdAt", "DESC"]],
|
|
exclude: ["data"],
|
|
});
|
|
|
|
// extract only necessary fields to return
|
|
const formattedDocuments = savedDocuments.map((doc) => ({
|
|
doc_id: doc.doc_id,
|
|
reference_no: doc.reference_no,
|
|
doc_type: doc.doc_type,
|
|
status: doc.status,
|
|
createdAt: doc.createdAt,
|
|
updatedAt: doc.updatedAt,
|
|
}));
|
|
|
|
return res.json({
|
|
success: true,
|
|
savedDocuments: formattedDocuments,
|
|
});
|
|
} catch (error) {
|
|
log("Error fetching saved documents:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get specific document data by doc_id
|
|
exports.getDocumentData = async (req, res) => {
|
|
try {
|
|
const { docId } = req.params;
|
|
|
|
if (!docId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "docId parameter is required",
|
|
});
|
|
}
|
|
|
|
const document = await Document.findOne({
|
|
where: { doc_id: docId },
|
|
});
|
|
|
|
if (!document) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Document not found",
|
|
});
|
|
}
|
|
|
|
return res.json({
|
|
success: true,
|
|
document: {
|
|
doc_id: document.doc_id,
|
|
reference_no: document.reference_no,
|
|
doc_type: document.doc_type,
|
|
data: document.data,
|
|
status: document.status,
|
|
createdAt: document.createdAt,
|
|
updatedAt: document.updatedAt,
|
|
},
|
|
});
|
|
|
|
} catch (error) {
|
|
log("Error fetching document data:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.generateReferenceNo = async (req, res) => {
|
|
try {
|
|
const { documentType } = req.params;
|
|
|
|
if (!documentType) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "documentType is required",
|
|
});
|
|
}
|
|
|
|
// Generate reference number
|
|
const reference_no = await generateDocumentReferenceNo(documentType);
|
|
|
|
return res.json({
|
|
success: true,
|
|
reference_no,
|
|
});
|
|
} catch (error) {
|
|
log("Error generating reference number:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
exports.generateDraftDocument = async (req, res) => {
|
|
try {
|
|
const { document } = req.body;
|
|
const documentData = normalizeDocumentData(req.body.documentData || req.body.data || req.body);
|
|
|
|
// Validation
|
|
if (!document || !documentData) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "document and documentData are required",
|
|
});
|
|
}
|
|
|
|
let documentDetails;
|
|
|
|
if (document !== "PRECOST") {
|
|
// Save document details before generating
|
|
documentDetails = await createDocumentData(
|
|
document,
|
|
documentData,
|
|
"DRAFT",
|
|
);
|
|
} else {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message:
|
|
"Invalid document type, This document type is not allowed to be generated as draft",
|
|
});
|
|
}
|
|
|
|
return res.status(201).json({
|
|
success: true,
|
|
message: "Draft document created successfully",
|
|
documentDetails,
|
|
});
|
|
} catch (error) {
|
|
log("Error generating draft document:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate document asynchronously
|
|
* Returns jobId immediately
|
|
*/
|
|
exports.generateDocument = async (req, res) => {
|
|
try {
|
|
const { document, documentType } = req.body;
|
|
const documentData = normalizeDocumentData(req.body.documentData || req.body.data || req.body);
|
|
|
|
// Validation
|
|
if (!document || !documentType || !documentData) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "document, documentType, and documentData are required",
|
|
});
|
|
}
|
|
|
|
console.log(
|
|
`📨 generateDocument request: document="${document}", documentType="${documentType}"`,
|
|
);
|
|
|
|
if (document !== "PRECOST") {
|
|
// Save document details before generating
|
|
// If doc_id exists, finalize (update existing); otherwise create as DRAFT
|
|
const status = documentData.doc_id ? "FINAL" : "DRAFT";
|
|
await createDocumentData(document, documentData, status);
|
|
}
|
|
|
|
// Add job to queue
|
|
const job = await documentQueue.add("generate-document", {
|
|
document,
|
|
documentType,
|
|
data: documentData,
|
|
});
|
|
|
|
log(
|
|
`📋 Document generation job queued: ${job.id} (document="${document}", type="${documentType}")`,
|
|
);
|
|
|
|
return res.status(202).json({
|
|
success: true,
|
|
message: "Document generation started",
|
|
jobId: job.id,
|
|
});
|
|
} catch (error) {
|
|
log("Error queuing document generation:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get job status and result
|
|
*/
|
|
exports.getJobStatus = async (req, res) => {
|
|
try {
|
|
const { jobId } = req.params;
|
|
|
|
if (!jobId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "jobId is required",
|
|
});
|
|
}
|
|
|
|
// Get job from queue
|
|
const job = await documentQueue.getJob(jobId);
|
|
|
|
if (!job) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Job not found",
|
|
});
|
|
}
|
|
|
|
// Get job state
|
|
const state = await job.getState();
|
|
const result = job.returnvalue;
|
|
const failedReason = job.failedReason;
|
|
|
|
return res.json({
|
|
success: true,
|
|
jobId: job.id,
|
|
state, // "waiting" | "active" | "completed" | "failed" | "delayed"
|
|
result: state === "completed" ? result : null,
|
|
error: state === "failed" ? failedReason : null,
|
|
attempts: job.attemptsMade,
|
|
stacktrace: job.stacktrace,
|
|
});
|
|
} catch (error) {
|
|
log("Error fetching job status:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Download generated document
|
|
*/
|
|
exports.downloadDocument = async (req, res) => {
|
|
try {
|
|
const { uuid } = req.params;
|
|
|
|
const key = `uploads/${uuid}.pdf`;
|
|
|
|
const command = new GetObjectCommand({
|
|
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
Key: key,
|
|
});
|
|
|
|
const response = await s3.send(command);
|
|
|
|
res.setHeader(
|
|
"Content-Type",
|
|
response.ContentType || "application/octet-stream",
|
|
);
|
|
|
|
res.setHeader("Content-Disposition", `attachment; filename="${uuid}.pdf"`);
|
|
|
|
response.Body.pipe(res);
|
|
|
|
res.on("finish", async () => {
|
|
try {
|
|
await s3.send(
|
|
new DeleteObjectCommand({
|
|
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
Key: key,
|
|
}),
|
|
);
|
|
|
|
log(`Deleted from S3: ${key}`);
|
|
} catch (err) {
|
|
log(`Failed to delete ${key}:`, err.message);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
log("Download error:", error.message);
|
|
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Document not found",
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Cancel/delete a job
|
|
*/
|
|
exports.cancelJob = async (req, res) => {
|
|
try {
|
|
const { jobId } = req.params;
|
|
|
|
if (!jobId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "jobId is required",
|
|
});
|
|
}
|
|
|
|
const job = await documentQueue.getJob(jobId);
|
|
|
|
if (!job) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Job not found",
|
|
});
|
|
}
|
|
|
|
await job.remove();
|
|
|
|
return res.json({
|
|
success: true,
|
|
message: "Job cancelled successfully",
|
|
jobId,
|
|
});
|
|
} catch (error) {
|
|
log("Error cancelling job:", error.message);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|