diff --git a/@knowleadge/dbchange/howtoalter table.txt b/@knowleadge/dbchange/howtoalter table.txt new file mode 100644 index 0000000..71ad8f6 --- /dev/null +++ b/@knowleadge/dbchange/howtoalter table.txt @@ -0,0 +1,23 @@ +BEGIN; + +-- 1. ย้ายตารางเก่าหลบไปก่อน (Backup ไว้ในตัว) +ALTER TABLE users RENAME TO users_old; + +-- 2. สร้างตารางใหม่ (ด้วยโครงสร้างที่ต้องการแก้ไข) +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL, + -- ใส่ Column ใหม่ หรือแก้ไข Type ตรงนี้ + new_column_data JSONB +); + +-- 3. ย้ายข้อมูลจากตารางเก่ามาใส่ตารางใหม่ +INSERT INTO users (id, username, new_column_data) +SELECT id, username, CAST(old_data AS JSONB) -- แปลงข้อมูลระหว่างทางได้เลย +FROM users_old; + +-- 4. สร้าง Index และ Constraint ให้ครบ (สำคัญมาก!) +CREATE INDEX idx_users_username ON users(username); +-- อย่าลืม Add Foreign Key หรือ Grant Permission ให้ User อื่นด้วย + +COMMIT; \ No newline at end of file diff --git a/exthernal-ttc-api/src/controllers/budgetExpenseController.js b/exthernal-ttc-api/src/controllers/budgetExpenseController.js index 9580876..89cf8fe 100644 --- a/exthernal-ttc-api/src/controllers/budgetExpenseController.js +++ b/exthernal-ttc-api/src/controllers/budgetExpenseController.js @@ -1,70 +1,85 @@ -import { BudgetExpenseService } from '../services/budgetExpenseService.js' -import { ProjectSearchService } from '../services/projectSearchService.js' -import { sendError } from '../utils/response.js' -// import { OftenError } from '../utils/oftenError.js' +import { BudgetExpenseService } from '../services/budgetexpenseservice.js'; +import { ProjectSearchService } from '../services/projectSearchService.js'; +import { sendError, formatSuccessResponse } 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 { verifyToken } from '../utils/token.js'; +export class BudgetExpenseController { + + constructor() { + this.generalService = new GeneralService(); + this.budgetExpenseService = new BudgetExpenseService(); + this.projectSearchService = new ProjectSearchService(); + } -export class budgetExpense { + async onNavigate(req, res) { + this.generalService.devhint(1, 'budgetexpensecontroller.js', 'onNavigate() start'); + + // Logic เดิม: รับ organization จาก body แต่เดี๋ยวจะถูก override ด้วย token ใน onBudgetExpense + let organization = req.body.organization || 'dbo'; + const result = await this.onBudgetExpense(req, res, organization); + return result; + } - constructor() { - this.generalService = new GeneralService(); - this.Interface = new Interface(); - this.budgetExpenseService = new BudgetExpenseService(); - this.projectSearchService = new ProjectSearchService(); - } + async onBudgetExpense(req, res, database) { + let idx = -1; + let aryResult = []; + let condition = {}; + + try { + // 1. แกะ Token เพื่อหา Organization + let token = req.headers.authorization?.split(' ')[1]; + if(!token) return sendError('ไม่พบ Token', 'Missing Token'); - async onNavigate(req, res) { - this.generalService.devhint(1, 'budgetExpense.js', 'onNavigate() start'); - let organization = req.body.organization; - const prommis = await this.onBudgetExpense(req, res, organization); - return prommis; - } + const decoded = verifyToken(token); + if(!decoded) return sendError('Token ไม่ถูกต้อง', 'Invalid Token'); - // TODO: - async onBudgetExpense(req, res, database) { - let idx = -1 - let aryResult = [] - let condition = {} - try { - let token = req.headers.authorization?.split(' ')[1]; - const decoded = verifyToken(token); - database = decoded.organization - var column = "" + database = decoded.organization || database; - column = `prjseq` - condition['prjseq'] = req.body.request.prjseq; + // 2. เตรียมเงื่อนไขค้นหา Project + // column = `prjseq` (กำหนดเงื่อนไขการค้นหา) + condition['prjseq'] = req.body.request.prjseq; - // Check if Project is valid - aryResult = await this.projectSearchService.getProjectSearch(database, column, condition) - } catch (error) { - idx = 1; - } finally { - if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); - if (!aryResult) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); + // 3. ตรวจสอบว่ามี Project นี้จริงหรือไม่ + aryResult = await this.projectSearchService.getProjectSearch(database, 'prjseq, prjcomstt', condition); - if (aryResult.length == 1) { - // TODO: + } catch (error) { + console.error(error); + idx = 1; + } finally { + if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); + + // ถ้าไม่เจอข้อมูล Project + if (!aryResult || aryResult.length === 0) { + return sendError('ไม่พบข้อมูลโครงการ', 'Cannot Find Project Data'); + } - let prommis = await this.makeArySave(req); - return prommis - } else { - return sendError('ไม่พบการมีอยู่ของข้อมูลโครงการ', 'Cannot Find Project Data'); + // ถ้าเจอ Project (aryResult.length >= 1) + if (aryResult.length > 0) { + + // [Check 1] เช็คว่าโครงการนี้อนุมัติไปแล้วหรือยัง? + const project = aryResult[0]; + if (project.prjcomstt === 'BAP') { + return sendError('โครงการนี้ได้รับการอนุมัติงบประมาณไปแล้ว ไม่สามารถทำรายการซ้ำได้', 'Project Already Approved'); } - } - } - async makeArySave(req) { - let arysave = { - methods: 'post', - bdgseq: req.body.request.bdgseq, - bdgnam: req.body.request.bdgnam, - bdgcod: req.body.request.bdgcod, - bdgttl: req.body.request.bdgttl + // เรียก makeArySave เพื่อทำการตัดงบ + const promise = await this.makeArySave(req, database); + return promise; } - return this.Interface.saveInterface('bdgmst', arysave, req); } + } + + async makeArySave(req, database) { + // เตรียมข้อมูลสำหรับตัดงบ + const { prjseq, expenseList } = req.body.request; + + // เรียก Service ที่ทำ Transaction (ตัดงบ, บันทึกรายการ, อัปเดตโปรเจกต์) + try { + const result = await this.budgetExpenseService.approveBudgetExpense(database, prjseq, expenseList); + return result; + } catch (error) { + return sendError(error.message); + } + } } \ 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 9709e60..7b7376b 100644 --- a/exthernal-ttc-api/src/controllers/projectSearchController.js +++ b/exthernal-ttc-api/src/controllers/projectSearchController.js @@ -14,7 +14,7 @@ export class projectSearch { async onNavigate(req, res) { this.generalService.devhint(1, 'projectSearch.js', 'onNavigate() start'); - let organization = req.body.organization; + let organization = req.body.organization || 'dbo'; // Default Schema const prommis = await this.onProjectSearch(req, res, organization); return prommis; } @@ -27,24 +27,41 @@ export class projectSearch { let token = req.headers.authorization?.split(' ')[1]; const decoded = verifyToken(token); - database = decoded.organization + // ใช้ Organization จาก Token ถ้ามี + database = decoded.organization || database + let columnParams = req.query.column - var column = "" - if(columnParams == 'edit'){ - column = `prjnam, prjwntbdg` + + + if (columnParams == 'edit') { + let column = `prjnam, prjwntbdg, ${database}.translatedtl('COMSTT', prjcomstt) as prjcomstt` condition['prjseq'] = req.body.request.prjseq - } else if(columnParams == 'result' || columnParams == undefined || columnParams == ''){ - column = `prjseq, prjnam, prjwntbdg, prjacpbdg, ${database}.translatebdg(prjbdgcod) as prbdgnam, ${database}.translatedtl('COMSTT', prjcomstt) as prjcomstt, prjacpdtm` + + // เรียก Service ตัวเดิม (Simple) + aryResult = await this.projectSearchService.getProjectSearch(database, column, condition); + + } else if (columnParams == 'result' || columnParams == undefined || columnParams == '') { + condition['prjseq'] = req.body.request.prjseq + let column = ` + prjseq, + prjnam, + usrthinam as prjusrnam, + prjwntbdg, + bdgnam, + bdgcod, + prjacpbdg, + ${database}.translatedtl('COMSTT', prjcomstt) as prjcomstt, + prjacpdtm` + aryResult = await this.projectSearchService.getProjectDetailSearch(database, column, condition); } - aryResult = await this.projectSearchService.getProjectSearch(database, column, condition); - } catch (error) { + console.error(error); idx = 1; } finally { - if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); - if (aryResult == 0) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); - return aryResult + if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); + if (!aryResult || aryResult.length === 0) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); + return aryResult } } } diff --git a/exthernal-ttc-api/src/routes/route.js b/exthernal-ttc-api/src/routes/route.js index 319c69a..09b45f6 100644 --- a/exthernal-ttc-api/src/routes/route.js +++ b/exthernal-ttc-api/src/routes/route.js @@ -4,7 +4,7 @@ import { budgetSearch } from '../controllers/budgetSearchController.js' import { budgetAdd } from '../controllers/budgetAddController.js' import { projectSearch } from '../controllers/projectSearchController.js' import { projectAdd } from '../controllers/projectAddController.js' -import { budgetExpense } from '../controllers/budgetExpenseController.js' +import { BudgetExpenseController } from '../controllers/budgetExpenseController.js' import { reportController } from '../controllers/reportController.js' import { transactionSearch } from '../controllers/transactionSearchController.js' @@ -15,7 +15,7 @@ const router = express.Router() const controller_projectSearch_post = new projectSearch() const controller_budgetSearch_post = new budgetSearch() const controller_budgetAdd_post = new budgetAdd() -const controller_budgetSetup_post = new budgetExpense() +const controller_budgetSetup_post = new BudgetExpenseController() const controller_report_post = new reportController() const controller_projectAdd_post = new projectAdd() const controller_transactionSearch_post = new transactionSearch() diff --git a/exthernal-ttc-api/src/services/budgetExpenseService.js b/exthernal-ttc-api/src/services/budgetExpenseService.js index c856096..6dd32ae 100644 --- a/exthernal-ttc-api/src/services/budgetExpenseService.js +++ b/exthernal-ttc-api/src/services/budgetExpenseService.js @@ -1,21 +1,112 @@ -import { GeneralService } from '../share/generalservice.js' +import { GeneralService } from '../share/generalservice.js'; +import { connection } from '../config/db.js'; // ต้อง import connection เพื่อทำ Transaction +import { sendError } from '../utils/response.js'; export class BudgetExpenseService { constructor() { - this.generalService = new GeneralService() + this.generalService = new GeneralService(); } + // อันนี้ใช้ GeneralService ได้เพราะเป็น Query เดียวจบ async getBudgetExpense(database, name) { const sql = ` - SELECT - trnseq, - trnprjnam + SELECT trnseq, trnprjnam FROM ${database}.trnmst WHERE trnprjnam = $1 - ` - const params = [name] + `; + const params = [name]; const result = await this.generalService.executeQueryParam(database, sql, params); - return result + return result; + } + + // ฟังก์ชันอนุมัติและตัดงบ (Transaction) + async approveBudgetExpense(database, projectSeq, expenseList, user) { + // ⚠️ จำเป็นต้องใช้ client โดยตรงเพื่อคุม Transaction (GeneralService ปกติจะใช้ pool ซึ่งอาจเปลี่ยน connection ระหว่างทางได้) + const client = await connection.connect(); + + try { + await client.query('BEGIN'); // เริ่ม Transaction + + let totalApprovedAmount = 0; + // Format วันที่: YYYYMMDD + const currentDate = new Date().toISOString().slice(0, 10).replace(/-/g, ''); + + // 1. วนลูปตัดงบและบันทึก Transaction + for (const expense of expenseList) { + // expense: { bdgcod: '33', amount: 5000 } + + // [Check 2] เช็คก่อนว่ามีรหัสงบประมาณนี้จริงหรือไม่ + const checkBdgSql = `SELECT bdgseq FROM ${database}.bdgmst WHERE bdgcod = $1`; + const checkBdgRes = await client.query(checkBdgSql, [expense.bdgcod]); + + if (checkBdgRes.rows.length === 0) { + return sendError(`ไม่พบรหัสงบประมาณ: ${expense.bdgcod} ในระบบ`, `Cannot Find Budget Code ${expense.bdgcod} in System`); + } + + // 1.1 หา trnseq ล่าสุด + const seqRes = await client.query(`SELECT COALESCE(MAX(trnseq), 0) + 1 as nextseq FROM ${database}.trnmst`); + const nextTrnSeq = seqRes.rows[0].nextseq; + + // 1.2 หาชื่อโครงการ + const prjRes = await client.query(`SELECT prjnam FROM ${database}.prjmst WHERE prjseq = $1`, [projectSeq]); + if (prjRes.rows.length === 0) throw new Error(`Project ${projectSeq} not found`); + const projectName = prjRes.rows[0].prjnam; + + // แปลงยอดเงินเป็นทศนิยม 2 ตำแหน่งให้ชัวร์ก่อนบันทึก + const expenseAmount = Number(expense.amount).toFixed(2); + + // 1.3 Insert ลง trnmst (บันทึกยอดที่ตัดในรายการนี้) + const sqlTrn = ` + INSERT INTO ${database}.trnmst + (trnseq, trnprjnam, trnprjseq, trnexpbdg, trnbdgcod, trncomstt, trnacpdtm) + VALUES ($1, $2, $3, $4, $5, 'BAP', $6) + `; + await client.query(sqlTrn, [ + nextTrnSeq, + projectName, + projectSeq, + expenseAmount, //ใช้ค่าที่ format แล้ว + expense.bdgcod, + currentDate + ]); + + // 1.4 ตัดเงินจากงบประมาณ (bdgmst) + // Postgres จะจัดการลบเลขทศนิยมให้เอง แต่ส่งค่าที่ถูกต้องไปจะดีที่สุด + const sqlUpdateBdg = ` + UPDATE ${database}.bdgmst + SET bdgttl = bdgttl - $1, + bdgedtdtm = $2 + WHERE bdgcod = $3 + `; + await client.query(sqlUpdateBdg, [expenseAmount, currentDate, expense.bdgcod]); + + // สะสมยอดรวม (ระวังเรื่องทศนิยมใน JS) + totalApprovedAmount += Number(expense.amount); + } + + // 2. อัปเดต Project Master (prjmst) + // Format ยอดรวมให้เป็นทศนิยม 2 ตำแหน่งเป๊ะๆ ก่อนบันทึก (เช่น 45000.00) + const formattedTotal = totalApprovedAmount.toFixed(2); + + const sqlUpdatePrj = ` + UPDATE ${database}.prjmst + SET prjacpbdg = $1, + prjcomstt = 'BAP', + prjacpdtm = $2 + WHERE prjseq = $3 + `; + await client.query(sqlUpdatePrj, [formattedTotal, currentDate, projectSeq]); + + await client.query('COMMIT'); // ยืนยัน (ทุกอย่างจะถูกบันทึกพร้อมกันเมื่อถึงบรรทัดนี้) + return { status: true, total: totalApprovedAmount }; + + } catch (error) { + await client.query('ROLLBACK'); // ยกเลิกทั้งหมด (ถ้ามี error ข้อมูลจะกลับไปเหมือนเดิมทุกประการ) + console.error('Transaction Error:', error); + throw error; + } finally { + client.release(); // คืน Connection กลับสู่ Pool + } } } \ No newline at end of file diff --git a/exthernal-ttc-api/src/services/projectSearchService.js b/exthernal-ttc-api/src/services/projectSearchService.js index af829e1..a97394a 100644 --- a/exthernal-ttc-api/src/services/projectSearchService.js +++ b/exthernal-ttc-api/src/services/projectSearchService.js @@ -1,26 +1,46 @@ -import { GeneralService } from '../share/generalservice.js' +import { GeneralService } from '../share/generalservice.js'; export class ProjectSearchService { constructor() { - this.generalService = new GeneralService() + this.generalService = new GeneralService(); } + // 🟢 ฟังก์ชันเดิม (Simple Search) - คืนสภาพเดิมเพื่อไม่ให้กระทบ Service อื่น + // ใช้สำหรับค้นหาข้อมูลในตาราง prjmst อย่างเดียว async getProjectSearch(database, column, condition) { + const selectCol = column || '*'; const sql = ` - SELECT - ${column} + SELECT ${selectCol} FROM ${database}.prjmst WHERE 1=1 - ` - const params = [] - const result = await this.generalService.executeQueryConditions(database, sql, condition); - return result + `; + return await this.generalService.executeQueryConditions(database, sql, condition); } -} -// bdgseq, -// bdgnam, -// bdgcod, -// bdgttl, -// bdgedtdtm \ No newline at end of file + // ดึงข้อมูล: ลำดับ, รหัส, ชื่อโครงการ, ผู้รับผิดชอบ, งบขอ, หมวดงบ, งบอนุมัติ, สถานะ + async getProjectDetailSearch(database, column, condition) { + const selectCol = column || ` + prjseq, + prjnam, + usrnam, + prjwntbdg, + bdgnam, + prjacpbdg, + prjcomstt, + prjacpdtm + `; + + const sql = ` + SELECT ${selectCol} + FROM ${database}.prjmst p + LEFT JOIN ${database}.usrmst u ON prjusrseq = usrseq + LEFT JOIN ${database}.bdgmst b ON prjbdgcod = bdgcod + WHERE 1=1 + `; + + const result = await this.generalService.executeQueryConditions(database, sql, condition); + + return result; + } +} \ No newline at end of file