Transaction.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. const { pool } = require('../config/database');
  2. // 创建交易表
  3. const createTransactionTable = async () => {
  4. try {
  5. // 先检查表是否存在
  6. const [tables] = await pool.query('SHOW TABLES LIKE "transactions"');
  7. if (tables.length === 0) {
  8. // 如果表不存在,创建新表
  9. await pool.query(`
  10. CREATE TABLE transactions (
  11. id INT AUTO_INCREMENT PRIMARY KEY,
  12. group_id VARCHAR(50) NOT NULL,
  13. group_name VARCHAR(100) NOT NULL,
  14. type ENUM('deposit', 'withdrawal') NOT NULL COMMENT 'deposit:入款,withdrawal:出款',
  15. amount DECIMAL(10,2) NOT NULL,
  16. time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  17. remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
  18. operator_id INT NOT NULL COMMENT '操作人ID',
  19. INDEX idx_group_time (group_id, time),
  20. FOREIGN KEY (operator_id) REFERENCES users(id)
  21. )
  22. `);
  23. console.log('交易表创建成功');
  24. } else {
  25. // 如果表存在,检查是否需要添加新字段
  26. const [columns] = await pool.query('SHOW COLUMNS FROM transactions');
  27. const columnNames = columns.map(col => col.Field);
  28. // 检查并添加缺失的字段
  29. if (!columnNames.includes('operator_id')) {
  30. try {
  31. // 先添加字段
  32. await pool.query(`
  33. ALTER TABLE transactions
  34. ADD COLUMN operator_id INT NOT NULL DEFAULT 1 COMMENT '操作人ID' AFTER remark
  35. `);
  36. console.log('添加operator_id字段成功');
  37. // 然后添加外键约束
  38. await pool.query(`
  39. ALTER TABLE transactions
  40. ADD CONSTRAINT fk_transaction_operator
  41. FOREIGN KEY (operator_id) REFERENCES users(id)
  42. `);
  43. console.log('添加外键约束成功');
  44. } catch (error) {
  45. console.error('添加operator_id字段或外键约束失败:', error);
  46. // 如果添加外键失败,至少保留字段
  47. }
  48. }
  49. if (!columnNames.includes('remark')) {
  50. await pool.query(`
  51. ALTER TABLE transactions
  52. ADD COLUMN remark VARCHAR(255) DEFAULT NULL COMMENT '备注' AFTER time
  53. `);
  54. console.log('添加remark字段成功');
  55. }
  56. }
  57. } catch (error) {
  58. console.error('创建/更新交易表失败:', error);
  59. throw error;
  60. }
  61. };
  62. // 初始化表
  63. createTransactionTable().catch(error => {
  64. console.error('初始化交易表失败:', error);
  65. });
  66. // 交易相关方法
  67. const Transaction = {
  68. // 获取交易列表
  69. findAll: async (query = {}, page = 1, limit = 10) => {
  70. try {
  71. console.log('查询参数:', query);
  72. let sql = `
  73. SELECT
  74. t.*,
  75. u.username as operator_name
  76. FROM transactions t
  77. LEFT JOIN users u ON t.operator_id = u.id
  78. WHERE 1=1
  79. `;
  80. const params = [];
  81. if (query.startDate) {
  82. sql += ' AND t.time >= ?';
  83. params.push(query.startDate);
  84. }
  85. if (query.endDate) {
  86. sql += ' AND t.time <= ?';
  87. params.push(query.endDate);
  88. }
  89. if (query.type) {
  90. sql += ' AND t.type = ?';
  91. params.push(query.type);
  92. }
  93. if (query.groupId) {
  94. sql += ' AND t.group_id = ?';
  95. params.push(query.groupId);
  96. }
  97. console.log('SQL查询:', sql);
  98. console.log('参数:', params);
  99. // 获取总数
  100. const countSql = `
  101. SELECT COUNT(*) as total
  102. FROM transactions t
  103. WHERE 1=1
  104. ${query.startDate ? 'AND t.time >= ?' : ''}
  105. ${query.endDate ? 'AND t.time <= ?' : ''}
  106. ${query.type ? 'AND t.type = ?' : ''}
  107. ${query.groupId ? 'AND t.group_id = ?' : ''}
  108. `;
  109. const [countResult] = await pool.query(countSql, params);
  110. const total = countResult[0]?.total || 0;
  111. // 获取分页数据
  112. sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
  113. params.push(limit, (page - 1) * limit);
  114. const [rows] = await pool.query(sql, params);
  115. console.log('查询结果数量:', rows.length);
  116. return {
  117. transactions: rows,
  118. total,
  119. page: parseInt(page),
  120. pages: Math.ceil(total / limit)
  121. };
  122. } catch (error) {
  123. console.error('查询交易列表失败:', error);
  124. throw error;
  125. }
  126. },
  127. // 创建交易
  128. create: async (transactionData) => {
  129. const [result] = await pool.query(
  130. 'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id) VALUES (?, ?, ?, ?, ?, ?)',
  131. [
  132. transactionData.groupId,
  133. transactionData.groupName,
  134. transactionData.type,
  135. transactionData.amount,
  136. transactionData.remark || null,
  137. transactionData.operatorId
  138. ]
  139. );
  140. return result.insertId;
  141. },
  142. // 删除交易
  143. delete: async (id) => {
  144. await pool.query('DELETE FROM transactions WHERE id = ?', [id]);
  145. },
  146. // 获取仪表板数据
  147. getDashboardData: async () => {
  148. const today = new Date();
  149. today.setHours(0, 0, 0, 0);
  150. const [
  151. totalGroups,
  152. totalTransactions,
  153. totalAmount,
  154. todayTransactions,
  155. recentTransactions
  156. ] = await Promise.all([
  157. pool.query('SELECT COUNT(*) as count FROM groups').then(([rows]) => rows[0].count),
  158. pool.query('SELECT COUNT(*) as count FROM transactions').then(([rows]) => rows[0].count),
  159. pool.query('SELECT SUM(amount) as total FROM transactions').then(([rows]) => rows[0].total || 0),
  160. pool.query('SELECT COUNT(*) as count FROM transactions WHERE time >= ?', [today]).then(([rows]) => rows[0].count),
  161. pool.query(`
  162. SELECT
  163. t.*,
  164. u.username as operator_name
  165. FROM transactions t
  166. LEFT JOIN users u ON t.operator_id = u.id
  167. ORDER BY t.time DESC
  168. LIMIT 5
  169. `).then(([rows]) => rows)
  170. ]);
  171. // 获取活跃群组
  172. const [activeGroups] = await pool.query(`
  173. SELECT
  174. g.group_name as name,
  175. COUNT(t.id) as totalTransactions,
  176. SUM(CASE WHEN t.time >= ? THEN 1 ELSE 0 END) as todayTransactions
  177. FROM groups g
  178. LEFT JOIN transactions t ON g.group_id = t.group_id
  179. GROUP BY g.id
  180. ORDER BY todayTransactions DESC
  181. LIMIT 5
  182. `, [today]);
  183. return {
  184. totalGroups,
  185. totalTransactions,
  186. totalAmount,
  187. todayTransactions,
  188. recentTransactions,
  189. activeGroups
  190. };
  191. },
  192. // 入款方法
  193. deposit: async (transactionData) => {
  194. try {
  195. const id = await Transaction.create({
  196. groupId: transactionData.groupId,
  197. groupName: transactionData.groupName,
  198. type: 'deposit',
  199. amount: parseFloat(transactionData.amount)
  200. });
  201. const transaction = await Transaction.findById(id);
  202. if (transaction) {
  203. return {
  204. success: true,
  205. transaction,
  206. message: '入款记录创建成功'
  207. };
  208. } else {
  209. return {
  210. success: false,
  211. message: '入款记录创建失败'
  212. };
  213. }
  214. } catch (error) {
  215. console.error('入款记录创建失败:', error);
  216. return {
  217. success: false,
  218. message: '入款记录创建失败,请稍后重试'
  219. };
  220. }
  221. },
  222. // 出款方法
  223. withdrawal: async (transactionData) => {
  224. try {
  225. const id = await Transaction.create({
  226. groupId: transactionData.groupId,
  227. groupName: transactionData.groupName,
  228. type: 'withdrawal',
  229. amount: parseFloat(transactionData.amount)
  230. });
  231. const transaction = await Transaction.findById(id);
  232. if (transaction) {
  233. return {
  234. success: true,
  235. transaction,
  236. message: '出款记录创建成功'
  237. };
  238. } else {
  239. return {
  240. success: false,
  241. message: '出款记录创建失败'
  242. };
  243. }
  244. } catch (error) {
  245. console.error('出款记录创建失败:', error);
  246. return {
  247. success: false,
  248. message: '出款记录创建失败,请稍后重试'
  249. };
  250. }
  251. },
  252. // 查询群组账单
  253. getGroupTransactions: async (groupId, options = {}) => {
  254. const {
  255. startDate,
  256. endDate,
  257. type,
  258. page = 1,
  259. limit = 10
  260. } = options;
  261. let sql = `
  262. SELECT
  263. t.*,
  264. u.username as operator_name,
  265. DATE_FORMAT(t.time, '%Y-%m-%d %H:%i:%s') as formatted_time
  266. FROM transactions t
  267. LEFT JOIN users u ON t.operator_id = u.id
  268. WHERE t.group_id = ?
  269. `;
  270. const params = [groupId];
  271. if (startDate) {
  272. sql += ' AND t.time >= ?';
  273. params.push(startDate);
  274. }
  275. if (endDate) {
  276. sql += ' AND t.time <= ?';
  277. params.push(endDate);
  278. }
  279. if (type) {
  280. sql += ' AND t.type = ?';
  281. params.push(type);
  282. }
  283. // 获取总数
  284. const countSql = `
  285. SELECT COUNT(*) as total
  286. FROM transactions t
  287. WHERE 1=1
  288. ${startDate ? 'AND t.time >= ?' : ''}
  289. ${endDate ? 'AND t.time <= ?' : ''}
  290. ${type ? 'AND t.type = ?' : ''}
  291. ${groupId ? 'AND t.group_id = ?' : ''}
  292. `;
  293. const [countResult] = await pool.query(countSql, params);
  294. const total = countResult[0].total;
  295. // 获取分页数据
  296. sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
  297. params.push(limit, (page - 1) * limit);
  298. const [rows] = await pool.query(sql, params);
  299. // 计算总入款和总出款
  300. const [summary] = await pool.query(`
  301. SELECT
  302. SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END) as total_deposit,
  303. SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END) as total_withdrawal
  304. FROM transactions
  305. WHERE group_id = ?
  306. ${startDate ? 'AND time >= ?' : ''}
  307. ${endDate ? 'AND time <= ?' : ''}
  308. `, [groupId, ...(startDate ? [startDate] : []), ...(endDate ? [endDate] : [])]);
  309. return {
  310. transactions: rows,
  311. total,
  312. page: parseInt(page),
  313. pages: Math.ceil(total / limit),
  314. summary: {
  315. totalDeposit: summary[0].total_deposit || 0,
  316. totalWithdrawal: summary[0].total_withdrawal || 0,
  317. balance: (summary[0].total_deposit || 0) - (summary[0].total_withdrawal || 0)
  318. }
  319. };
  320. },
  321. // 根据ID查找交易记录
  322. findById: async (id) => {
  323. try {
  324. const [rows] = await pool.query(`
  325. SELECT
  326. t.*,
  327. u.username as operator_name
  328. FROM transactions t
  329. LEFT JOIN users u ON t.operator_id = u.id
  330. WHERE t.id = ?
  331. `, [id]);
  332. return rows[0] || null;
  333. } catch (error) {
  334. console.error('查询交易记录失败:', error);
  335. return null;
  336. }
  337. }
  338. };
  339. module.exports = Transaction;