Files
nxxmdata/bank-backend/controllers/transactionController.js
2025-09-17 18:04:28 +08:00

459 lines
12 KiB
JavaScript

/**
* 交易控制器
* @file transactionController.js
* @description 处理银行交易相关的请求
*/
const { Transaction, Account, User } = require('../models');
const { validationResult } = require('express-validator');
const { Op } = require('sequelize');
/**
* 获取交易记录列表
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.getTransactions = async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 20;
const offset = (page - 1) * limit;
const {
account_id,
transaction_type,
status,
start_date,
end_date,
amount_min,
amount_max
} = req.query;
const whereClause = {};
// 普通用户只能查看自己账户的交易记录
if (req.user.role.name !== 'admin') {
const userAccounts = await Account.findAll({
where: { user_id: req.user.id },
attributes: ['id']
});
const accountIds = userAccounts.map(account => account.id);
whereClause.account_id = { [Op.in]: accountIds };
} else if (account_id) {
whereClause.account_id = account_id;
}
if (transaction_type) {
whereClause.transaction_type = transaction_type;
}
if (status) {
whereClause.status = status;
}
if (start_date || end_date) {
whereClause.created_at = {};
if (start_date) {
whereClause.created_at[Op.gte] = new Date(start_date);
}
if (end_date) {
whereClause.created_at[Op.lte] = new Date(end_date);
}
}
if (amount_min || amount_max) {
whereClause.amount = {};
if (amount_min) {
whereClause.amount[Op.gte] = Math.round(parseFloat(amount_min) * 100);
}
if (amount_max) {
whereClause.amount[Op.lte] = Math.round(parseFloat(amount_max) * 100);
}
}
const { count, rows } = await Transaction.findAndCountAll({
where: whereClause,
include: [{
model: Account,
as: 'account',
include: [{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name']
}]
}],
limit,
offset,
order: [['created_at', 'DESC']]
});
res.json({
success: true,
data: {
transactions: rows.map(transaction => ({
...transaction.getSafeInfo(),
amount_formatted: transaction.getAmountFormatted(),
balance_after_formatted: transaction.getBalanceAfterFormatted(),
type_description: transaction.getTypeDescription(),
status_description: transaction.getStatusDescription(),
is_income: transaction.isIncome(),
is_expense: transaction.isExpense()
})),
pagination: {
page,
limit,
total: count,
pages: Math.ceil(count / limit)
}
}
});
} catch (error) {
console.error('获取交易记录错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 获取交易详情
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.getTransactionDetail = async (req, res) => {
try {
const { transactionId } = req.params;
const transaction = await Transaction.findByPk(transactionId, {
include: [{
model: Account,
as: 'account',
include: [{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name']
}]
}]
});
if (!transaction) {
return res.status(404).json({
success: false,
message: '交易记录不存在'
});
}
// 检查权限
if (req.user.role.name !== 'admin' && transaction.account.user_id !== req.user.id) {
return res.status(403).json({
success: false,
message: '无权访问该交易记录'
});
}
res.json({
success: true,
data: {
...transaction.getSafeInfo(),
amount_formatted: transaction.getAmountFormatted(),
balance_after_formatted: transaction.getBalanceAfterFormatted(),
type_description: transaction.getTypeDescription(),
status_description: transaction.getStatusDescription(),
is_income: transaction.isIncome(),
is_expense: transaction.isExpense(),
can_reverse: transaction.canReverse()
}
});
} catch (error) {
console.error('获取交易详情错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 转账
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.transfer = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '输入数据验证失败',
errors: errors.array()
});
}
const { from_account_id, to_account_number, amount, description } = req.body;
// 查找转出账户
const fromAccount = await Account.findByPk(from_account_id);
if (!fromAccount) {
return res.status(404).json({
success: false,
message: '转出账户不存在'
});
}
// 检查转出账户权限
if (req.user.role.name !== 'admin' && fromAccount.user_id !== req.user.id) {
return res.status(403).json({
success: false,
message: '无权操作该账户'
});
}
// 查找转入账户
const toAccount = await Account.findOne({
where: { account_number: to_account_number },
include: [{
model: User,
as: 'user',
attributes: ['id', 'username', 'real_name']
}]
});
if (!toAccount) {
return res.status(404).json({
success: false,
message: '转入账户不存在'
});
}
// 检查账户状态
if (!fromAccount.isActive() || !toAccount.isActive()) {
return res.status(400).json({
success: false,
message: '账户状态异常,无法进行转账操作'
});
}
const amountInCents = Math.round(amount * 100);
// 检查余额是否充足
if (!fromAccount.hasSufficientBalance(amountInCents)) {
return res.status(400).json({
success: false,
message: '账户余额不足'
});
}
// 开始事务
const transaction = await sequelize.transaction();
try {
// 更新转出账户余额
await fromAccount.update({
balance: fromAccount.balance - amountInCents,
available_balance: fromAccount.available_balance - amountInCents
}, { transaction });
// 更新转入账户余额
await toAccount.update({
balance: toAccount.balance + amountInCents,
available_balance: toAccount.available_balance + amountInCents
}, { transaction });
const transactionNumber = await generateTransactionNumber();
// 创建转出交易记录
await Transaction.create({
transaction_number: transactionNumber,
account_id: fromAccount.id,
transaction_type: 'transfer_out',
amount: amountInCents,
balance_before: fromAccount.balance + amountInCents,
balance_after: fromAccount.balance,
counterparty_account: toAccount.account_number,
counterparty_name: toAccount.user.real_name,
description: description || `转账给${toAccount.user.real_name}`,
status: 'completed',
processed_at: new Date()
}, { transaction });
// 创建转入交易记录
await Transaction.create({
transaction_number: transactionNumber,
account_id: toAccount.id,
transaction_type: 'transfer_in',
amount: amountInCents,
balance_before: toAccount.balance - amountInCents,
balance_after: toAccount.balance,
counterparty_account: fromAccount.account_number,
counterparty_name: fromAccount.user.real_name,
description: description || `来自${fromAccount.user.real_name}的转账`,
status: 'completed',
processed_at: new Date()
}, { transaction });
await transaction.commit();
res.json({
success: true,
message: '转账成功',
data: {
transaction_number: transactionNumber,
amount: amount,
from_account: fromAccount.account_number,
to_account: toAccount.account_number,
to_account_holder: toAccount.user.real_name
}
});
} catch (error) {
await transaction.rollback();
throw error;
}
} catch (error) {
console.error('转账错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 撤销交易
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.reverseTransaction = async (req, res) => {
try {
const { transactionId } = req.params;
const transaction = await Transaction.findByPk(transactionId, {
include: [{
model: Account,
as: 'account'
}]
});
if (!transaction) {
return res.status(404).json({
success: false,
message: '交易记录不存在'
});
}
// 检查权限
if (req.user.role.name !== 'admin' && transaction.account.user_id !== req.user.id) {
return res.status(403).json({
success: false,
message: '无权操作该交易'
});
}
// 检查是否可以撤销
if (!transaction.canReverse()) {
return res.status(400).json({
success: false,
message: '该交易无法撤销'
});
}
const result = await transaction.reverse();
if (result) {
res.json({
success: true,
message: '交易撤销成功'
});
} else {
res.status(400).json({
success: false,
message: '交易撤销失败'
});
}
} catch (error) {
console.error('撤销交易错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 获取交易统计
* @param {Object} req 请求对象
* @param {Object} res 响应对象
*/
exports.getTransactionStats = async (req, res) => {
try {
const { start_date, end_date, account_id } = req.query;
const whereClause = {};
// 普通用户只能查看自己账户的统计
if (req.user.role.name !== 'admin') {
const userAccounts = await Account.findAll({
where: { user_id: req.user.id },
attributes: ['id']
});
const accountIds = userAccounts.map(account => account.id);
whereClause.account_id = { [Op.in]: accountIds };
} else if (account_id) {
whereClause.account_id = account_id;
}
if (start_date || end_date) {
whereClause.created_at = {};
if (start_date) {
whereClause.created_at[Op.gte] = new Date(start_date);
}
if (end_date) {
whereClause.created_at[Op.lte] = new Date(end_date);
}
}
// 获取交易统计
const stats = await Transaction.findAll({
where: whereClause,
attributes: [
'transaction_type',
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
[sequelize.fn('SUM', sequelize.col('amount')), 'total_amount']
],
group: ['transaction_type'],
raw: true
});
// 获取总交易数
const totalCount = await Transaction.count({ where: whereClause });
// 获取总交易金额
const totalAmount = await Transaction.sum('amount', { where: whereClause });
res.json({
success: true,
data: {
total_count: totalCount,
total_amount: totalAmount ? (totalAmount / 100).toFixed(2) : '0.00',
by_type: stats.map(stat => ({
type: stat.transaction_type,
count: parseInt(stat.count),
total_amount: (parseInt(stat.total_amount) / 100).toFixed(2)
}))
}
});
} catch (error) {
console.error('获取交易统计错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
}
};
/**
* 生成交易流水号
* @returns {String} 交易流水号
*/
async function generateTransactionNumber() {
const timestamp = Date.now().toString();
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
return `TXN${timestamp}${random}`;
}