Przeglądaj źródła

指定费率汇率

Taio_O 3 tygodni temu
rodzic
commit
c7d2617a08
4 zmienionych plików z 194 dodań i 133 usunięć
  1. 3 5
      admin/config/initDb.js
  2. 77 13
      admin/index.js
  3. 96 113
      admin/models/Transaction.js
  4. 18 2
      admin/views/transactions.html

+ 3 - 5
admin/config/initDb.js

@@ -60,7 +60,7 @@ async function initDatabase() {
 
         // 创建入款出款记录表
         await pool.query(`
-            CREATE TABLE IF NOT EXISTS money_records (
+            CREATE TABLE IF NOT EXISTS transactions (
                 id INT AUTO_INCREMENT PRIMARY KEY,
                 time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                 amount DECIMAL(10,2) NOT NULL COMMENT '金额',
@@ -69,10 +69,8 @@ async function initDatabase() {
                 group_id VARCHAR(50) NOT NULL COMMENT '关联群组ID',
                 type ENUM('deposit', 'withdrawal') NOT NULL COMMENT '类型:入款/出款',
                 status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending' COMMENT '状态',
-                in_fee_rate DECIMAL(5,2) DEFAULT 0.00 COMMENT '入款费率',
-                in_exchange_rate DECIMAL(10,4) DEFAULT 1.0000 COMMENT '入款汇率',
-                out_fee_rate DECIMAL(5,2) DEFAULT 0.00 COMMENT '出款费率',
-                out_exchange_rate DECIMAL(10,4) DEFAULT 1.0000 COMMENT '出款汇率',
+                fee_rate DECIMAL(5,2) DEFAULT NULL COMMENT '费率',
+                exchange_rate DECIMAL(10,4) DEFAULT NULL COMMENT '汇率',
                 FOREIGN KEY (operator_id) REFERENCES users(id),
                 FOREIGN KEY (responder_id) REFERENCES users(id),
                 FOREIGN KEY (group_id) REFERENCES groups(group_id),

+ 77 - 13
admin/index.js

@@ -115,13 +115,28 @@ bot.on('message', async (msg) => {
 
     // 处理入款命令
     if (text.startsWith('+')) {
-        const amount = parseFloat(text.substring(1));
+        let amount, exchangeRate, feeRate;
+        const parts = text.substring(1).split('/');
+        amount = parseFloat(parts[0]);
+        
+        // 如果指定了汇率,则使用指定的汇率
+        if (parts.length > 1) {
+            exchangeRate = parseFloat(parts[1]);
+        }
+        
+        // 如果指定了费率,则使用指定的费率
+        if (parts.length > 2) {
+            feeRate = parseFloat(parts[2]);
+        }
+
         if (!isNaN(amount)) {
             const transactionData = {
                 groupId: msg.chat.id.toString(),
                 groupName: msg.chat.title || '未命名群组',
                 amount: amount,
-                type: 'deposit'
+                type: 'deposit',
+                exchangeRate: exchangeRate, // 添加汇率字段
+                feeRate: feeRate // 添加费率字段
             };
 
             try {
@@ -149,13 +164,28 @@ bot.on('message', async (msg) => {
     } 
     // 处理入款修正命令
     else if (text.startsWith('-') && !text.includes('下发')) {
-        const amount = parseFloat(text.substring(1));
+        let amount, exchangeRate, feeRate;
+        const parts = text.substring(1).split('/');
+        amount = parseFloat(parts[0]);
+        
+        // 如果指定了汇率,则使用指定的汇率
+        if (parts.length > 1) {
+            exchangeRate = parseFloat(parts[1]);
+        }
+        
+        // 如果指定了费率,则使用指定的费率
+        if (parts.length > 2) {
+            feeRate = parseFloat(parts[2]);
+        }
+
         if (!isNaN(amount)) {
             const transactionData = {
                 groupId: msg.chat.id.toString(),
                 groupName: msg.chat.title || '未命名群组',
                 amount: -amount,
-                type: 'deposit'
+                type: 'deposit',
+                exchangeRate: exchangeRate, // 添加汇率字段
+                feeRate: feeRate // 添加费率字段
             };
 
             try {
@@ -183,13 +213,28 @@ bot.on('message', async (msg) => {
     }
     // 处理回款命令
     else if (text.startsWith('下发')) {
-        const amount = parseFloat(text.replace(/[^0-9.-]/g, ''));
+        let amount, exchangeRate, feeRate;
+        const parts = text.replace(/[^0-9./-]/g, '').split('/');
+        amount = parseFloat(parts[0]);
+        
+        // 如果指定了汇率,则使用指定的汇率
+        if (parts.length > 1) {
+            exchangeRate = parseFloat(parts[1]);
+        }
+        
+        // 如果指定了费率,则使用指定的费率
+        if (parts.length > 2) {
+            feeRate = parseFloat(parts[2]);
+        }
+
         if (!isNaN(amount)) {
             const transactionData = {
                 groupId: msg.chat.id.toString(),
                 groupName: msg.chat.title || '未命名群组',
                 amount: amount,
-                type: 'withdrawal'
+                type: 'withdrawal',
+                exchangeRate: exchangeRate, // 添加汇率字段
+                feeRate: feeRate // 添加费率字段
             };
 
             try {
@@ -217,13 +262,28 @@ bot.on('message', async (msg) => {
     }
     // 处理回款修正命令
     else if (text.startsWith('下发-')) {
-        const amount = parseFloat(text.replace(/[^0-9.-]/g, ''));
+        let amount, exchangeRate, feeRate;
+        const parts = text.replace(/[^0-9./-]/g, '').split('/');
+        amount = parseFloat(parts[0]);
+        
+        // 如果指定了汇率,则使用指定的汇率
+        if (parts.length > 1) {
+            exchangeRate = parseFloat(parts[1]);
+        }
+        
+        // 如果指定了费率,则使用指定的费率
+        if (parts.length > 2) {
+            feeRate = parseFloat(parts[2]);
+        }
+
         if (!isNaN(amount)) {
             const transactionData = {
                 groupId: msg.chat.id.toString(),
                 groupName: msg.chat.title || '未命名群组',
                 amount: -amount,
-                type: 'withdrawal'
+                type: 'withdrawal',
+                exchangeRate: exchangeRate, // 添加汇率字段
+                feeRate: feeRate // 添加费率字段
             };
 
             try {
@@ -539,10 +599,14 @@ async function generateBillMessage(chatId) {
 
         // 获取机器人加入后的交易记录
         const [records] = await pool.query(`
-            SELECT * FROM transactions 
-            WHERE group_id = ? 
-            AND DATE(time) = CURDATE()
-            ORDER BY time DESC
+            SELECT t.*, 
+                   COALESCE(t.fee_rate, g.in_fee_rate) as fee_rate,
+                   COALESCE(t.exchange_rate, g.in_exchange_rate) as exchange_rate
+            FROM transactions t
+            LEFT JOIN groups g ON t.group_id = g.group_id
+            WHERE t.group_id = ? 
+            AND DATE(t.time) = CURDATE()
+            ORDER BY t.time DESC
         `, [chatId.toString()]);
 
         if (!records || records.length === 0) {
@@ -561,7 +625,7 @@ async function generateBillMessage(chatId) {
 
         // 获取当前日期
         const today = new Date();
-        const version = 'v29'; // 版本号
+        const version = 'v29';
         const dateStr = today.toISOString().split('T')[0].replace(/-/g, '-');
 
         let message = `当前版本:<b>${dateStr}:${version}</b>\n\n`;

+ 96 - 113
admin/models/Transaction.js

@@ -17,116 +17,72 @@ const createTransactionTable = async () => {
                     time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                     remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
                     operator_id INT NOT NULL COMMENT '操作人ID',
+                    fee_rate DECIMAL(5,2) DEFAULT NULL COMMENT '费率',
+                    exchange_rate DECIMAL(10,4) DEFAULT NULL COMMENT '汇率',
                     INDEX idx_group_time (group_id, time),
                     FOREIGN KEY (operator_id) REFERENCES users(id)
                 )
             `);
             console.log('交易表创建成功');
-        } else {
-            // 如果表存在,检查是否需要添加新字段
-            const [columns] = await pool.query('SHOW COLUMNS FROM transactions');
-            const columnNames = columns.map(col => col.Field);
-            
-            // 检查并添加缺失的字段
-            if (!columnNames.includes('operator_id')) {
-                try {
-                    // 先添加字段
-                    await pool.query(`
-                        ALTER TABLE transactions 
-                        ADD COLUMN operator_id INT NOT NULL DEFAULT 1 COMMENT '操作人ID' AFTER remark
-                    `);
-                    console.log('添加operator_id字段成功');
-
-                    // 然后添加外键约束
-                    await pool.query(`
-                        ALTER TABLE transactions 
-                        ADD CONSTRAINT fk_transaction_operator 
-                        FOREIGN KEY (operator_id) REFERENCES users(id)
-                    `);
-                    console.log('添加外键约束成功');
-                } catch (error) {
-                    console.error('添加operator_id字段或外键约束失败:', error);
-                    // 如果添加外键失败,至少保留字段
-                }
-            }
-            
-            if (!columnNames.includes('remark')) {
-                await pool.query(`
-                    ALTER TABLE transactions 
-                    ADD COLUMN remark VARCHAR(255) DEFAULT NULL COMMENT '备注' AFTER time
-                `);
-                console.log('添加remark字段成功');
-            }
         }
     } catch (error) {
-        console.error('创建/更新交易表失败:', error);
+        console.error('创建交易表失败:', error);
         throw error;
     }
 };
 
 // 初始化表
