/** * 交易控制器 * @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}`; }