-expanse
All checks were successful
Build Docker Image / Build Docker Image (push) Successful in 3m14s
Build Docker Image / Restart Docker Compose (push) Successful in 0s

-prjadd '0.00'
This commit is contained in:
x2Skyz
2025-11-25 15:59:21 +07:00
parent 5e22a0af02
commit b46197ca06
2 changed files with 38 additions and 40 deletions

View File

@@ -59,7 +59,7 @@ export class projectAdd {
prjnam: req.body.request.prjnam, prjnam: req.body.request.prjnam,
prjusrseq: req.body.request.prjusrseq, prjusrseq: req.body.request.prjusrseq,
prjwntbdg: req.body.request.prjwntbdg, prjwntbdg: req.body.request.prjwntbdg,
prjacpbdg: req.body.request.prjacpbdg, prjacpbdg: '0.00',
prjbdgcod: req.body.request.prjbdgcod, prjbdgcod: req.body.request.prjbdgcod,
prjcomstt: req.body.request.prjcomstt, prjcomstt: req.body.request.prjcomstt,
prjacpdtm: req.body.request.prjacpdtm prjacpdtm: req.body.request.prjacpdtm

View File

@@ -1,7 +1,7 @@
import { GeneralService } from '../share/generalservice.js'; import { GeneralService } from '../share/generalservice.js';
import { connection } from '../config/db.js'; import { connection } from '../config/db.js';
import { sendError } from '../utils/response.js'; import { sendError } from '../utils/response.js';
import { getDTM } from '../utils/date.js'; import { getDTM } from '../utils/date.js'; // ✅ 1. Import ฟังก์ชันกลางเข้ามา
export class BudgetExpenseService { export class BudgetExpenseService {
@@ -9,7 +9,6 @@ export class BudgetExpenseService {
this.generalService = new GeneralService(); this.generalService = new GeneralService();
} }
// อันนี้ใช้ GeneralService ได้เพราะเป็น Query เดียวจบ
async getBudgetExpense(database, name) { async getBudgetExpense(database, name) {
const sql = ` const sql = `
SELECT trnseq, trnprjnam SELECT trnseq, trnprjnam
@@ -23,18 +22,17 @@ export class BudgetExpenseService {
// ฟังก์ชันอนุมัติและตัดงบ (Transaction) // ฟังก์ชันอนุมัติและตัดงบ (Transaction)
async approveBudgetExpense(database, projectSeq, expenseList, user) { async approveBudgetExpense(database, projectSeq, expenseList, user) {
// ⚠️ จำเป็นต้องใช้ client โดยตรงเพื่อคุม Transaction (GeneralService ปกติจะใช้ pool ซึ่งอาจเปลี่ยน connection ระหว่างทางได้)
const client = await connection.connect(); const client = await connection.connect();
try { try {
await client.query('BEGIN'); // เริ่ม Transaction await client.query('BEGIN');
const currentDate = getDTM(); // 2. เรียกใช้ getDTM() แทนโค้ดเดิม
const currentDTM = getDTM();
// ========================================================= // =========================================================
// STEP 1: คืนเงินงบประมาณเดิมก่อน (กรณีแก้ไข/ลบรายการ) // STEP 1: คืนเงินงบประมาณเดิมก่อน (กรณีแก้ไข/ลบรายการ)
// ========================================================= // =========================================================
// ดึงรายการเดิมที่เคยบันทึกไว้ของ Project นี้ เพื่อเอามาคืนเงิน
const oldExpenses = await client.query( const oldExpenses = await client.query(
`SELECT trnbdgcod, trnexpbdg FROM ${database}.trnmst WHERE trnprjseq = $1`, `SELECT trnbdgcod, trnexpbdg FROM ${database}.trnmst WHERE trnprjseq = $1`,
[projectSeq] [projectSeq]
@@ -46,13 +44,12 @@ export class BudgetExpenseService {
UPDATE ${database}.bdgmst UPDATE ${database}.bdgmst
SET bdgttl = bdgttl + $1, bdgedtdtm = $2 SET bdgttl = bdgttl + $1, bdgedtdtm = $2
WHERE bdgcod = $3 WHERE bdgcod = $3
`, [oldItem.trnexpbdg, currentDate, oldItem.trnbdgcod]); `, [oldItem.trnexpbdg, currentDTM, oldItem.trnbdgcod]); // update เวลาแก้ไขล่าสุด
} }
// ========================================================= // =========================================================
// STEP 2: ลบรายการเดิมทิ้งทั้งหมด (Clear Old Transactions) // STEP 2: ลบรายการเดิมทิ้ง (เพื่อเตรียมลงใหม่)
// ========================================================= // =========================================================
// การลบทั้งหมดแล้วลงใหม่ จะครอบคลุมทั้งกรณี แก้ไขยอด, เพิ่มรายการใหม่, และลบรายการออก
await client.query(`DELETE FROM ${database}.trnmst WHERE trnprjseq = $1`, [projectSeq]); await client.query(`DELETE FROM ${database}.trnmst WHERE trnprjseq = $1`, [projectSeq]);
@@ -61,37 +58,31 @@ export class BudgetExpenseService {
// ========================================================= // =========================================================
let totalApprovedAmount = 0; let totalApprovedAmount = 0;
// 1.2 หาชื่อโครงการ (ดึงครั้งเดียวนอกลูปเพื่อประสิทธิภาพ)
const prjRes = await client.query(`SELECT prjnam FROM ${database}.prjmst WHERE prjseq = $1`, [projectSeq]); 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`); if (prjRes.rows.length === 0) throw new Error(`Project ${projectSeq} not found`);
const projectName = prjRes.rows[0].prjnam; const projectName = prjRes.rows[0].prjnam;
// 1. วนลูปตัดงบและบันทึก Transaction
for (const expense of expenseList) { for (const expense of expenseList) {
// expense: { bdgcod: '33', amount: 5000 } // 🛑 [Validation] ห้ามยอดเงินเป็น 0 หรือติดลบ
// [Validation] ห้ามยอดเงินเป็น 0 หรือติดลบ
if (!expense.amount || Number(expense.amount) <= 0) { if (!expense.amount || Number(expense.amount) <= 0) {
throw new sendError(`ยอดเงินต้องมากกว่า 0 (รายการรหัสงบ: ${expense.bdgcod})`); return sendError(`ยอดเงินต้องมากกว่า 0 (รายการรหัสงบ: ${expense.bdgcod})`);
} }
// [Check 2] เช็คก่อนว่ามีรหัสงบประมาณนี้จริงหรือไม่ // [Check 2] ตรวจสอบว่ามีรหัสงบประมาณนี้จริงหรือไม่
const checkBdgSql = `SELECT bdgseq FROM ${database}.bdgmst WHERE bdgcod = $1`; const checkBdgSql = `SELECT bdgseq FROM ${database}.bdgmst WHERE bdgcod = $1`;
const checkBdgRes = await client.query(checkBdgSql, [expense.bdgcod]); const checkBdgRes = await client.query(checkBdgSql, [expense.bdgcod]);
if (checkBdgRes.rows.length === 0) { if (checkBdgRes.rows.length === 0) {
// ⚠️ ต้อง throw error เพื่อให้ไปตกที่ catch แล้ว ROLLBACK return sendError(`ไม่พบรหัสงบประมาณ: ${expense.bdgcod} ในระบบ`);
throw new sendError(`ไม่พบรหัสงบประมาณ: ${expense.bdgcod} ในระบบ`);
} }
// 1.1 หา trnseq ล่าสุด // Get Next Seq
const seqRes = await client.query(`SELECT COALESCE(MAX(trnseq), 0) + 1 as nextseq FROM ${database}.trnmst`); const seqRes = await client.query(`SELECT COALESCE(MAX(trnseq), 0) + 1 as nextseq FROM ${database}.trnmst`);
const nextTrnSeq = seqRes.rows[0].nextseq; const nextTrnSeq = seqRes.rows[0].nextseq;
// แปลงยอดเงินเป็นทศนิยม 2 ตำแหน่งให้ชัวร์ก่อนบันทึก
const expenseAmount = Number(expense.amount).toFixed(2); const expenseAmount = Number(expense.amount).toFixed(2);
// 1.3 Insert ลง trnmst (บันทึกยอดที่ตัดในรายการนี้) // Insert รายการใหม่ (บันทึก currentDTM ลง trnacpdtm)
const sqlTrn = ` const sqlTrn = `
INSERT INTO ${database}.trnmst INSERT INTO ${database}.trnmst
(trnseq, trnprjnam, trnprjseq, trnexpbdg, trnbdgcod, trncomstt, trnacpdtm) (trnseq, trnprjnam, trnprjseq, trnexpbdg, trnbdgcod, trncomstt, trnacpdtm)
@@ -101,47 +92,54 @@ export class BudgetExpenseService {
nextTrnSeq, nextTrnSeq,
projectName, projectName,
projectSeq, projectSeq,
expenseAmount, //ใช้ค่าที่ format แล้ว expenseAmount,
expense.bdgcod, expense.bdgcod,
currentDate currentDTM
]); ]);
// 1.4 ตัดเงินจากงบประมาณ (bdgmst) // ตัดเงินงบประมาณ (อัปเดตเวลาแก้ไข)
// Postgres จะจัดการลบเลขทศนิยมให้เอง แต่ส่งค่าที่ถูกต้องไปจะดีที่สุด
const sqlUpdateBdg = ` const sqlUpdateBdg = `
UPDATE ${database}.bdgmst UPDATE ${database}.bdgmst
SET bdgttl = bdgttl - $1, SET bdgttl = bdgttl - $1,
bdgedtdtm = $2 bdgedtdtm = $2
WHERE bdgcod = $3 WHERE bdgcod = $3
`; `;
await client.query(sqlUpdateBdg, [expenseAmount, currentDate, expense.bdgcod]); await client.query(sqlUpdateBdg, [expenseAmount, currentDTM, expense.bdgcod]);
// สะสมยอดรวม (ระวังเรื่องทศนิยมใน JS)
totalApprovedAmount += Number(expense.amount); totalApprovedAmount += Number(expense.amount);
} }
// 2. อัปเดต Project Master (prjmst) // ---------------------------------------------------------
// Format ยอดรวมให้เป็นทศนิยม 2 ตำแหน่งเป๊ะๆ ก่อนบันทึก (เช่น 45000.00) // STEP 4: อัปเดต Project Master (เปลี่ยนสถานะตามยอดเงิน)
// ---------------------------------------------------------
const formattedTotal = totalApprovedAmount.toFixed(2); const formattedTotal = totalApprovedAmount.toFixed(2);
// [UPDATED] กำหนดสถานะ: ถ้ายอดรวมเป็น 0 ให้เป็น UAC (รออนุมัติ), ถ้ามีเงินให้เป็น BAP (อนุมัติแล้ว)
const projectStatus = totalApprovedAmount > 0 ? 'BAP' : 'UAC';
const sqlUpdatePrj = ` const sqlUpdatePrj = `
UPDATE ${database}.prjmst UPDATE ${database}.prjmst
SET prjacpbdg = $1, SET prjacpbdg = $1,
prjcomstt = 'BAP', prjcomstt = $2,
prjacpdtm = $2 prjacpdtm = $3
WHERE prjseq = $3 WHERE prjseq = $4
`; `;
await client.query(sqlUpdatePrj, [formattedTotal, currentDate, projectSeq]); await client.query(sqlUpdatePrj, [formattedTotal, projectStatus, currentDTM, projectSeq]);
await client.query('COMMIT'); // ยืนยัน (ทุกอย่างจะถูกบันทึกพร้อมกันเมื่อถึงบรรทัดนี้) await client.query('COMMIT');
return { status: true, msg: 'Budget updated successfully', total: formattedTotal }; return {
status: true,
msg: 'Budget updated successfully',
total: formattedTotal,
projectStatus: projectStatus
};
} catch (error) { } catch (error) {
await client.query('ROLLBACK'); // ยกเลิกทั้งหมด (ถ้ามี error ข้อมูลจะกลับไปเหมือนเดิมทุกประการ) await client.query('ROLLBACK');
console.error('Transaction Error:', error); console.error('Transaction Error:', error);
throw error; throw error;
} finally { } finally {
client.release(); // คืน Connection กลับสู่ Pool client.release();
} }
} }
} }