budget-api: initial
This commit is contained in:
@@ -1,142 +0,0 @@
|
|||||||
import { AccountingSumService } from '../services/accountingSumService.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'
|
|
||||||
|
|
||||||
export class accountingSum {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.generalService = new GeneralService();
|
|
||||||
this.accountingSumService = new AccountingSumService();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onNavigate(req, res) {
|
|
||||||
this.generalService.devhint(1, 'AccountingSum.js', 'onNavigate() start');
|
|
||||||
let organization = req.body.organization;
|
|
||||||
const prommis = await this.onAccountingSum(req, res, organization);
|
|
||||||
return prommis;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAccountingSum(req, res, database) {
|
|
||||||
let idx = -1
|
|
||||||
let result = []
|
|
||||||
var aryResult
|
|
||||||
try {
|
|
||||||
let token = req.body.request.token;
|
|
||||||
const decoded = verifyToken(token);
|
|
||||||
|
|
||||||
let id = decoded.id
|
|
||||||
let username = decoded.name
|
|
||||||
database = decoded.organization
|
|
||||||
|
|
||||||
result = await this.accountingSumService.getAccountingSum(database, id); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
|
||||||
// if(result){
|
|
||||||
// if(result.acttyp == 'e'){
|
|
||||||
// // (ยังไม่มีการใช้งาน)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
idx = 1;
|
|
||||||
} finally {
|
|
||||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
|
||||||
if (!result) return sendError('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ✅ 1) เตรียม data สำหรับใช้คำนวณ
|
|
||||||
// ถ้า service คืนมาเป็น { code, message, data: [...] }
|
|
||||||
const data = Array.isArray(result)
|
|
||||||
? result
|
|
||||||
: Array.isArray(result.data)
|
|
||||||
? result.data
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// ถ้าไม่มีข้อมูลก็ไม่ต้องคำนวณ
|
|
||||||
if (!data.length) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 2) แยก income / expense
|
|
||||||
const incomeList = data.filter(i => i.acttyp === 'i');
|
|
||||||
const expenseList = data.filter(e => e.acttyp === 'e');
|
|
||||||
|
|
||||||
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 = {
|
|
||||||
totalIncome: totalIncome.toFixed(2),
|
|
||||||
totalExpense: totalExpense.toFixed(2),
|
|
||||||
netProfit: netProfit.toFixed(2),
|
|
||||||
profitRate: profitRate.toFixed(2) + ' %',
|
|
||||||
adjustedProfitRate: adjustedProfitRate.toFixed(2) + ' %',
|
|
||||||
period: '30 วัน'
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ 4) ดึงสีจาก dtlmst (แนะนำให้เรียกจาก service เพิ่ม)
|
|
||||||
// ตัวอย่างสมมติ: คุณไป query มาจาก service ก่อนหน้าแล้วได้เป็น object แบบนี้
|
|
||||||
// key = ชื่อหมวด (actcatnam หรือ code), value = color
|
|
||||||
const categoryColorMap = await this.accountingSumService.getCategoryColorMap(database);
|
|
||||||
// ตัวอย่างที่คาดหวังจาก service:
|
|
||||||
// { 'ค่าอาหาร': '#FF6384', 'ค่าเดินทาง': '#36A2EB', 'ขายสินค้า': '#4BC0C0', ... }
|
|
||||||
|
|
||||||
// ✅ 5) สรุปยอดตามหมวด แล้วคำนวณ % สำหรับ expense
|
|
||||||
const expenseAgg = {};
|
|
||||||
expenseList.forEach(row => {
|
|
||||||
const key = row.actcatnam; // หรือใช้รหัส category ถ้ามี เช่น row.actcatcod
|
|
||||||
const amount = parseFloat(row.actqty || 0);
|
|
||||||
expenseAgg[key] = (expenseAgg[key] || 0) + amount;
|
|
||||||
});
|
|
||||||
|
|
||||||
const incomeAgg = {};
|
|
||||||
incomeList.forEach(row => {
|
|
||||||
const key = row.actcatnam;
|
|
||||||
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 เข้า result
|
|
||||||
var pie = {
|
|
||||||
expense: expensePie,
|
|
||||||
income: incomePie
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('calculate summary/pie error:', err);
|
|
||||||
}
|
|
||||||
let arydiy = {
|
|
||||||
summary,
|
|
||||||
pie
|
|
||||||
}
|
|
||||||
|
|
||||||
return arydiy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
import { AccountingSearchService } from '../services/accountingSearchService.js'
|
import { BudgetSearchService } from '../services/budgetSearchService.js'
|
||||||
import { sendError } from '../utils/response.js'
|
import { sendError } from '../utils/response.js'
|
||||||
// import { OftenError } from '../utils/oftenError.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'
|
||||||
|
|
||||||
export class accountingSearch {
|
export class budgetSearch {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generalService = new GeneralService();
|
this.generalService = new GeneralService();
|
||||||
this.accountingSearchService = new AccountingSearchService();
|
this.budgetSearchService = new BudgetSearchService();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigate(req, res) {
|
async onNavigate(req, res) {
|
||||||
this.generalService.devhint(1, 'accountingSearch.js', 'onNavigate() start');
|
this.generalService.devhint(1, 'budgetSearch.js', 'onNavigate() start');
|
||||||
let organization = req.body.organization;
|
let organization = req.body.organization;
|
||||||
const prommis = await this.onAccountingSearch(req, res, organization);
|
const prommis = await this.onBudgetSearch(req, res, organization);
|
||||||
return prommis;
|
return prommis;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAccountingSearch(req, res, database) {
|
async onBudgetSearch(req, res, database) {
|
||||||
let idx = -1
|
let idx = -1
|
||||||
let aryResult = []
|
let aryResult = []
|
||||||
try {
|
try {
|
||||||
@@ -32,8 +32,8 @@ export class accountingSearch {
|
|||||||
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.budgetSearchService.getBudgetSearch(database, id); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
||||||
// this.generalService.devhint(1, 'accountingSearch.js', 'Login success');
|
// this.generalService.devhint(1, 'budgetSearch.js', 'Login success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
idx = 1;
|
idx = 1;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
import { AccountingSetupService } from '../services/accountingSetupService.js'
|
import { BudgetSetupService } from '../services/budgetSetupService.js'
|
||||||
import { sendError } from '../utils/response.js'
|
import { sendError } from '../utils/response.js'
|
||||||
// import { OftenError } from '../utils/oftenError.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'
|
||||||
|
|
||||||
export class accountingSetup {
|
export class budgetSetup {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generalService = new GeneralService();
|
this.generalService = new GeneralService();
|
||||||
this.AccountingSetupService = new AccountingSetupService();
|
this.BudgetSetupService = new BudgetSetupService();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onNavigate(req, res) {
|
async onNavigate(req, res) {
|
||||||
this.generalService.devhint(1, 'accountingSetup.js', 'onNavigate() start');
|
this.generalService.devhint(1, 'budgetSetup.js', 'onNavigate() start');
|
||||||
let organization = req.body.organization;
|
let organization = req.body.organization;
|
||||||
const prommis = await this.onAccountingSetup(req, res, organization);
|
const prommis = await this.onBudgetSetup(req, res, organization);
|
||||||
return prommis;
|
return prommis;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onAccountingSetup(req, res, database) {
|
async onBudgetSetup(req, res, database) {
|
||||||
let idx = -1
|
let idx = -1
|
||||||
let result = []
|
let result = []
|
||||||
try {
|
try {
|
||||||
@@ -31,13 +31,13 @@ export class accountingSetup {
|
|||||||
database = decoded.organization
|
database = decoded.organization
|
||||||
|
|
||||||
|
|
||||||
result = await this.AccountingSetupService.getAccountingSetup(database); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
result = await this.BudgetSetupService.getBudgetSetup(database); // เช็คกับ db กลาง ส่ง jwttoken ออกมา
|
||||||
// this.generalService.devhint(1, 'accountingSetup.js', 'Login success');
|
// this.generalService.devhint(1, 'budgetSetup.js', 'Login success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
idx = 1;
|
idx = 1;
|
||||||
} finally {
|
} finally {
|
||||||
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
if (idx === 1) return sendError('เกิดข้อผิดพลาดไม่คาดคิดเกิดขึ้น', 'Unexpected error');
|
||||||
if (!result) return sendError('ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', 'Invalid credentials');
|
if (!result) return sendError('ไม่พบข้อมูล', 'Data not found');
|
||||||
// แยกกลุ่ม income / expense
|
// แยกกลุ่ม income / expense
|
||||||
let income = result.filter(item => item.dtltblcod === 'ACTCAT_INC').map(({ dtltblcod, ...rest }) => rest);
|
let income = result.filter(item => item.dtltblcod === 'ACTCAT_INC').map(({ dtltblcod, ...rest }) => rest);
|
||||||
|
|
||||||
@@ -1,59 +1,36 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { accountingSetup } from '../controllers/accountingSetupController.js'
|
import { budgetSearch } from '../controllers/budgetSearchController.js'
|
||||||
import { accountingSearch } from '../controllers/accountingSearchController.js'
|
|
||||||
import { accountingSum } from '../controllers/accountingSumController.js'
|
|
||||||
|
|
||||||
// import { authMiddleware } from '../middlewares/auth.js'
|
// import { authMiddleware } from '../middlewares/auth.js'
|
||||||
// import { sendResponse } from '../utils/response.js'
|
// import { sendResponse } from '../utils/response.js'
|
||||||
|
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const controller_accountingSetup_post = new accountingSetup()
|
const controller_budgetSearch_get = new budgetSearch()
|
||||||
const controller_accountingSearch_post = new accountingSearch()
|
|
||||||
const controller_accountingSum_post = new accountingSum()
|
|
||||||
|
|
||||||
|
router.post('/budgetSetup', async (req, res) => {
|
||||||
router.post('/accountingSetup', async (req, res) => {
|
const result = await controller_budgetSetup_post.onNavigate(req, res)
|
||||||
const result = await controller_accountingSetup_post.onNavigate(req, res)
|
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// router.put('/budgetadd', async (req, res) => {
|
||||||
|
// const result = await controller_budgetSetup_post.onNavigate(req, res)
|
||||||
|
// if (result) return res.json(result)
|
||||||
|
// })
|
||||||
|
|
||||||
router.post('/accountingSearch', async (req, res) => {
|
router.get('/budgetsearch', async (req, res) => {
|
||||||
const result = await controller_accountingSearch_post.onNavigate(req, res)
|
const result = await controller_budgetSearch_get.onNavigate(req, res)
|
||||||
if (result) return res.json(result)
|
if (result) return res.json(result)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.post('/accountingSum', async (req, res) => {
|
// router.get('/projectsearch', async (req, res) => {
|
||||||
const result = await controller_accountingSum_post.onNavigate(req, res)
|
// const result = await controller_budgetSetup_post.onNavigate(req, res)
|
||||||
if (result) return res.json(result)
|
// if (result) return res.json(result)
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// // ===================================================
|
|
||||||
// // 🔹 BIOMETRIC LOGIN
|
|
||||||
// // ===================================================
|
|
||||||
// router.post('/biometric/login', async (req, res) => {
|
|
||||||
// const data = await controller_login_post.onBiometricLogin(req, res)
|
|
||||||
// if (data)
|
|
||||||
// return sendResponse(res, 200, 'เข้าสู่ระบบผ่าน Biometric สำเร็จ', 'Biometric login succeed', data)
|
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// // ===================================================
|
// router.put('/budgetexpense', async (req, res) => {
|
||||||
// // 🔹 BIOMETRIC REGISTER (ต้อง login ก่อน)
|
// const result = await controller_budgetSetup_post.onNavigate(req, res)
|
||||||
// // ===================================================
|
// if (result) return res.json(result)
|
||||||
// router.post('/biometric/register', authMiddleware, async (req, res) => {
|
|
||||||
// const data = await controller_login_post.onBiometricRegister(req, res)
|
|
||||||
// if (data)
|
|
||||||
// return sendResponse(res, 200, 'ผูก Biometric สำเร็จ', 'Biometric registered', data)
|
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// // ===================================================
|
|
||||||
// // 🔹 TOKEN RENEW (ต่ออายุ Token)
|
|
||||||
// // ===================================================
|
|
||||||
// router.post('/token/renew', authMiddleware, async (req, res) => {
|
|
||||||
// const data = await controller_login_post.onRenewToken(req, res)
|
|
||||||
// if (data)
|
|
||||||
// return sendResponse(res, 200, 'ออก Token ใหม่สำเร็จ', 'Token renewed', data)
|
|
||||||
// })
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { GeneralService } from '../share/generalservice.js'
|
|
||||||
|
|
||||||
export class AccountingSearchService {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.generalService = new GeneralService()
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAccountingSearch(database, id, username) {
|
|
||||||
const sql = `
|
|
||||||
SELECT
|
|
||||||
actseq,
|
|
||||||
actnum,
|
|
||||||
acttyp,
|
|
||||||
${database}.translatedtl('ACTTYP', acttyp) as acttypnam,
|
|
||||||
${database}.translatedtl_multi(ARRAY['ACTCAT_INC', 'ACTCAT_EXP'], actcat) as actcatnam,
|
|
||||||
actqty,
|
|
||||||
actcmt,
|
|
||||||
actacpdtm
|
|
||||||
FROM ${database}.actmst
|
|
||||||
WHERE actnum = $1
|
|
||||||
`
|
|
||||||
const params = [id]
|
|
||||||
const result = await this.generalService.executeQueryParam(database, sql, params);
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { GeneralService } from '../share/generalservice.js'
|
|
||||||
|
|
||||||
export class AccountingSumService {
|
|
||||||
constructor() {
|
|
||||||
this.generalService = new GeneralService()
|
|
||||||
}
|
|
||||||
async getAccountingSum(database, id) {
|
|
||||||
const sql = `
|
|
||||||
SELECT
|
|
||||||
actseq,
|
|
||||||
actnum,
|
|
||||||
acttyp,
|
|
||||||
${database}.translatedtl('ACTTYP', acttyp) as acttypnam,
|
|
||||||
${database}.translatedtl_multi(ARRAY['ACTCAT_INC', 'ACTCAT_EXP'], actcat) as actcatnam,
|
|
||||||
actqty,
|
|
||||||
actcmt,
|
|
||||||
actacpdtm
|
|
||||||
FROM ${database}.actmst
|
|
||||||
WHERE actnum = $1
|
|
||||||
`
|
|
||||||
const params = [id]
|
|
||||||
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; // ใช้ชื่อหมวดเป็น key
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
23
exthernal-ttc-api/src/services/budgetSearchService.js
Normal file
23
exthernal-ttc-api/src/services/budgetSearchService.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { GeneralService } from '../share/generalservice.js'
|
||||||
|
|
||||||
|
export class BudgetSearchService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.generalService = new GeneralService()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBudgetSearch(database, id) {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
bdgseq,
|
||||||
|
bdgnam,
|
||||||
|
bdgcod,
|
||||||
|
bdgttl,
|
||||||
|
bdgedtdtm
|
||||||
|
FROM ${database}.bdgmst
|
||||||
|
`
|
||||||
|
const params = []
|
||||||
|
const result = await this.generalService.executeQueryParam(database, sql, params);
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
import { GeneralService } from '../share/generalservice.js'
|
import { GeneralService } from '../share/generalservice.js'
|
||||||
|
|
||||||
export class AccountingSetupService {
|
export class BudgetSetupService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generalService = new GeneralService()
|
this.generalService = new GeneralService()
|
||||||
}
|
}
|
||||||
async getAccountingSetup(database) {
|
async getBudgetSetup(database) {
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT
|
SELECT
|
||||||
dtlnam,
|
bgdnam,
|
||||||
dtlcod,
|
bgdcod,
|
||||||
dtltblcod
|
bgdttl,
|
||||||
FROM ${database}.dtlmst
|
bgdedtdtm
|
||||||
|
FROM ${database}.bdgmst
|
||||||
WHERE dtltblcod IN ('ACTTYP', 'ACTCAT_INC', 'ACTCAT_EXP');
|
WHERE dtltblcod IN ('ACTTYP', 'ACTCAT_INC', 'ACTCAT_EXP');
|
||||||
`
|
`
|
||||||
const params = []
|
const params = []
|
||||||
Reference in New Issue
Block a user