add multi-currency master CRUD (Phase 2)
- add currencyadapter.js: list (with pagination+keyword), detail, history, create, update (auto-log rate change to tbl_currency_log), delete (soft), convertAmount helper - add controllers/currency.js and routes/currency.js, auto-mounted at /currency - update dbproc.js: configurable port via HOSTPORT env variable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
311
adapter/currencyadapter.js
Normal file
311
adapter/currencyadapter.js
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
const db = require('../config/dbproc.js');
|
||||||
|
const Adapter = require('./dbadapter.js');
|
||||||
|
|
||||||
|
class CurrencyAdapter extends Adapter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryCurrencyList(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let limit = req.query.limit;
|
||||||
|
let offset = req.query.offset;
|
||||||
|
let keyword = req.query.keyword ?? '';
|
||||||
|
|
||||||
|
let qryBase = "select _idx, name, currency, symbol, rate from tbl_currency ";
|
||||||
|
qryBase += "where isdeleted=0 and (name like '%" + keyword + "%' or currency like '%" + keyword + "%') ";
|
||||||
|
|
||||||
|
db.query(qryBase + "order by currency asc", [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
if (result.length > 0) {
|
||||||
|
let pagination = result.length / limit;
|
||||||
|
if (!Number.isInteger(pagination)) {
|
||||||
|
pagination = (Math.floor(result.length / limit)) + 1;
|
||||||
|
}
|
||||||
|
apires.success = true;
|
||||||
|
apires.data.push({
|
||||||
|
"totalpage": pagination,
|
||||||
|
"totalrows": result.length
|
||||||
|
});
|
||||||
|
let qryPage = qryBase + "order by currency asc limit " + offset + ", " + limit;
|
||||||
|
db.query(qryPage, [], function (err2, result2, fields2) {
|
||||||
|
if (err2) {
|
||||||
|
apires.meta['message'] = err2.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
apires.data.push({
|
||||||
|
"results": JSON.parse(JSON.stringify(result2))
|
||||||
|
});
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
apires.meta.code = 200;
|
||||||
|
apires.meta.message = "Record Not Found";
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryCurrencyDetail(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let id = req.params.id;
|
||||||
|
let qry = "select _idx, name, currency, symbol, rate, isdeleted, iby, idt, uby, udt from tbl_currency where _idx='" + id + "' and isdeleted=0";
|
||||||
|
db.query(qry, [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
if (result.length > 0) {
|
||||||
|
apires.success = true;
|
||||||
|
apires.data.push({
|
||||||
|
"results": JSON.parse(JSON.stringify(result))
|
||||||
|
});
|
||||||
|
callback(null, apires);
|
||||||
|
} else {
|
||||||
|
apires.meta.code = 200;
|
||||||
|
apires.meta.message = "Record Not Found";
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryCurrencyHistory(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let id = req.params.id;
|
||||||
|
// get currency_code first, then fetch log
|
||||||
|
let qryCode = "select currency from tbl_currency where _idx='" + id + "'";
|
||||||
|
db.query(qryCode, [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
if (result.length === 0) {
|
||||||
|
apires.meta.code = 200;
|
||||||
|
apires.meta.message = "Record Not Found";
|
||||||
|
callback(null, apires);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let currencyCode = result[0].currency;
|
||||||
|
let qryLog = "select _idx, currency_code, old_rate, new_rate, source, remark, iby, idt from tbl_currency_log where currency_code='" + currencyCode + "' order by idt desc";
|
||||||
|
db.query(qryLog, [], function (err2, result2, fields2) {
|
||||||
|
if (err2) {
|
||||||
|
apires.meta['message'] = err2.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
apires.success = true;
|
||||||
|
apires.data.push({
|
||||||
|
"results": JSON.parse(JSON.stringify(result2))
|
||||||
|
});
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryCreateCurrency(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let name = req.body.name;
|
||||||
|
let currency = req.body.currency;
|
||||||
|
let symbol = req.body.symbol;
|
||||||
|
let rate = req.body.rate;
|
||||||
|
let nik = req.nik;
|
||||||
|
|
||||||
|
let qry = "insert into tbl_currency set ";
|
||||||
|
qry += "name='" + name + "',";
|
||||||
|
qry += "currency='" + currency + "',";
|
||||||
|
qry += "symbol='" + symbol + "',";
|
||||||
|
qry += "rate='" + rate + "',";
|
||||||
|
qry += "isdeleted=0,";
|
||||||
|
qry += "iby='" + nik + "',idt=now()";
|
||||||
|
|
||||||
|
db.query(qry, [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
apires.success = true;
|
||||||
|
apires.meta.message = "Saved Success";
|
||||||
|
apires.data = JSON.parse(JSON.stringify(result));
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryUpdateCurrency(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let id = req.params.id;
|
||||||
|
let name = req.body.name;
|
||||||
|
let currency = req.body.currency;
|
||||||
|
let symbol = req.body.symbol;
|
||||||
|
let newRate = req.body.rate;
|
||||||
|
let nik = req.nik;
|
||||||
|
|
||||||
|
// fetch current rate to detect change
|
||||||
|
let qryOld = "select rate, currency from tbl_currency where _idx='" + id + "' and isdeleted=0";
|
||||||
|
db.query(qryOld, [], function (err, oldResult, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oldResult.length === 0) {
|
||||||
|
apires.meta.code = 200;
|
||||||
|
apires.meta.message = "Record Not Found";
|
||||||
|
callback(null, apires);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldRate = oldResult[0].rate;
|
||||||
|
let currencyCode = oldResult[0].currency;
|
||||||
|
let rateChanged = parseFloat(oldRate) !== parseFloat(newRate);
|
||||||
|
|
||||||
|
let qryUpdate = "update tbl_currency set ";
|
||||||
|
qryUpdate += "name='" + name + "',";
|
||||||
|
qryUpdate += "currency='" + currency + "',";
|
||||||
|
qryUpdate += "symbol='" + symbol + "',";
|
||||||
|
qryUpdate += "rate='" + newRate + "',";
|
||||||
|
qryUpdate += "uby='" + nik + "',udt=now() ";
|
||||||
|
qryUpdate += "where _idx='" + id + "'";
|
||||||
|
|
||||||
|
db.query(qryUpdate, [], function (err2, result2, fields2) {
|
||||||
|
if (err2) {
|
||||||
|
apires.meta['message'] = err2.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rateChanged) {
|
||||||
|
apires.success = true;
|
||||||
|
apires.meta.message = "Updated Success";
|
||||||
|
apires.data = JSON.parse(JSON.stringify(result2));
|
||||||
|
callback(null, apires);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// log the rate change
|
||||||
|
let qryLog = "insert into tbl_currency_log set ";
|
||||||
|
qryLog += "currency_code='" + currencyCode + "',";
|
||||||
|
qryLog += "old_rate='" + oldRate + "',";
|
||||||
|
qryLog += "new_rate='" + newRate + "',";
|
||||||
|
qryLog += "source='manual',";
|
||||||
|
qryLog += "iby='" + nik + "',idt=now()";
|
||||||
|
|
||||||
|
db.query(qryLog, [], function (err3, result3, fields3) {
|
||||||
|
if (err3) {
|
||||||
|
apires.meta['message'] = err3.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
apires.success = true;
|
||||||
|
apires.meta.message = "Updated Success";
|
||||||
|
apires.data = JSON.parse(JSON.stringify(result2));
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async queryDeleteCurrency(req, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
try {
|
||||||
|
let id = req.params.id;
|
||||||
|
let nik = req.nik;
|
||||||
|
|
||||||
|
let qry = "update tbl_currency set isdeleted=1,dby='" + nik + "',ddt=now() where _idx='" + id + "'";
|
||||||
|
db.query(qry, [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
apires.meta['message'] = err.toString();
|
||||||
|
apires.meta['code'] = 500;
|
||||||
|
callback('err', apires);
|
||||||
|
} else {
|
||||||
|
apires.success = true;
|
||||||
|
apires.meta.message = "Deleted Success";
|
||||||
|
apires.data = JSON.parse(JSON.stringify(result));
|
||||||
|
callback(null, apires);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apires.meta.code = 500;
|
||||||
|
apires.meta.message = err.toString();
|
||||||
|
callback('error', apires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch rates for two currency IDs — used for cross-currency conversion
|
||||||
|
queryCurrencyRates(fromId, toId, callback) {
|
||||||
|
var apires = this.getApiResultDefined();
|
||||||
|
let qry = "select _idx, rate from tbl_currency where _idx in ('" + fromId + "','" + toId + "') and isdeleted=0";
|
||||||
|
db.query(qry, [], function (err, result, fields) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var rates = {};
|
||||||
|
result.forEach(function (row) {
|
||||||
|
rates[row._idx] = parseFloat(row.rate);
|
||||||
|
});
|
||||||
|
if (rates[fromId] === undefined || rates[toId] === undefined) {
|
||||||
|
callback(new Error('Currency not found'), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null, { from: rates[fromId], to: rates[toId] });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert amount from one currency to another using USD as pivot
|
||||||
|
convertAmount(amount, rateFrom, rateTo, callback) {
|
||||||
|
if (!rateTo || rateTo === 0) {
|
||||||
|
callback(new Error('Invalid target rate: rate cannot be zero'), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(null, parseFloat(amount) * (rateFrom / rateTo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CurrencyAdapter;
|
||||||
@ -7,7 +7,8 @@ const dbcon = mysql.createConnection({
|
|||||||
database : process.env.DBHOST,
|
database : process.env.DBHOST,
|
||||||
acquireTimeout: 30000,
|
acquireTimeout: 30000,
|
||||||
insecureAuth: true,
|
insecureAuth: true,
|
||||||
timezone: 'utc'
|
timezone: 'utc',
|
||||||
|
port : process.env.HOSTPORT ?? 3306
|
||||||
});
|
});
|
||||||
|
|
||||||
dbcon.connect(function(err) {
|
dbcon.connect(function(err) {
|
||||||
|
|||||||
89
controllers/currency.js
Normal file
89
controllers/currency.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
const CurrencyAdapter = require('../adapter/currencyadapter.js');
|
||||||
|
const currencyadapter = new CurrencyAdapter();
|
||||||
|
const Controllers = require('./controller.js');
|
||||||
|
const controllers = new Controllers();
|
||||||
|
var apireshandler = controllers.getApiResultDefined();
|
||||||
|
|
||||||
|
exports.getCurrencyList = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryCurrencyList(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[getCurrencyList] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getCurrencyDetail = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryCurrencyDetail(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[getCurrencyDetail] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getCurrencyHistory = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryCurrencyHistory(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[getCurrencyHistory] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createCurrency = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryCreateCurrency(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[createCurrency] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateCurrency = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryUpdateCurrency(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[updateCurrency] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.deleteCurrency = (req, res) => {
|
||||||
|
try {
|
||||||
|
currencyadapter.queryDeleteCurrency(req, function (err, data) {
|
||||||
|
let statusCode = data != null ? data.meta.code : 200;
|
||||||
|
if (err) statusCode = 500;
|
||||||
|
currencyadapter.sendResponse(statusCode, data, res);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
apireshandler.meta.code = 502;
|
||||||
|
apireshandler.meta.message = "[deleteCurrency] : Currency controller, " + err.toString();
|
||||||
|
currencyadapter.sendResponse(502, apireshandler, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
routes/currency.js
Normal file
13
routes/currency.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const currencycontroller = require('../controllers/currency');
|
||||||
|
const jwtauth = require('../middlewares/auth.js');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get('/list', [jwtauth], currencycontroller.getCurrencyList);
|
||||||
|
router.get('/detail/:id', [jwtauth], currencycontroller.getCurrencyDetail);
|
||||||
|
router.get('/history/:id', [jwtauth], currencycontroller.getCurrencyHistory);
|
||||||
|
router.post('/create', [jwtauth], currencycontroller.createCurrency);
|
||||||
|
router.put('/update/:id', [jwtauth], currencycontroller.updateCurrency);
|
||||||
|
router.delete('/delete/:id', [jwtauth], currencycontroller.deleteCurrency);
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user