-createTransactionTable().catch(error => {
-    console.error('初始化交易表失败:', error);
-});
+createTransactionTable();
 
-// 交易相关方法
 const Transaction = {
     // 获取交易列表
     findAll: async (query = {}, page = 1, limit = 10) => {
         try {
-            console.log('查询参数:', query);
+            const offset = (page - 1) * limit;
             let sql = `
                 SELECT 
                     t.*,
-                    u.username as operator_name
+                    u.username as operator_name,
+                    g.group_name
                 FROM transactions t
                 LEFT JOIN users u ON t.operator_id = u.id
+                LEFT JOIN groups g ON t.group_id = g.group_id
                 WHERE 1=1
             `;
             const params = [];
 
+            if (query.groupId) {
+                sql += ' AND t.group_id = ?';
+                params.push(query.groupId);
+            }
+
+            if (query.type) {
+                sql += ' AND t.type = ?';
+                params.push(query.type);
+            }
+
             if (query.startDate) {
                 sql += ' AND DATE(t.time) >= ?';
                 params.push(query.startDate);
             }
+
             if (query.endDate) {
                 sql += ' AND DATE(t.time) <= ?';
                 params.push(query.endDate);
             }
-            if (query.type) {
-                sql += ' AND t.type = ?';
-                params.push(query.type);
-            }
-            if (query.groupId) {
-                sql += ' AND t.group_id = ?';
-                params.push(query.groupId);
-            }
-
-            // console.log('SQL查询:', sql);
-            // console.log('参数:', params);
 
-            // 获取总数
-            const countSql = `
-                SELECT COUNT(*) as total 
-                FROM transactions t
-                WHERE 1=1
-                ${query.startDate ? 'AND DATE(t.time) >= ?' : ''}
-                ${query.endDate ? 'AND DATE(t.time) <= ?' : ''}
-                ${query.type ? 'AND t.type = ?' : ''}
-                ${query.groupId ? 'AND t.group_id = ?' : ''}
-            `;
-            
-            const [countResult] = await pool.query(countSql, params);
-            const total = countResult[0]?.total || 0;
-            // console.log('查询总数:', total);
+            // 获取总记录数
+            const [countResult] = await pool.query(
+                sql.replace('t.*,', 'COUNT(*) as total,'),
+                params
+            );
+            const total = countResult[0].total;
 
-            // 获取分页数据
+            // 添加排序和分页
             sql += ' ORDER BY t.time DESC LIMIT ? OFFSET ?';
-            params.push(limit, (page - 1) * limit);
+            params.push(limit, offset);
 
             const [rows] = await pool.query(sql, params);
-            // console.log('查询结果数量:', rows.length);
-            // console.log('查询结果:', rows);
 
             return {
                 transactions: rows,
@@ -142,18 +98,45 @@ const Transaction = {
 
     // 创建交易
     create: async (transactionData) => {
-        const [result] = await pool.query(
-            'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id) VALUES (?, ?, ?, ?, ?, ?)',
-            [
-                transactionData.groupId,
-                transactionData.groupName,
-                transactionData.type,
-                transactionData.amount,
-                transactionData.remark || null,
-                transactionData.operatorId
-            ]
-        );
-        return result.insertId;
+        try {
+            // 获取群组的默认费率和汇率
+            const [groupInfo] = await pool.query(
+                'SELECT in_fee_rate, in_exchange_rate, out_fee_rate, out_exchange_rate FROM groups WHERE group_id = ?',
+                [transactionData.groupId]
+            );
+
+            if (!groupInfo || groupInfo.length === 0) {
+                throw new Error('群组不存在');
+            }
+
+            // 根据交易类型选择对应的费率和汇率
+            const defaultFeeRate = transactionData.type === 'deposit' ? 
+                groupInfo[0].in_fee_rate : groupInfo[0].out_fee_rate;
+            const defaultExchangeRate = transactionData.type === 'deposit' ? 
+                groupInfo[0].in_exchange_rate : groupInfo[0].out_exchange_rate;
+
+            // 使用指定的费率和汇率,如果没有指定则使用默认值
+            const feeRate = transactionData.feeRate || defaultFeeRate;
+            const exchangeRate = transactionData.exchangeRate || defaultExchangeRate;
+
+            const [result] = await pool.query(
+                'INSERT INTO transactions (group_id, group_name, type, amount, remark, operator_id, fee_rate, exchange_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
+                [
+                    transactionData.groupId,
+                    transactionData.groupName,
+                    transactionData.type,
+                    transactionData.amount,
+                    transactionData.remark || null,
+                    transactionData.operatorId,
+                    feeRate,
+                    exchangeRate
+                ]
+            );
+            return result.insertId;
+        } catch (error) {
+            console.error('创建交易记录失败:', error);
+            throw error;
+        }
     },
 
     // 删除交易
@@ -164,26 +147,29 @@ const Transaction = {
     // 获取仪表板数据
     getDashboardData: async () => {
         try {
-            const today = new Date();
-            today.setHours(0, 0, 0, 0);
-
             // 获取总群组数
-            const [totalGroupsResult] = await pool.query('SELECT COUNT(*) as count FROM groups');
-            const totalGroups = totalGroupsResult[0].count;
+            const [groupResult] = await pool.query('SELECT COUNT(*) as total FROM groups WHERE is_active = true');
+            const totalGroups = groupResult[0].total;
 
             // 获取总交易数
-            const [totalTransactionsResult] = await pool.query('SELECT COUNT(*) as count FROM transactions');
-            const totalTransactions = totalTransactionsResult[0].count;
+            const [transactionResult] = await pool.query('SELECT COUNT(*) as total FROM transactions');
+            const totalTransactions = transactionResult[0].total;
 
             // 获取总金额
-            const [totalAmountResult] = await pool.query('SELECT SUM(amount) as total FROM transactions');
-            const totalAmount = totalAmountResult[0].total || 0;
+            const [amountResult] = await pool.query(`
+                SELECT 
+                    SUM(CASE WHEN type = 'deposit' THEN amount ELSE -amount END) as total
+                FROM transactions
+            `);
+            const totalAmount = amountResult[0].total || 0;
 
             // 获取今日交易数
-            const [todayTransactionsResult] = await pool.query(
-                'SELECT COUNT(*) as count FROM transactions WHERE DATE(time) = CURDATE()'
-            );
-            const todayTransactions = todayTransactionsResult[0].count;
+            const [todayResult] = await pool.query(`
+                SELECT COUNT(*) as total 
+                FROM transactions 
+                WHERE DATE(time) = CURDATE()
+            `);
+            const todayTransactions = todayResult[0].total;
 
             // 获取最近交易
             const [recentTransactions] = await pool.query(`
@@ -192,32 +178,25 @@ const Transaction = {
                     u.username as operator_name
                 FROM transactions t
                 LEFT JOIN users u ON t.operator_id = u.id
-                ORDER BY t.time DESC 
+                ORDER BY t.time DESC
                 LIMIT 5
             `);
 
             // 获取活跃群组
             const [activeGroups] = await pool.query(`
                 SELECT 
-                    g.group_name as name,
-                    COUNT(t.id) as totalTransactions,
-                    SUM(CASE WHEN DATE(t.time) = CURDATE() THEN 1 ELSE 0 END) as todayTransactions
+                    g.*,
+                    COUNT(t.id) as transaction_count,
+                    SUM(CASE WHEN t.type = 'deposit' THEN t.amount ELSE 0 END) as total_deposit,
+                    SUM(CASE WHEN t.type = 'withdrawal' THEN t.amount ELSE 0 END) as total_withdrawal
                 FROM groups g
                 LEFT JOIN transactions t ON g.group_id = t.group_id
-                GROUP BY g.id, g.group_name
-                ORDER BY todayTransactions DESC, totalTransactions DESC
+                WHERE g.is_active = true
+                GROUP BY g.id
+                ORDER BY transaction_count DESC
                 LIMIT 5
             `);
 
-            console.log('仪表板数据:', {
-                totalGroups,
-                totalTransactions,
-                totalAmount,
-                todayTransactions,
-                recentTransactions,
-                activeGroups
-            });
-
             return {
                 totalGroups,
                 totalTransactions,
@@ -240,7 +219,9 @@ const Transaction = {
                 groupName: transactionData.groupName,
                 type: 'deposit',
                 amount: parseFloat(transactionData.amount),
-                operatorId: transactionData.operatorId || 1  // 添加默认操作者ID
+                operatorId: transactionData.operatorId || 1,  // 添加默认操作者ID
+                feeRate: transactionData.feeRate,
+                exchangeRate: transactionData.exchangeRate
             });
 
             const transaction = await Transaction.findById(id);
@@ -273,7 +254,9 @@ const Transaction = {
                 groupName: transactionData.groupName,
                 type: 'withdrawal',
                 amount: parseFloat(transactionData.amount),
-                operatorId: transactionData.operatorId || 1  // 添加默认操作者ID
+                operatorId: transactionData.operatorId || 1,  // 添加默认操作者ID
+                feeRate: transactionData.feeRate,
+                exchangeRate: transactionData.exchangeRate
             });
 
             const transaction = await Transaction.findById(id);

+ 18 - 2
admin/views/transactions.html

@@ -158,6 +158,8 @@
                                         <th>群组</th>
                                         <th>类型</th>
                                         <th>金额</th>
+                                        <th>费率</th>
+                                        <th>汇率</th>
                                         <th>操作人</th>
                                         <th>备注</th>
                                         <th>操作</th>
@@ -203,6 +205,14 @@
                             <label for="amount" class="form-label">金额</label>
                             <input type="number" class="form-control" id="amount" step="0.01" required>
                         </div>
+                        <div class="mb-3">
+                            <label for="feeRate" class="form-label">费率 (%)</label>
+                            <input type="number" class="form-control" id="feeRate" step="0.01">
+                        </div>
+                        <div class="mb-3">
+                            <label for="exchangeRate" class="form-label">汇率</label>
+                            <input type="number" class="form-control" id="exchangeRate" step="0.0001">
+                        </div>
                         <div class="mb-3">
                             <label for="remark" class="form-label">备注</label>
                             <input type="text" class="form-control" id="remark">
@@ -326,7 +336,7 @@
         function updateTransactionsList(transactions) {
             const transactionsList = document.getElementById('transactionsList');
             if (!transactions || transactions.length === 0) {
-                transactionsList.innerHTML = '<tr><td colspan="7" class="text-center">暂无数据</td></tr>';
+                transactionsList.innerHTML = '<tr><td colspan="9" class="text-center">暂无数据</td></tr>';
                 return;
             }
             
@@ -339,7 +349,9 @@
                             ${transaction.type === 'deposit' ? '入款' : '出款'}
                         </span>
                     </td>
-                    <td>¥${parseFloat(transaction.amount).toFixed(2)}</td>
+                    <td>¥${Math.abs(parseFloat(transaction.amount)).toFixed(2)}</td>
+                    <td>${transaction.fee_rate ? parseFloat(transaction.fee_rate).toFixed(2) + '%' : '-'}</td>
+                    <td>${transaction.exchange_rate ? parseFloat(transaction.exchange_rate).toFixed(4) : '-'}</td>
                     <td>${transaction.operator_name || '-'}</td>
                     <td>${transaction.remark || '-'}</td>
                     <td>
@@ -389,6 +401,8 @@
             const groupId = document.getElementById('transactionGroupId').value;
             const type = document.getElementById('transactionType').value;
             const amount = parseFloat(document.getElementById('amount').value);
+            const feeRate = document.getElementById('feeRate').value ? parseFloat(document.getElementById('feeRate').value) : null;
+            const exchangeRate = document.getElementById('exchangeRate').value ? parseFloat(document.getElementById('exchangeRate').value) : null;
             const remark = document.getElementById('remark').value;
 
             try {
@@ -403,6 +417,8 @@
                         groupId: groupId,
                         type: type,
                         amount: amount,
+                        feeRate: feeRate,
+                        exchangeRate: exchangeRate,
                         remark: remark,
                         operatorId: window.currentUser._id
                     })