Compare commits
8 Commits
351e348af1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8df36bd3e2 | |||
| d7c19bbc5b | |||
|
|
f2d988681a | ||
|
|
5ad8079465 | ||
|
|
fc8332f25b | ||
|
|
dd07f09243 | ||
|
|
98e69ca5f0 | ||
|
|
20f0bb12fa |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ node_modules
|
|||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
/exthernal-rentroom-api
|
/exthernal-rentroom-api
|
||||||
|
uploads
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ export class accountingSearch {
|
|||||||
try {
|
try {
|
||||||
// let username = req.body.request.username;
|
// let username = req.body.request.username;
|
||||||
// let password = req.body.request.password;
|
// let password = req.body.request.password;
|
||||||
let token = req.body.request.token;
|
let token = req.headers.authorization?.split(' ')[1];''
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
let id = decoded.id
|
let id = decoded.id
|
||||||
let username = decoded.name
|
let username = decoded.name
|
||||||
database = decoded.organization
|
database = decoded.organization
|
||||||
|
|
||||||
aryResult = await this.accountingSearchService.getAccountingSearch(database, id, username); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
aryResult = await this.accountingSearchService.getAccountingSearch(database, id, username); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
||||||
// this.generalService.devhint(1, 'accountingSearch.js', 'Login success');
|
// this.generalService.devhint(1, 'accountingSearch.js', 'Login success');
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class accountingSetup {
|
|||||||
try {
|
try {
|
||||||
// let username = req.body.request.username;
|
// let username = req.body.request.username;
|
||||||
// let password = req.body.request.password;
|
// let password = req.body.request.password;
|
||||||
let token = req.body.request.token;
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
database = decoded.organization
|
database = decoded.organization
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class accountingSum {
|
|||||||
let result = []
|
let result = []
|
||||||
var aryResult
|
var aryResult
|
||||||
try {
|
try {
|
||||||
let token = req.body.request.token;
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
let id = decoded.id
|
let id = decoded.id
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class reportController {
|
|||||||
let token = req.headers.authorization?.split(' ')[1];
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
let actnum = req.body.request.actnum;
|
let actnum = decoded.id;
|
||||||
database = decoded.organization;
|
database = decoded.organization;
|
||||||
|
|
||||||
aryResult = await this.reportService.getReportController(database, actnum);
|
aryResult = await this.reportService.getReportController(database, actnum);
|
||||||
@@ -52,7 +52,7 @@ export class reportController {
|
|||||||
return aryResult;
|
return aryResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 2) แยก income / expense
|
// 2) แยก income / expense
|
||||||
const incomeList = data.filter(i => i.acttyp === 'i');
|
const incomeList = data.filter(i => i.acttyp === 'i');
|
||||||
const expenseList = data.filter(e => e.acttyp === 'e');
|
const expenseList = data.filter(e => e.acttyp === 'e');
|
||||||
|
|
||||||
@@ -63,11 +63,13 @@ export class reportController {
|
|||||||
const profitRate = totalIncome > 0 ? (netProfit / totalIncome) * 100 : 0;
|
const profitRate = totalIncome > 0 ? (netProfit / totalIncome) * 100 : 0;
|
||||||
const adjustedProfitRate = profitRate + 1.9;
|
const adjustedProfitRate = profitRate + 1.9;
|
||||||
|
|
||||||
// ✅ 3) แนบ summary (เหมือนที่เราทำไปก่อนหน้า)
|
// 3) แนบ summary (เหมือนที่เราทำไปก่อนหน้า)
|
||||||
var summary = {
|
var summary = {
|
||||||
totalIncome: totalIncome.toFixed(2),
|
totalIncome: totalIncome.toFixed(2),
|
||||||
totalExpense: totalExpense.toFixed(2),
|
totalExpense: totalExpense.toFixed(2),
|
||||||
netProfit: netProfit.toFixed(2),
|
netProfit: netProfit.toFixed(2),
|
||||||
|
profitRate: profitRate.toFixed(2) + ' %',
|
||||||
|
period: '30 วัน'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ 3.5) Create actdata table with required fields grouped by actnum
|
// ✅ 3.5) Create actdata table with required fields grouped by actnum
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class LoginService {
|
|||||||
this.generalService.devhint(2, 'loginservice.js', 'token generated successfully')
|
this.generalService.devhint(2, 'loginservice.js', 'token generated successfully')
|
||||||
|
|
||||||
|
|
||||||
delete user.usrseq
|
// delete user.usrseq
|
||||||
delete user.usrnam
|
delete user.usrnam
|
||||||
delete user.usrpwd
|
delete user.usrpwd
|
||||||
delete user.usrorg
|
delete user.usrorg
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { ProjectAddService } from '../services/projectAddService.js'
|
import { ProjectAddService } from '../services/projectAddService.js'
|
||||||
import { sendError } from '../utils/response.js'
|
import { sendError } from '../utils/response.js'
|
||||||
// import { OftenError } from '../utils/oftenError.js'
|
|
||||||
import { GeneralService } from '../share/generalservice.js';
|
import { GeneralService } from '../share/generalservice.js';
|
||||||
import { trim_all_array } from '../utils/trim.js'
|
import { trim_all_array } from '../utils/trim.js'
|
||||||
import { verifyToken, generateToken } from '../utils/token.js'
|
import { verifyToken, generateToken } from '../utils/token.js'
|
||||||
import { Interface } from '../interfaces/Interface.js';
|
import { Interface } from '../interfaces/Interface.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { getDTM } from '../utils/date.js';
|
||||||
|
|
||||||
|
|
||||||
export class projectAdd {
|
export class projectAdd {
|
||||||
@@ -17,6 +19,7 @@ export class projectAdd {
|
|||||||
|
|
||||||
async onNavigate(req, res) {
|
async onNavigate(req, res) {
|
||||||
this.generalService.devhint(1, 'projectAdd.js', 'onNavigate() start');
|
this.generalService.devhint(1, 'projectAdd.js', 'onNavigate() start');
|
||||||
|
|
||||||
let organization = req.body.organization;
|
let organization = req.body.organization;
|
||||||
const prommis = await this.onProjectAdd(req, res, organization);
|
const prommis = await this.onProjectAdd(req, res, organization);
|
||||||
return prommis;
|
return prommis;
|
||||||
@@ -30,63 +33,90 @@ export class projectAdd {
|
|||||||
let token = req.headers.authorization?.split(' ')[1];
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
// ✅ รองรับทั้ง JSON { request: {...} } และ Form-Data (Flat body)
|
const requestData = req.body;
|
||||||
const requestData = req.body.request ? req.body.request : req.body;
|
|
||||||
|
|
||||||
let name = requestData.prjnam;
|
let name = requestData.prjnam;
|
||||||
|
|
||||||
// Override Database จาก Token ตาม Pattern เดิม
|
|
||||||
database = decoded.organization || 'dbo';
|
database = decoded.organization || 'dbo';
|
||||||
|
|
||||||
aryResult = await this.projectAddService.getProjectAdd(database, name);
|
aryResult = await this.projectAddService.getProjectAdd(database, name);
|
||||||
latSeq = await this.projectAddService.getLatestProjectSeq(database);
|
latSeq = await this.projectAddService.getLatestProjectSeq(database);
|
||||||
// this.generalService.devhint(1, 'budgetSearch.js', 'Login success');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
idx = 1;
|
idx = 1;
|
||||||
console.error(error); // เพิ่ม log error เพื่อ debug
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
// if (!aryResult) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data');
|
|
||||||
|
|
||||||
if (aryResult == 0) {
|
if (aryResult == 0) {
|
||||||
// ส่ง latSeq เข้าไป (ต้อง handle กรณี null ถ้า table ว่าง)
|
|
||||||
const currentSeq = (latSeq && latSeq[0] && latSeq[0].prjseq) ? latSeq[0].prjseq : 0;
|
const currentSeq = (latSeq && latSeq[0] && latSeq[0].prjseq) ? latSeq[0].prjseq : 0;
|
||||||
let prommis = await this.makeArySave(req, currentSeq);
|
let prommis = await this.makeArySave(req, currentSeq);
|
||||||
return prommis
|
return prommis
|
||||||
} else {
|
} else {
|
||||||
|
if (req.files) {
|
||||||
|
req.files.forEach(f => {
|
||||||
|
if (fs.existsSync(f.path)) fs.unlinkSync(f.path);
|
||||||
|
});
|
||||||
|
}
|
||||||
return sendError('คีย์หลักซ้ำในระบบ', 'Duplicate Primary Key');
|
return sendError('คีย์หลักซ้ำในระบบ', 'Duplicate Primary Key');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async makeArySave(req, latseq) {
|
async makeArySave(req, latseq) {
|
||||||
// Extract Data ให้รองรับทั้ง 2 แบบ
|
const requestData = req.body;
|
||||||
const requestData = req.body.request ? req.body.request : req.body;
|
const nextSeq = latseq + 1;
|
||||||
|
|
||||||
// ✅ แก้ไข: Sanitise Input ป้องกัน Error numeric: ""
|
let prjwntbdg = requestData.prjwntbdg;
|
||||||
// ถ้าเป็นค่าว่าง ให้แปลงเป็น null หรือ 0.00 ตามประเภทข้อมูล
|
if (!prjwntbdg || prjwntbdg === '' || prjwntbdg === 'undefined' || prjwntbdg === 'null') {
|
||||||
const prjwntbdg = (requestData.prjwntbdg && requestData.prjwntbdg !== '') ? requestData.prjwntbdg : '0.00';
|
prjwntbdg = '0.00';
|
||||||
const prjusrseq = (requestData.prjusrseq && requestData.prjusrseq !== '') ? requestData.prjusrseq : null;
|
}
|
||||||
|
|
||||||
|
let prjusrseq = requestData.prjusrseq;
|
||||||
|
if (!prjusrseq || prjusrseq === '' || prjusrseq === 'undefined' || prjusrseq === 'null') {
|
||||||
|
prjusrseq = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typ = requestData.typ;
|
||||||
|
|
||||||
let arysave = {
|
let arysave = {
|
||||||
methods: 'post',
|
methods: 'post',
|
||||||
prjseq: latseq + 1,
|
prjseq: nextSeq,
|
||||||
prjnam: requestData.prjnam,
|
prjnam: requestData.prjnam,
|
||||||
prjusrseq: prjusrseq,
|
prjusrseq: prjusrseq,
|
||||||
prjwntbdg: prjwntbdg,
|
prjwntbdg: prjwntbdg,
|
||||||
prjacpbdg: '0.00',
|
prjacpbdg: '0.00',
|
||||||
prjbdgcod: '',
|
prjbdgcod: '',
|
||||||
prjcomstt: requestData.prjcomstt || 'UAC', // Default UAC ถ้ายิงมาแค่ชื่อกับงบ
|
prjcomstt: requestData.prjcomstt || 'UAC',
|
||||||
prjacpdtm: requestData.prjacpdtm || null
|
prjacpdtm: getDTM(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// เพิ่ม Logic จัดการไฟล์
|
let savedFileNames = [];
|
||||||
if (req.file) {
|
if (req.files && req.files.length > 0) {
|
||||||
arysave.prjdoc = req.file.filename // บันทึกชื่อไฟล์ลง DB
|
if (typ === 'prj') {
|
||||||
|
const targetDir = `uploads/projects/${nextSeq}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(targetDir)) {
|
||||||
|
fs.mkdirSync(targetDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
req.files.forEach(file => {
|
||||||
|
const targetPath = path.join(targetDir, file.filename);
|
||||||
|
fs.renameSync(file.path, targetPath);
|
||||||
|
savedFileNames.push(file.filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
arysave.prjdoc = savedFileNames.join(',');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error moving files:', err);
|
||||||
|
return sendError('ไม่สามารถบันทึกไฟล์ลงโฟลเดอร์โครงการได้');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arysave.prjdoc = req.files.map(f => f.filename).join(',');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// กรณี User Seq ไม่ได้ส่งมา (เป็น null) ให้ใช้จาก Token
|
|
||||||
if (!arysave.prjusrseq) {
|
if (!arysave.prjusrseq) {
|
||||||
const token = req.headers.authorization?.split(' ')[1];
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|||||||
114
exthernal-ttc-api/src/controllers/projectDownloadController.js
Normal file
114
exthernal-ttc-api/src/controllers/projectDownloadController.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export class projectDownload {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.generalService = new GeneralService();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onNavigate(req, res) {
|
||||||
|
this.generalService.devhint(1, 'projectDownload.js', 'onNavigate() start');
|
||||||
|
|
||||||
|
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, docType, database);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProjectDownload(req, res, prjseq, docType, database) {
|
||||||
|
try {
|
||||||
|
// ✅ 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]);
|
||||||
|
|
||||||
|
// ถ้าไม่เจอโครงการเลย
|
||||||
|
if (result.length === 0) {
|
||||||
|
return res.json(sendError('ไม่พบข้อมูลโครงการนี้ในระบบ', 'Project not found in DB', 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
archive.on('error', function(err) {
|
||||||
|
console.error('Archiver Error:', err);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).send({error: err.message});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
archive.pipe(res);
|
||||||
|
|
||||||
|
validFiles.forEach(filename => {
|
||||||
|
const filePath = path.join(folderPath, filename);
|
||||||
|
archive.file(filePath, { name: filename });
|
||||||
|
});
|
||||||
|
|
||||||
|
await archive.finalize();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download Controller Error:', error);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
return res.json(sendError('เกิดข้อผิดพลาดในการดาวน์โหลด', 'Download Error', 500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
exthernal-ttc-api/src/controllers/projectEditController.js
Normal file
128
exthernal-ttc-api/src/controllers/projectEditController.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { ProjectEditService } from '../services/projectEditService.js'
|
||||||
|
import { sendError } from '../utils/response.js'
|
||||||
|
import { GeneralService } from '../share/generalservice.js';
|
||||||
|
import { trim_all_array } from '../utils/trim.js'
|
||||||
|
import { verifyToken, generateToken } from '../utils/token.js'
|
||||||
|
import { Interface } from '../interfaces/Interface.js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { getDTM } from '../utils/date.js';
|
||||||
|
|
||||||
|
|
||||||
|
export class projectEdit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.generalService = new GeneralService();
|
||||||
|
this.Interface = new Interface();
|
||||||
|
this.projectEditService = new ProjectEditService();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onNavigate(req, res) {
|
||||||
|
this.generalService.devhint(1, 'projectAdd.js', 'onNavigate() start');
|
||||||
|
|
||||||
|
let organization = req.body.organization;
|
||||||
|
const prommis = await this.onProjectEdit(req, res, organization);
|
||||||
|
return prommis;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProjectEdit(req, res, database) {
|
||||||
|
let idx = -1
|
||||||
|
let aryResult = []
|
||||||
|
let latSeq = []
|
||||||
|
try {
|
||||||
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
|
const requestData = req.body;
|
||||||
|
let name = requestData.prjnam;
|
||||||
|
|
||||||
|
database = decoded.organization || 'dbo';
|
||||||
|
|
||||||
|
aryResult = await this.projectEditService.getProjectEdit(database, name);
|
||||||
|
latSeq = await this.projectEditService.getLatestProjectSeq(database);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
idx = 1;
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
|
|
||||||
|
if (aryResult == 0) {
|
||||||
|
const currentSeq = (latSeq && latSeq[0] && latSeq[0].prjseq) ? latSeq[0].prjseq : 0;
|
||||||
|
let prommis = await this.makeArySave(req, currentSeq);
|
||||||
|
return prommis
|
||||||
|
} else {
|
||||||
|
if (req.files) {
|
||||||
|
req.files.forEach(f => {
|
||||||
|
if (fs.existsSync(f.path)) fs.unlinkSync(f.path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sendError('คีย์หลักซ้ำในระบบ', 'Duplicate Primary Key');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeArySave(req, latseq) {
|
||||||
|
const requestData = req.body;
|
||||||
|
const nextSeq = latseq + 1;
|
||||||
|
|
||||||
|
let prjwntbdg = requestData.prjwntbdg;
|
||||||
|
if (!prjwntbdg || prjwntbdg === '' || prjwntbdg === 'undefined' || prjwntbdg === 'null') {
|
||||||
|
prjwntbdg = '0.00';
|
||||||
|
}
|
||||||
|
|
||||||
|
let prjusrseq = requestData.prjusrseq;
|
||||||
|
if (!prjusrseq || prjusrseq === '' || prjusrseq === 'undefined' || prjusrseq === 'null') {
|
||||||
|
prjusrseq = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typ = requestData.typ;
|
||||||
|
|
||||||
|
let arysave = {
|
||||||
|
methods: 'post',
|
||||||
|
prjseq: nextSeq,
|
||||||
|
prjnam: requestData.prjnam,
|
||||||
|
prjusrseq: prjusrseq,
|
||||||
|
prjwntbdg: prjwntbdg,
|
||||||
|
prjacpbdg: '0.00',
|
||||||
|
prjbdgcod: '',
|
||||||
|
prjcomstt: requestData.prjcomstt || 'UAC',
|
||||||
|
prjacpdtm: getDTM(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let savedFileNames = [];
|
||||||
|
if (req.files && req.files.length > 0) {
|
||||||
|
if (typ === 'prj') {
|
||||||
|
const targetDir = `uploads/projects/${nextSeq}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(targetDir)) {
|
||||||
|
fs.mkdirSync(targetDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
req.files.forEach(file => {
|
||||||
|
const targetPath = path.join(targetDir, file.filename);
|
||||||
|
fs.renameSync(file.path, targetPath);
|
||||||
|
savedFileNames.push(file.filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
arysave.prjdoc = savedFileNames.join(',');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error moving files:', err);
|
||||||
|
return sendError('ไม่สามารถบันทึกไฟล์ลงโฟลเดอร์โครงการได้');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arysave.prjdoc = req.files.map(f => f.filename).join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!arysave.prjusrseq) {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
const decoded = verifyToken(token);
|
||||||
|
if (decoded) arysave.prjusrseq = decoded.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Interface.saveInterface('prjmst', arysave, req);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,36 +23,42 @@ export class projectSearch {
|
|||||||
let idx = -1
|
let idx = -1
|
||||||
let aryResult = []
|
let aryResult = []
|
||||||
let condition = {}
|
let condition = {}
|
||||||
|
let column = ""
|
||||||
try {
|
try {
|
||||||
let token = req.headers.authorization?.split(' ')[1];
|
let token = req.headers.authorization?.split(' ')[1];
|
||||||
const decoded = verifyToken(token);
|
const decoded = verifyToken(token);
|
||||||
|
|
||||||
// ใช้ Organization จาก Token ถ้ามี
|
// ใช้ Organization จาก Token ถ้ามี
|
||||||
database = decoded.organization || database
|
database = decoded.organization || database
|
||||||
|
|
||||||
let columnParams = req.query.column
|
let columnParams = req.query.column
|
||||||
|
condition['prjseq'] = req.body.request.prjseq;
|
||||||
|
|
||||||
if (columnParams == 'user') {
|
|
||||||
let column = `prjnam, prjwntbdg, prjcomstt`
|
|
||||||
condition['prjusrseq'] = req.body.request.prjusrseq || decoded.id
|
|
||||||
// เรียก Service ตัวเดิม (Simple)
|
|
||||||
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);
|
|
||||||
|
|
||||||
} else if (columnParams == 'edit') {
|
switch (columnParams) {
|
||||||
let column = `prjnam, prjwntbdg, prjcomstt`
|
case "user":
|
||||||
condition['prjseq'] = req.body.request.prjseq
|
column = `prjseq, prjnam, prjwntbdg, prjcomstt, prjacpdtm,
|
||||||
|
(
|
||||||
// เรียก Service ตัวเดิม (Simple)
|
SELECT trnacpdtm
|
||||||
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);
|
FROM ${database}.trnmst t
|
||||||
|
WHERE trnprjseq = prjseq
|
||||||
|
ORDER BY trnacpdtm DESC
|
||||||
|
LIMIT 1
|
||||||
|
) as trnacpdtm`
|
||||||
|
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);
|
||||||
|
break;
|
||||||
|
|
||||||
} else if (columnParams == 'result' || columnParams == undefined || columnParams == '') {
|
case "edit":
|
||||||
|
column = `prjseq, prjnam, prjwntbdg, prjcomstt`
|
||||||
// กำหนดเงื่อนไข (ถ้ามีส่งมา)
|
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);
|
||||||
condition['prjseq'] = req.body.request.prjseq;
|
break;
|
||||||
|
|
||||||
// สร้าง Column String ที่มี Subquery ดึงงบจาก trnmst
|
case "useredit":
|
||||||
let column = `
|
column = `prjseq, prjnam, prjwntbdg, prjdoc`
|
||||||
|
aryResult = await this.projectSearchService.getProjectSearch(database, column, condition);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
column = `
|
||||||
prjseq,
|
prjseq,
|
||||||
prjnam,
|
prjnam,
|
||||||
usrthinam as prjusrnam,
|
usrthinam as prjusrnam,
|
||||||
@@ -76,15 +82,14 @@ export class projectSearch {
|
|||||||
prjcomstt,
|
prjcomstt,
|
||||||
prjacpdtm
|
prjacpdtm
|
||||||
`;
|
`;
|
||||||
|
aryResult = await this.projectSearchService.getProjectDetailSearch(database, column, condition);
|
||||||
// ใช้ Service ตัวใหม่ (Detail Search / Join)
|
break;
|
||||||
aryResult = await this.projectSearchService.getProjectDetailSearch(database, column, condition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
idx = 1;
|
idx = 1;
|
||||||
} finally {
|
} finally {
|
||||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
if (!aryResult || aryResult.length === 0) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data');
|
if (!aryResult || aryResult.length === 0) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data');
|
||||||
return aryResult
|
return aryResult
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import multer from 'multer'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { sendError } from '../utils/response.js'
|
import { sendError } from '../utils/response.js'
|
||||||
|
import { getDTM } from '../utils/date.js'
|
||||||
|
|
||||||
const tempDir = 'uploads/temp'
|
const tempDir = 'uploads/temp'
|
||||||
if (!fs.existsSync(tempDir)) {
|
if (!fs.existsSync(tempDir)) {
|
||||||
@@ -13,8 +14,21 @@ const storage = multer.diskStorage({
|
|||||||
cb(null, tempDir)
|
cb(null, tempDir)
|
||||||
},
|
},
|
||||||
filename: function (req, file, cb) {
|
filename: function (req, file, cb) {
|
||||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
|
// ดึงนามสกุลไฟล์
|
||||||
cb(null, uniqueSuffix + path.extname(file.originalname))
|
const ext = path.extname(file.originalname);
|
||||||
|
|
||||||
|
// ดึงชื่อไฟล์เดิม (ตัดนามสกุลออก)
|
||||||
|
const originalName = path.basename(file.originalname, ext);
|
||||||
|
|
||||||
|
// Clean ชื่อไฟล์: เปลี่ยน space เป็น _, ลบอักขระพิเศษ, เหลือแค่ภาษาอังกฤษ ตัวเลข และ - _
|
||||||
|
const cleanName = originalName.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 100);
|
||||||
|
|
||||||
|
// Format: YYYYMMDDHHmm-Random-CleanName.ext
|
||||||
|
// ตัวอย่าง: 202511300930-1234-System_Req.docx
|
||||||
|
const dtm = getDTM();
|
||||||
|
const random = Math.round(Math.random() * 1E4);
|
||||||
|
|
||||||
|
cb(null, `${dtm}-${random}-${cleanName}${ext}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -59,20 +73,16 @@ function verifyFileSignature(filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const uploadMiddleware = (req, res, next) => {
|
export const uploadMiddleware = (req, res, next) => {
|
||||||
// เปลี่ยนเป็น .array() เพื่อรับหลายไฟล์ (รองรับสูงสุด 10 ไฟล์ต่อครั้ง)
|
|
||||||
const uploadHandler = upload.array('prjdoc', 10)
|
const uploadHandler = upload.array('prjdoc', 10)
|
||||||
|
|
||||||
uploadHandler(req, res, (err) => {
|
uploadHandler(req, res, (err) => {
|
||||||
if (err) return res.status(400).json(sendError(err.message))
|
if (err) return res.status(400).json(sendError(err.message))
|
||||||
|
|
||||||
// ถ้าไม่มีไฟล์ ข้ามไป
|
|
||||||
if (!req.files || req.files.length === 0) return next()
|
if (!req.files || req.files.length === 0) return next()
|
||||||
|
|
||||||
// Loop ตรวจสอบ Signature ทุกไฟล์
|
|
||||||
for (const file of req.files) {
|
for (const file of req.files) {
|
||||||
const isSafe = verifyFileSignature(file.path)
|
const isSafe = verifyFileSignature(file.path)
|
||||||
if (!isSafe) {
|
if (!isSafe) {
|
||||||
// ลบไฟล์ทั้งหมดทิ้งทันทีถ้าเจอไฟล์อันตรายแม้แต่ไฟล์เดียว
|
|
||||||
req.files.forEach(f => {
|
req.files.forEach(f => {
|
||||||
if (fs.existsSync(f.path)) fs.unlinkSync(f.path)
|
if (fs.existsSync(f.path)) fs.unlinkSync(f.path)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
// import { budgetSetup } from '../controllers/budgetSetupController.js'
|
|
||||||
import { budgetSearch } from '../controllers/budgetSearchController.js'
|
import { budgetSearch } from '../controllers/budgetSearchController.js'
|
||||||
import { budgetAdd } from '../controllers/budgetAddController.js'
|
import { budgetAdd } from '../controllers/budgetAddController.js'
|
||||||
import { projectSearch } from '../controllers/projectSearchController.js'
|
import { projectSearch } from '../controllers/projectSearchController.js'
|
||||||
@@ -7,10 +6,8 @@ import { projectAdd } from '../controllers/projectAddController.js'
|
|||||||
import { BudgetExpenseController } from '../controllers/budgetExpenseController.js'
|
import { BudgetExpenseController } from '../controllers/budgetExpenseController.js'
|
||||||
import { reportController } from '../controllers/reportController.js'
|
import { reportController } from '../controllers/reportController.js'
|
||||||
import { transactionSearch } from '../controllers/transactionSearchController.js'
|
import { transactionSearch } from '../controllers/transactionSearchController.js'
|
||||||
import { uploadMiddleware } from '../middlewares/uploadMiddleware.js' // ✅ Import แบบ Named Export
|
import { uploadMiddleware } from '../middlewares/uploadMiddleware.js'
|
||||||
|
import { projectDownload } from '../controllers/projectDownloadController.js' // ✅ Import
|
||||||
// import { authMiddleware } from '../middlewares/auth.js'
|
|
||||||
// import { sendResponse } from '../utils/response.js'
|
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const controller_projectSearch_post = new projectSearch()
|
const controller_projectSearch_post = new projectSearch()
|
||||||
@@ -20,11 +17,7 @@ const controller_budgetSetup_post = new BudgetExpenseController()
|
|||||||
const controller_report_post = new reportController()
|
const controller_report_post = new reportController()
|
||||||
const controller_projectAdd_post = new projectAdd()
|
const controller_projectAdd_post = new projectAdd()
|
||||||
const controller_transactionSearch_post = new transactionSearch()
|
const controller_transactionSearch_post = new transactionSearch()
|
||||||
|
const controller_projectDownload_get = new projectDownload()
|
||||||
// router.post('/budgetSetup', async (req, res) => {
|
|
||||||
// const result = await controller_budgetSetup_post.onNavigate(req, res)
|
|
||||||
// if (result) return res.json(result)
|
|
||||||
// })
|
|
||||||
|
|
||||||
router.post('/budgetadd', async (req, res) => {
|
router.post('/budgetadd', async (req, res) => {
|
||||||
const result = await controller_budgetAdd_post.onNavigate(req, res)
|
const result = await controller_budgetAdd_post.onNavigate(req, res)
|
||||||
@@ -41,12 +34,21 @@ router.post('/projectsearch', async (req, res) => {
|
|||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ใช้ uploadMiddleware แทน upload.single (เพราะ Wrapper จัดการให้แล้ว)
|
|
||||||
router.post('/projectadd', uploadMiddleware, async (req, res) => {
|
router.post('/projectadd', uploadMiddleware, async (req, res) => {
|
||||||
const result = await controller_projectAdd_post.onNavigate(req, res)
|
const result = await controller_projectAdd_post.onNavigate(req, res)
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.post('/projectedit', uploadMiddleware, async (req, res) => {
|
||||||
|
const result = await controller_projectAdd_post.onNavigate(req, res)
|
||||||
|
if (result) return res.json(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/projectdownload', async (req, res) => {
|
||||||
|
// ไม่ต้อง return res.json() เพราะ Controller จัดการ Stream แล้ว
|
||||||
|
await controller_projectDownload_get.onNavigate(req, res)
|
||||||
|
})
|
||||||
|
|
||||||
router.post('/transactionsearch', async (req, res) => {
|
router.post('/transactionsearch', async (req, res) => {
|
||||||
const result = await controller_transactionSearch_post.onNavigate(req, res)
|
const result = await controller_transactionSearch_post.onNavigate(req, res)
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
|
|||||||
@@ -120,11 +120,10 @@ export class BudgetExpenseService {
|
|||||||
const sqlUpdatePrj = `
|
const sqlUpdatePrj = `
|
||||||
UPDATE ${database}.prjmst
|
UPDATE ${database}.prjmst
|
||||||
SET prjacpbdg = $1,
|
SET prjacpbdg = $1,
|
||||||
prjcomstt = $2,
|
prjcomstt = $2
|
||||||
prjacpdtm = $3
|
WHERE prjseq = $3
|
||||||
WHERE prjseq = $4
|
|
||||||
`;
|
`;
|
||||||
await client.query(sqlUpdatePrj, [formattedTotal, projectStatus, currentDTM, projectSeq]);
|
await client.query(sqlUpdatePrj, [formattedTotal, projectStatus, projectSeq]);
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
return {
|
return {
|
||||||
|
|||||||
34
exthernal-ttc-api/src/services/projectEditService.js
Normal file
34
exthernal-ttc-api/src/services/projectEditService.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { GeneralService } from '../share/generalservice.js'
|
||||||
|
|
||||||
|
export class ProjectEditService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.generalService = new GeneralService()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProjectEdit(database, name) {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
prjseq,
|
||||||
|
prjnam
|
||||||
|
FROM ${database}.prjmst
|
||||||
|
WHERE prjnam = $1
|
||||||
|
`
|
||||||
|
const params = [name]
|
||||||
|
const result = await this.generalService.executeQueryParam(database, sql, params);
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestProjectSeq(database) {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
prjseq
|
||||||
|
FROM ${database}.prjmst
|
||||||
|
WHERE prjseq=(SELECT max(prjseq) FROM ${database}.prjmst)
|
||||||
|
`
|
||||||
|
|
||||||
|
const params = []
|
||||||
|
const result = await this.generalService.executeQueryParam(database, sql, params);
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ export class ProjectSearchService {
|
|||||||
const sql = `
|
const sql = `
|
||||||
SELECT ${selectCol}
|
SELECT ${selectCol}
|
||||||
FROM ${database}.prjmst p
|
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
|
LEFT JOIN ${database}.bdgmst b ON prjbdgcod = bdgcod
|
||||||
WHERE 1=1
|
WHERE 1=1
|
||||||
ORDER BY prjseq ASC
|
ORDER BY prjseq ASC
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user