diff --git a/exthernal-accountingwep-api/.env b/exthernal-accountingwep-api/.env index aceb7f0..f4c11cd 100644 --- a/exthernal-accountingwep-api/.env +++ b/exthernal-accountingwep-api/.env @@ -2,9 +2,9 @@ PJ_NAME=exthernal-wepaccounting-api # database -PG_HOST=localhost +PG_HOST=10.9.0.0 PG_USER=postgres -PG_PASS=123456 +PG_PASS=ttc@2026 PG_DB=ttc PG_PORT=5432 @@ -13,7 +13,7 @@ SMTP_USER=lalisakuty@gmail.com SMTP_PASS=lurl pckw qugk tzob # REDIS -REDIS_HOST=127.0.0.1 +REDIS_HOST=10.9.0.0x REDIS_PORT=6379 OTP_TTL_SECONDS=300 diff --git a/exthernal-accountingwep-api/src/controllers/accountingAddController.js b/exthernal-accountingwep-api/src/controllers/accountingAddController.js index 70083d4..3a28922 100644 --- a/exthernal-accountingwep-api/src/controllers/accountingAddController.js +++ b/exthernal-accountingwep-api/src/controllers/accountingAddController.js @@ -4,12 +4,14 @@ 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'; export class accountingAdd { constructor() { this.generalService = new GeneralService(); this.accountingAddService = new AccountingAddService(); + this.Interface = new Interface(); } async onNavigate(req, res) { @@ -22,20 +24,43 @@ export class accountingAdd { async onAccountingAdd(req, res, database) { let idx = -1 let aryResult = [] + let latSeq = [] try { - let token = req.body.request.token; + let token = req.headers.authorization?.split(' ')[1]; const decoded = verifyToken(token); - let num = req.body.request.actnum; - database = decoded.organization + let actnum = req.body.request.actnum; + database = decoded.organization; - aryResult = await this.accountingAddService.getAccountingAdd(database, num); + aryResult = await this.accountingAddService.getAccountingAdd(database, actnum); + latSeq = await this.accountingAddService.getLatestAccountingSeq(database); } catch (error) { idx = 1; } finally { if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); - if (!aryResult) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); - return aryResult + // if (!aryResult) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); + + if (aryResult == 0) { + let prommis = await this.makeArySave(req, latSeq[0].actseq); + return prommis + } else { + return sendError('คีย์หลักซ้ำในระบบ', 'Duplicate Primary Key'); + } } } + + + async makeArySave(req, seq) { + let arysave = { + methods: 'post', + actseq: seq + 1, + actnum: req.body.request.actnum, + actacpdtm: req.body.request.actacpdtm, + actcat: req.body.request.actcat, + actqty: req.body.request.actqty, + actcmt: req.body.request.actcmt, + acttyp: req.body.request.acttyp + } + return this.Interface.saveInterface('actmst', arysave, req); + } } diff --git a/exthernal-accountingwep-api/src/controllers/reportController.js b/exthernal-accountingwep-api/src/controllers/reportController.js index ef06f6c..8fcd412 100644 --- a/exthernal-accountingwep-api/src/controllers/reportController.js +++ b/exthernal-accountingwep-api/src/controllers/reportController.js @@ -68,11 +68,17 @@ export class reportController { totalIncome: totalIncome.toFixed(2), totalExpense: totalExpense.toFixed(2), netProfit: netProfit.toFixed(2), - profitRate: profitRate.toFixed(2) + ' %', - adjustedProfitRate: adjustedProfitRate.toFixed(2) + ' %', - period: '30 วัน' }; + // ✅ 3.5) Create actdata table with required fields grouped by actnum + var actdata = data.map(row => ({ + actnam: row.actnam, + actcat: row.actcat, + actqty: row.actqty, + actcmt: row.actcmt, + actacpdtm: row.actacpdtm + })); + // ✅ 4) ดึงสีจาก dtlmst (แนะนำให้เรียกจาก service เพิ่ม) // ตัวอย่างสมมติ: คุณไป query มาจาก service ก่อนหน้าแล้วได้เป็น object แบบนี้ // key = ชื่อหมวด (actcatnam หรือ code), value = color @@ -127,8 +133,9 @@ export class reportController { console.error('calculate summary/pie error:', err); } let arydiy = { + actdata, summary, - pie + pie, } return arydiy; diff --git a/exthernal-accountingwep-api/src/services/accountingAddService.js b/exthernal-accountingwep-api/src/services/accountingAddService.js index 9c0bd2d..d5f888a 100644 --- a/exthernal-accountingwep-api/src/services/accountingAddService.js +++ b/exthernal-accountingwep-api/src/services/accountingAddService.js @@ -6,15 +6,29 @@ export class AccountingAddService { this.generalService = new GeneralService() } - async getAccountingAdd(database, name) { + async getAccountingAdd(database, number) { const sql = ` SELECT actseq, - actnam + actnum FROM ${database}.actmst - WHERE actnam = $1 - ` - const params = [name] + WHERE actnum = $1 + ` + + const params = [number] + const result = await this.generalService.executeQueryParam(database, sql, params); + return result + } + + async getLatestAccountingSeq(database) { + const sql = ` + SELECT + actseq + FROM ${database}.actmst + WHERE actseq=(SELECT max(actseq) FROM ${database}.actmst) + ` + + const params = [] const result = await this.generalService.executeQueryParam(database, sql, params); return result } diff --git a/exthernal-accountingwep-api/src/services/accountingSumService.js b/exthernal-accountingwep-api/src/services/accountingSumService.js index caedd62..6718ffd 100644 --- a/exthernal-accountingwep-api/src/services/accountingSumService.js +++ b/exthernal-accountingwep-api/src/services/accountingSumService.js @@ -23,7 +23,6 @@ export class AccountingSumService { return result } - async getCategoryColorMap(database) { const sql = ` SELECT dtlcod, dtlnam, dtlmsc as dtlclr diff --git a/exthernal-accountingwep-api/src/services/reportService.js b/exthernal-accountingwep-api/src/services/reportService.js index ea23ee1..01d6760 100644 --- a/exthernal-accountingwep-api/src/services/reportService.js +++ b/exthernal-accountingwep-api/src/services/reportService.js @@ -9,8 +9,9 @@ export class ReportService { async getReportController(database, number) { const sql = ` SELECT + ${database}.translatedtl('ACTTYP', acttyp) AS actnam, acttyp, - actcat, + ${database}.translatedtl_multi(ARRAY['ACTCAT_INC','ACTCAT_EXP'], actcat) AS actcat, actqty, actcmt, actacpdtm diff --git a/exthernal-login-api/.env b/exthernal-login-api/.env index 739cb1c..d5df980 100644 --- a/exthernal-login-api/.env +++ b/exthernal-login-api/.env @@ -1,10 +1,10 @@ #project -PJ_NAME=exthernal-mobile-api +PJ_NAME=exthernal-login-api # database -PG_HOST=localhost +PG_HOST=10.9.0.0 PG_USER=postgres -PG_PASS=123456 +PG_PASS=ttc@2026 PG_DB=ttc PG_PORT=5432 @@ -13,7 +13,7 @@ SMTP_USER=lalisakuty@gmail.com SMTP_PASS=lurl pckw qugk tzob # REDIS -REDIS_HOST=127.0.0.1 +REDIS_HOST=10.9.0.0 REDIS_PORT=6379 OTP_TTL_SECONDS=300 diff --git a/exthernal-ttc-api/src/controllers/reportController.js b/exthernal-ttc-api/src/controllers/reportController.js new file mode 100644 index 0000000..c8655de --- /dev/null +++ b/exthernal-ttc-api/src/controllers/reportController.js @@ -0,0 +1,159 @@ +import { ReportService } from '../services/reportService.js' +import { sendError } from '../utils/response.js' +// import { OftenError } from '../utils/oftenError.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'; + +export class reportController { + + constructor() { + this.generalService = new GeneralService(); + this.reportService = new ReportService(); + this.Interface = new Interface(); + } + + async onNavigate(req, res) { + this.generalService.devhint(1, 'reportController.js', 'onNavigate() start'); + let organization = req.body.organization; + const prommis = await this.onReportController(req, res, organization); + return prommis; + } + + async onReportController(req, res, database) { + let idx = -1 + let aryResult = [] + try { + let token = req.headers.authorization?.split(' ')[1]; + const decoded = verifyToken(token); + + let acpTime = req.body.request.acpTime; + let expTime = req.body.request.expTime; + database = decoded.organization; + + aryResult = await this.reportService.getReportController(database, acpTime, expTime); + } catch (error) { + idx = 1; + } finally { + if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error'); + if (!aryResult) return sendError('ไม่พบการมีอยู่ของข้อมูล', 'Cannot Find Any Data'); + + try { + // ✅ 1) เตรียม data สำหรับใช้คำนวณ + // ถ้า service คืนมาเป็น { code, message, data: [...] } + const data = Array.isArray(aryResult) + ? aryResult + : Array.isArray(aryResult.data) + ? aryResult.data + : []; + + // ถ้าไม่มีข้อมูลก็ไม่ต้องคำนวณ + if (!data.length) { + return aryResult; + } + + // 2) แยก Budget Type + const developStudentList = data.filter(i => i.trnbdgcod === '29'); + const incomeList = data.filter(i => i.trnbdgcod === '33'); + const incomeBachelorList = data.filter(i => i.trnbdgcod === '38'); + const budgetCollegeList = data.filter(i => i.trnbdgcod === '24'); + const budgetTeachingList = data.filter(i => i.trnbdgcod === '30'); + const shortBudgetList = data.filter(i => i.trnbdgcod === '25'); + + // const totalIncome = incomeList.reduce((sum, i) => sum + parseFloat(i.actqty || 0), 0); + // const totalExpense = expenseList.reduce((sum, e) => sum + parseFloat(e.actqty || 0), 0); + + // const netProfit = totalIncome - totalExpense; + // const profitRate = totalIncome > 0 ? (netProfit / totalIncome) * 100 : 0; + // const adjustedProfitRate = profitRate + 1.9; + + // ✅ 3) แนบ summary (เหมือนที่เราทำไปก่อนหน้า) + var summary = { + // DEBUG + developStudentList: developStudentList, + incomeList: incomeList, + incomeBachelorList: incomeBachelorList, + budgetCollegeList: budgetCollegeList, + budgetTeachingList: budgetTeachingList, + shortBudgetList: shortBudgetList, + + // totalIncome: totalIncome.toFixed(2), + // totalExpense: totalExpense.toFixed(2), + // netProfit: netProfit.toFixed(2), + }; + + // ✅ 3.5) Create actdata table with required fields grouped by actnum + var trndata = data.map(row => ({ + trnprjnam: row.trnprjnam, + trnexpbdg: row.trnexpbdg, + trnbdgnam: row.trnbdgnam, + // trnbdgcod: row.trnbdgcod, // DEBUG + trncomsttnam: row.trncomsttnam, + // trncomstt: row.trncomstt, // DEBUG + trnacpdtm: row.trnacpdtm + })); + + // // ✅ 4) ดึงสีจาก dtlmst (แนะนำให้เรียกจาก service เพิ่ม) + // // ตัวอย่างสมมติ: คุณไป query มาจาก service ก่อนหน้าแล้วได้เป็น object แบบนี้ + // // key = ชื่อหมวด (actcatnam หรือ code), value = color + // const categoryColorMap = await this.reportService.getCategoryColorMap(database); + // // ตัวอย่างที่คาดหวังจาก service: + // // { 'ค่าอาหาร': '#FF6384', 'ค่าเดินทาง': '#36A2EB', 'ขายสินค้า': '#4BC0C0', ... } + + // // ✅ 5) สรุปยอดตามหมวด แล้วคำนวณ % สำหรับ expense + // const expenseAgg = {}; + // expenseList.forEach(row => { + // const key = row.actcat; // หรือใช้รหัส category ถ้ามี เช่น row.actcatcod + // const amount = parseFloat(row.actqty || 0); + // expenseAgg[key] = (expenseAgg[key] || 0) + amount; + // }); + + // const incomeAgg = {}; + // incomeList.forEach(row => { + // const key = row.actcat; + // const amount = parseFloat(row.actqty || 0); + // incomeAgg[key] = (incomeAgg[key] || 0) + amount; + // }); + + // const expensePie = Object.entries(expenseAgg).map(([cat, value]) => { + // const percent = totalExpense > 0 ? (value / totalExpense) * 100 : 0; + // const color = categoryColorMap[cat] || '#CCCCCC'; // fallback สี default + // return { + // label: cat, + // value: value.toFixed(2), + // percent: percent.toFixed(2), + // color + // }; + // }); + + // const incomePie = Object.entries(incomeAgg).map(([cat, value]) => { + // const percent = totalIncome > 0 ? (value / totalIncome) * 100 : 0; + // const color = categoryColorMap[cat] || '#CCCCCC'; + // return { + // label: cat, + // value: value.toFixed(2), + // percent: percent.toFixed(2), + // color + // }; + // }); + + // // ✅ 6) แนบข้อมูล pie chart เข้า aryResult + // var pie = { + // expense: expensePie, + // income: incomePie + // }; + + } catch (err) { + console.error('calculate summary/pie error:', err); + } + let arydiy = { + trndata, + summary, + // pie, + } + + return arydiy; + } + } +} diff --git a/exthernal-ttc-api/src/routes/route.js b/exthernal-ttc-api/src/routes/route.js index b26c5a9..65761f4 100644 --- a/exthernal-ttc-api/src/routes/route.js +++ b/exthernal-ttc-api/src/routes/route.js @@ -4,6 +4,7 @@ import { budgetSearch } from '../controllers/budgetSearchController.js' import { budgetAdd } from '../controllers/budgetAddController.js' import { projectSearch } from '../controllers/projectSearchController.js' import { budgetExpense } from '../controllers/budgetExpenseController.js' +import { reportController } from '../controllers/ReportController.js' // import { authMiddleware } from '../middlewares/auth.js' // import { sendResponse } from '../utils/response.js' @@ -13,6 +14,7 @@ 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_report_post = new reportController() // router.post('/budgetSetup', async (req, res) => { // const result = await controller_budgetSetup_post.onNavigate(req, res) @@ -39,5 +41,9 @@ router.post('/budgetexpense', async (req, res) => { if (result) return res.json(result) }) +router.post('/report', async (req, res) => { + const result = await controller_report_post.onNavigate(req, res) + if (result) return res.json(result) +}) export default router diff --git a/exthernal-ttc-api/src/services/reportService.js b/exthernal-ttc-api/src/services/reportService.js new file mode 100644 index 0000000..6d997dc --- /dev/null +++ b/exthernal-ttc-api/src/services/reportService.js @@ -0,0 +1,45 @@ +import { GeneralService } from '../share/generalservice.js' + +export class ReportService { + + constructor() { + this.generalService = new GeneralService() + } + + async getReportController(database, acpTime, expTime) { + // trnprjseq, + const sql = ` + SELECT + trnprjnam, + trnexpbdg, + ${database}.translatebdg(trnbdgcod) AS trnbdgnam, + trnbdgcod, + ${database}.translatedtl('COMSTT', trncomstt) AS trncomsttnam, + trncomstt, + trnacpdtm + FROM ${database}.trnmst + WHERE trnacpdtm BETWEEN $1 AND $2; + `; + + const params = [acpTime, expTime]; + const result = await this.generalService.executeQueryParam(database, sql, params); + return result + } + + async getCategoryColorMap(database) { + const sql = ` + SELECT dtlcod, dtlnam, dtlmsc as dtlclr + FROM ${database}.dtlmst + WHERE dtltblcod IN ('ACTCAT_INC', 'ACTCAT_EXP') + `; + const params = []; + const rows = await this.generalService.executeQueryParam(database, sql, params); + + const map = {}; + rows.forEach(r => { + map[r.dtlnam] = r.dtlclr; + }); + + return map; + } +} \ No newline at end of file