.
All checks were successful
Build Docker Image / Build Docker Image (push) Successful in 1m15s
Build Docker Image / Restart Docker Compose (push) Successful in 1s

This commit is contained in:
x2Skyz
2025-11-30 23:47:47 +07:00
parent fc8332f25b
commit 5ad8079465
4 changed files with 81 additions and 35 deletions

View File

@@ -1,8 +1,9 @@
import { GeneralService } from '../share/generalservice.js';
import { sendError } from '../utils/response.js';
import { verifyToken } from '../utils/token.js'; // ✅ เพิ่ม verifyToken
import fs from 'fs';
import path from 'path';
import archiver from 'archiver'; // ⚠️ ต้อง npm install archiver
import archiver from 'archiver';
export class projectDownload {
@@ -10,65 +11,104 @@ export class projectDownload {
this.generalService = new GeneralService();
}
// GET Route ไม่รับ Body ดังนั้นรับ Parameter จาก Query String หรือ Params
async onNavigate(req, res) {
this.generalService.devhint(1, 'projectDownload.js', 'onNavigate() start');
// รับ prjseq จาก Query String (เช่น /projectdownload?prjseq=123)
const prjseq = req.query.prjseq;
const docType = req.query.docType;
// ✅ 1. แกะ Token เพื่อหา Database Schema (เพราะต้อง Query DB)
let token = req.headers.authorization?.split(' ')[1];
let database = 'dbo'; // Default
if (token) {
const decoded = verifyToken(token);
if (decoded && decoded.organization) {
database = decoded.organization;
}
}
if (!prjseq) {
return res.status(400).json(sendError('กรุณาระบุ prjseq', 'Missing prjseq parameter'));
}
return await this.onProjectDownload(req, res, prjseq);
return await this.onProjectDownload(req, res, prjseq, docType, database);
}
async onProjectDownload(req, res, prjseq) {
async onProjectDownload(req, res, prjseq, docType, database) {
try {
const folderPath = `uploads/projects/${prjseq}`;
// ✅ 2. Query เช็คค่า prjdoc ใน Database ก่อน (Source of Truth)
const sql = `SELECT prjdoc FROM ${database}.prjmst WHERE prjseq = $1`;
const result = await this.generalService.executeQueryParam(database, sql, [prjseq]);
// 1. ตรวจสอบว่า Folder มีอยู่จริงหรือไม่
if (!fs.existsSync(folderPath)) {
return res.status(404).json(sendError('ไม่พบเอกสารของโครงการนี้', 'Project documents not found'));
// ถ้าไม่เจอโครงการเลย
if (result.length === 0) {
return res.json(sendError('ไม่พบข้อมูลโครงการนี้ในระบบ', 'Project not found in DB', 404));
}
// 2. ตั้งค่า Response Header ให้เป็นไฟล์ Zip
const archive = archiver('zip', {
zlib: { level: 9 } // บีบอัดสูงสุด
const prjdoc = result[0].prjdoc;
// ✅ 3. เช็คว่า prjdoc ว่างหรือไม่? (null, undefined, หรือ string ว่าง)
if (!prjdoc || prjdoc.trim() === '') {
return res.json(sendError('ไม่พบเอกสารแนบในระบบ (prjdoc ว่าง)', 'No documents recorded in database', 404));
}
// ✅ 4. แปลงรายชื่อไฟล์จาก DB (Comma Separated) เป็น Array
// ตัวอย่าง: "file1.jpg,file2.pdf" -> ["file1.jpg", "file2.pdf"]
let dbFiles = prjdoc.split(',').map(f => f.trim()).filter(f => f !== '');
const folderPath = `uploads/projects/${prjseq}`;
// เช็คว่ามีโฟลเดอร์จริงไหม
if (!fs.existsSync(folderPath)) {
return res.json(sendError('ไม่พบไฟล์ใน Server (โฟลเดอร์สูญหาย)', 'Project folder missing on server', 404));
}
// ✅ 5. กรองไฟล์: ต้องมีชื่อใน DB **และ** มีไฟล์อยู่จริงบน Disk
// (และผ่าน filter docType ถ้ามี)
let validFiles = dbFiles.filter(filename => {
// Filter docType (ถ้าส่งมา)
if (docType && !filename.toLowerCase().endsWith(`.${docType.toLowerCase()}`)) {
return false;
}
// เช็คว่าไฟล์มีอยู่จริงไหม
const fullPath = path.join(folderPath, filename);
return fs.existsSync(fullPath);
});
// ถ้ากรองแล้วไม่เหลือไฟล์เลย
if (validFiles.length === 0) {
return res.json(sendError('ไม่พบไฟล์เอกสารที่สามารถดาวน์โหลดได้', 'No valid files found', 404));
}
// --- เริ่มกระบวนการ Zip ---
const archive = archiver('zip', { zlib: { level: 9 } });
const zipFilename = `project_${prjseq}_documents.zip`;
res.attachment(zipFilename); // บอก Browser ว่าให้ Download เป็นชื่อนี้
res.attachment(zipFilename);
// 3. Handle Events
archive.on('error', function(err) {
console.error('Zip Error:', err);
res.status(500).send({error: err.message});
console.error('Archiver Error:', err);
if (!res.headersSent) {
res.status(500).send({error: err.message});
}
});
// เมื่อบีบอัดเสร็จและส่งข้อมูลครบ
archive.on('end', function() {
console.log(`Archive wrote ${archive.pointer()} bytes`);
});
// 4. Pipe Archive -> Response (Stream)
archive.pipe(res);
// 5. เอาไฟล์ใน Folder ใส่ Zip
// name: false หมายถึงเอาไฟล์ใส่ root ของ zip เลย ไม่ต้องซ้อน folder
archive.directory(folderPath, false);
validFiles.forEach(filename => {
const filePath = path.join(folderPath, filename);
archive.file(filePath, { name: filename });
});
// 6. สั่งจบการบีบอัด (จะเริ่มส่งข้อมูลทันที)
await archive.finalize();
// ⚠️ หมายเหตุ: เราไม่ return อะไรกลับไป เพราะเรา Pipe Stream ใส่ res โดยตรงแล้ว
// globalResponseHandler จะไม่ทำงานเพราะเราไม่ได้ res.json() ซึ่งถูกต้องแล้วสำหรับการโหลดไฟล์
} catch (error) {
console.error('Download Controller Error:', error);
return res.status(500).json(sendError('เกิดข้อผิดพลาดในการดาวน์โหลด', 'Download Error'));
if (!res.headersSent) {
return res.json(sendError('เกิดข้อผิดพลาดในการดาวน์โหลด', 'Download Error', 500));
}
}
}
}

View File

@@ -34,7 +34,14 @@ export class projectSearch {
if (columnParams == 'user') {
let column = `prjseq, prjnam, prjwntbdg, prjcomstt`
let column = `prjseq, prjnam, prjwntbdg, prjcomstt, prjacpdtm,
(
SELECT trnacpdtm
FROM ${database}.trnmst t
WHERE trnprjseq = prjseq
ORDER BY trnacpdtm DESC
LIMIT 1
) as trnacpdtm`
condition['prjusrseq'] = req.body.request.prjusrseq || decoded.id
// เรียก Service ตัวเดิม (Simple)
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);

View File

@@ -121,10 +121,9 @@ export class BudgetExpenseService {
UPDATE ${database}.prjmst
SET prjacpbdg = $1,
prjcomstt = $2,
prjacpdtm = $3
WHERE prjseq = $4
WHERE prjseq = $3
`;
await client.query(sqlUpdatePrj, [formattedTotal, projectStatus, currentDTM, projectSeq]);
await client.query(sqlUpdatePrj, [formattedTotal, projectStatus, projectSeq]);
await client.query('COMMIT');
return {

View File

@@ -34,7 +34,7 @@ export class ProjectSearchService {
const sql = `
SELECT ${selectCol}
FROM ${database}.prjmst p
LEFT JOIN ${database}.usrmst u ON prjusrseq = usrseq
LEFT JOIN nuttakit.usrmst u ON prjusrseq = usrseq
LEFT JOIN ${database}.bdgmst b ON prjbdgcod = bdgcod
WHERE 1=1
ORDER BY prjseq ASC