From 5ad80794652296ea8b9f564e990dd4f04e702d0f Mon Sep 17 00:00:00 2001 From: x2Skyz Date: Sun, 30 Nov 2025 23:47:47 +0700 Subject: [PATCH] . --- .../controllers/projectDownloadController.js | 100 ++++++++++++------ .../controllers/projectSearchController.js | 9 +- .../src/services/budgetExpenseService.js | 5 +- .../src/services/projectSearchService.js | 2 +- 4 files changed, 81 insertions(+), 35 deletions(-) diff --git a/exthernal-ttc-api/src/controllers/projectDownloadController.js b/exthernal-ttc-api/src/controllers/projectDownloadController.js index b59998f..541a055 100644 --- a/exthernal-ttc-api/src/controllers/projectDownloadController.js +++ b/exthernal-ttc-api/src/controllers/projectDownloadController.js @@ -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)); + } } } } \ No newline at end of file diff --git a/exthernal-ttc-api/src/controllers/projectSearchController.js b/exthernal-ttc-api/src/controllers/projectSearchController.js index f41f4fe..25f1ac2 100644 --- a/exthernal-ttc-api/src/controllers/projectSearchController.js +++ b/exthernal-ttc-api/src/controllers/projectSearchController.js @@ -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); diff --git a/exthernal-ttc-api/src/services/budgetExpenseService.js b/exthernal-ttc-api/src/services/budgetExpenseService.js index bb8ee77..041e5fb 100644 --- a/exthernal-ttc-api/src/services/budgetExpenseService.js +++ b/exthernal-ttc-api/src/services/budgetExpenseService.js @@ -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 { diff --git a/exthernal-ttc-api/src/services/projectSearchService.js b/exthernal-ttc-api/src/services/projectSearchService.js index ef275b1..ec173b5 100644 --- a/exthernal-ttc-api/src/services/projectSearchService.js +++ b/exthernal-ttc-api/src/services/projectSearchService.js @@ -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