2025-08-26 10:06:23 +08:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
const db = require('../database');
|
|
|
|
|
|
const { auth, adminAuth } = require('../middleware/auth');
|
|
|
|
|
|
const logger = require('../config/logger');
|
|
|
|
|
|
const matchingService = require('../services/matchingService');
|
|
|
|
|
|
const dayjs = require('dayjs');
|
|
|
|
|
|
|
2025-08-28 09:14:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* tags:
|
|
|
|
|
|
* name: MatchingAdmin
|
|
|
|
|
|
* description: 匹配订单管理员相关接口
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* components:
|
|
|
|
|
|
* schemas:
|
|
|
|
|
|
* UnreasonableMatch:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* allocation_id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 分配ID
|
|
|
|
|
|
* from_user_id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 发送方用户ID
|
|
|
|
|
|
* to_user_id:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 接收方用户ID
|
|
|
|
|
|
* amount:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* description: 分配金额
|
|
|
|
|
|
* status:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* enum: [pending, confirmed, rejected, cancelled]
|
|
|
|
|
|
* description: 分配状态
|
|
|
|
|
|
* to_username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 接收方用户名
|
|
|
|
|
|
* to_user_balance:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* description: 接收方用户余额
|
|
|
|
|
|
* from_username:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 发送方用户名
|
|
|
|
|
|
* from_user_balance:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* description: 发送方用户余额
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/matching-admin/unreasonable-matches:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取不合理的匹配记录(正余额用户被匹配的情况)
|
|
|
|
|
|
* tags: [MatchingAdmin]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: page
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* default: 1
|
|
|
|
|
|
* description: 页码
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: limit
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* default: 20
|
|
|
|
|
|
* description: 每页数量
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 成功获取不合理匹配记录
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* matches:
|
|
|
|
|
|
* type: array
|
|
|
|
|
|
* items:
|
|
|
|
|
|
* $ref: '#/components/schemas/UnreasonableMatch'
|
|
|
|
|
|
* pagination:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* page:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* limit:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* total:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* totalPages:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 403:
|
|
|
|
|
|
* description: 无管理员权限
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器错误
|
|
|
|
|
|
*/
|
2025-08-26 10:06:23 +08:00
|
|
|
|
router.get('/unreasonable-matches', auth, adminAuth, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const page = parseInt(req.query.page) || 1;
|
|
|
|
|
|
const limit = parseInt(req.query.limit) || 20;
|
|
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找正余额用户被匹配的情况
|
|
|
|
|
|
const query = `SELECT
|
|
|
|
|
|
oa.id as allocation_id,
|
|
|
|
|
|
oa.from_user_id,
|
|
|
|
|
|
oa.to_user_id,
|
|
|
|
|
|
oa.amount,
|
|
|
|
|
|
oa.status,
|
|
|
|
|
|
oa.outbound_date,
|
|
|
|
|
|
oa.created_at,
|
|
|
|
|
|
u_to.username as to_username,
|
|
|
|
|
|
u_to.balance as to_user_balance,
|
|
|
|
|
|
u_from.username as from_username,
|
|
|
|
|
|
u_from.balance as from_user_balance,
|
|
|
|
|
|
mo.amount as total_order_amount
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
JOIN users u_from ON oa.from_user_id = u_from.id
|
|
|
|
|
|
JOIN matching_orders mo ON oa.id = mo.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation'
|
|
|
|
|
|
AND u_to.balance > 0
|
|
|
|
|
|
AND u_to.is_system_account = FALSE
|
|
|
|
|
|
AND oa.status IN ('pending', 'confirmed')
|
|
|
|
|
|
ORDER BY oa.created_at DESC
|
|
|
|
|
|
LIMIT ${offset}, ${limit}`;
|
|
|
|
|
|
|
|
|
|
|
|
const countQuery = `SELECT COUNT(*) as total
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation'
|
|
|
|
|
|
AND u_to.balance > 0
|
|
|
|
|
|
AND u_to.is_system_account = FALSE
|
|
|
|
|
|
AND oa.status IN ('pending', 'confirmed')`;
|
|
|
|
|
|
|
|
|
|
|
|
const unreasonableMatches = await db.executeQuery(query);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取总数
|
|
|
|
|
|
const countResult = await db.executeQuery(countQuery);
|
|
|
|
|
|
|
|
|
|
|
|
const total = countResult[0].total;
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
matches: unreasonableMatches,
|
|
|
|
|
|
pagination: {
|
|
|
|
|
|
page,
|
|
|
|
|
|
limit,
|
|
|
|
|
|
total,
|
|
|
|
|
|
totalPages: Math.ceil(total / limit)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取不合理匹配记录失败:', error);
|
|
|
|
|
|
res.status(500).json({ message: '获取不合理匹配记录失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-28 09:14:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/matching-admin/matching-stats:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: 获取匹配统计信息
|
|
|
|
|
|
* tags: [MatchingAdmin]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 成功获取匹配统计信息
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* currentStats:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* unreasonable_matches:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* reasonable_matches:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* system_matches:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* unreasonable_amount:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* reasonable_amount:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* yesterdayStats:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* total_outbound:
|
|
|
|
|
|
* type: number
|
|
|
|
|
|
* unique_amounts:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 403:
|
|
|
|
|
|
* description: 无管理员权限
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器错误
|
|
|
|
|
|
*/
|
2025-08-26 10:06:23 +08:00
|
|
|
|
router.get('/matching-stats', auth, adminAuth, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
// 获取各种统计数据
|
|
|
|
|
|
const stats = await db.executeQuery(
|
|
|
|
|
|
`SELECT
|
|
|
|
|
|
COUNT(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as unreasonable_matches,
|
|
|
|
|
|
COUNT(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as reasonable_matches,
|
|
|
|
|
|
COUNT(CASE WHEN u_to.is_system_account = TRUE AND oa.status IN ('pending', 'confirmed') THEN 1 END) as system_matches,
|
|
|
|
|
|
SUM(CASE WHEN u_to.balance > 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as unreasonable_amount,
|
|
|
|
|
|
SUM(CASE WHEN u_to.balance < 0 AND u_to.is_system_account = FALSE AND oa.status IN ('pending', 'confirmed') THEN oa.amount ELSE 0 END) as reasonable_amount
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation'`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取昨天的匹配验证统计
|
|
|
|
|
|
const yesterdayStr = dayjs().subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
|
|
|
|
|
|
|
|
|
const yesterdayStats = await db.executeQuery(
|
|
|
|
|
|
`SELECT
|
|
|
|
|
|
SUM(oa.amount) as total_outbound,
|
|
|
|
|
|
COUNT(DISTINCT oa.amount) as unique_amounts
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u ON oa.from_user_id = u.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation' AND DATE(oa.outbound_date) = ? AND oa.status = 'confirmed' AND u.is_system_account = FALSE`,
|
|
|
|
|
|
[yesterdayStr]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
currentStats: stats[0],
|
|
|
|
|
|
yesterdayStats: yesterdayStats[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取匹配统计失败:', error);
|
|
|
|
|
|
res.status(500).json({ message: '获取匹配统计失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-28 09:14:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/matching-admin/fix-all-unreasonable:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 批量修复所有不合理匹配
|
|
|
|
|
|
* tags: [MatchingAdmin]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 批量修复完成
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* data:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* fixedCount:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 成功修复的记录数
|
|
|
|
|
|
* errorCount:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 修复失败的记录数
|
|
|
|
|
|
* errors:
|
|
|
|
|
|
* type: array
|
|
|
|
|
|
* items:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* description: 错误信息列表(最多10条)
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 403:
|
|
|
|
|
|
* description: 无管理员权限
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器错误
|
|
|
|
|
|
*/
|
2025-08-26 10:06:23 +08:00
|
|
|
|
router.post('/fix-all-unreasonable', auth, adminAuth, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let fixedCount = 0;
|
|
|
|
|
|
let errorCount = 0;
|
|
|
|
|
|
const errors = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有不合理的匹配记录
|
|
|
|
|
|
const unreasonableMatches = await db.executeQuery(
|
|
|
|
|
|
`SELECT oa.id, oa.from_user_id, oa.to_user_id, oa.amount, u_to.username, u_to.balance
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation'
|
|
|
|
|
|
AND u_to.balance > 0
|
|
|
|
|
|
AND u_to.is_system_account = FALSE
|
|
|
|
|
|
AND oa.status IN ('pending', 'confirmed')
|
|
|
|
|
|
ORDER BY u_to.balance DESC`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (const match of unreasonableMatches) {
|
|
|
|
|
|
const connection = await db.getDB().getConnection();
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.query('START TRANSACTION');
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试重新分配给负余额用户
|
|
|
|
|
|
const usedTargetUsers = new Set([match.to_user_id]);
|
|
|
|
|
|
const newTargetUser = await matchingService.getMatchingTargetExcluding(match.from_user_id, usedTargetUsers);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间
|
|
|
|
|
|
const currentTime = new Date();
|
|
|
|
|
|
|
|
|
|
|
|
if (newTargetUser) {
|
|
|
|
|
|
// 更新分配目标
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE transfers SET to_user_id = ?, updated_at = ? WHERE id = ?',
|
|
|
|
|
|
[newTargetUser, currentTime, match.id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录操作日志
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)',
|
|
|
|
|
|
[req.user.id, match.id, `批量修复:从正余额用户${match.username}(余额${match.balance}元)重新分配${match.amount}元给负余额用户`, currentTime]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
fixedCount++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有可用的负余额用户,取消分配
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?',
|
|
|
|
|
|
[currentTime, match.id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "batch_fix_matching", "allocation", ?, ?, ?)',
|
|
|
|
|
|
[req.user.id, match.id, `批量修复:取消正余额用户${match.username}(余额${match.balance}元)的${match.amount}元分配`, currentTime]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
fixedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await connection.query('COMMIT');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
await connection.query('ROLLBACK');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
errorCount++;
|
|
|
|
|
|
errors.push(`分配ID ${match.id}: ${error.message}`);
|
|
|
|
|
|
console.error(`修复分配${match.id}失败:`, error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: `批量修复完成:成功修复${fixedCount}条记录,失败${errorCount}条记录`,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
fixedCount,
|
|
|
|
|
|
errorCount,
|
|
|
|
|
|
errors: errors.slice(0, 10) // 只返回前10个错误
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('批量修复不合理匹配失败:', error);
|
|
|
|
|
|
res.status(500).json({ message: '批量修复不合理匹配失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-28 09:14:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/matching-admin/confirm-allocation/{allocationId}:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 管理员确认分配
|
|
|
|
|
|
* tags: [MatchingAdmin]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: allocationId
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 分配ID
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 分配确认成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 403:
|
|
|
|
|
|
* description: 无管理员权限
|
|
|
|
|
|
* 404:
|
|
|
|
|
|
* description: 分配不存在或状态不是待处理
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器错误
|
|
|
|
|
|
*/
|
2025-08-26 10:06:23 +08:00
|
|
|
|
router.post('/confirm-allocation/:allocationId', auth, adminAuth, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { allocationId } = req.params;
|
|
|
|
|
|
const adminId = req.user.id;
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await db.getDB().getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.query('START TRANSACTION');
|
|
|
|
|
|
|
|
|
|
|
|
// 检查分配是否存在且状态为pending
|
|
|
|
|
|
const [allocations] = await connection.execute(
|
|
|
|
|
|
`SELECT oa.*, u_from.username as from_username, u_to.username as to_username
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_from ON oa.from_user_id = u_from.id
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`,
|
|
|
|
|
|
[allocationId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (allocations.length === 0) {
|
|
|
|
|
|
await connection.query('ROLLBACK');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(404).json({ message: '分配不存在或状态不是待处理' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const allocation = allocations[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间
|
|
|
|
|
|
const currentTime = new Date();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算3小时后的截止时间
|
|
|
|
|
|
const deadline = new Date();
|
|
|
|
|
|
deadline.setHours(deadline.getHours() + 3);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建转账记录,直接设置为confirmed状态
|
|
|
|
|
|
const transferDescription = `匹配订单 ${allocation.matching_order_id} 第 ${allocation.cycle_number} 轮转账(管理员确认)`;
|
|
|
|
|
|
const [transferResult] = await connection.execute(
|
|
|
|
|
|
`INSERT INTO transfers (from_user_id, to_user_id, amount, transfer_type, status, description, deadline_at, confirmed_at, source_type) VALUES (?, ?, ?, "user_to_user", "confirmed", ?, ?, ?, 'allocation')`,
|
|
|
|
|
|
[
|
|
|
|
|
|
allocation.from_user_id,
|
|
|
|
|
|
allocation.to_user_id,
|
|
|
|
|
|
allocation.amount,
|
|
|
|
|
|
transferDescription,
|
|
|
|
|
|
deadline,
|
|
|
|
|
|
currentTime
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新分配状态为已确认,并关联转账记录
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE transfers SET status = "confirmed", transfer_id = ?, confirmed_at = ?, updated_at = ? WHERE id = ?',
|
|
|
|
|
|
[transferResult.insertId, currentTime, currentTime, allocationId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录管理员操作日志
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "confirm_allocation", "allocation", ?, ?, ?)',
|
|
|
|
|
|
[adminId, allocationId, `管理员确认分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}元`, currentTime]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录确认动作到匹配记录
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'INSERT INTO matching_records (matching_order_id, user_id, action, amount, note) VALUES (?, ?, "confirm", ?, ?)',
|
|
|
|
|
|
[
|
|
|
|
|
|
allocation.matching_order_id,
|
|
|
|
|
|
adminId,
|
|
|
|
|
|
allocation.amount,
|
|
|
|
|
|
'管理员确认分配'
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await connection.query('COMMIT');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '分配已确认'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (innerError) {
|
|
|
|
|
|
await connection.query('ROLLBACK');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
throw innerError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('确认分配失败:', error);
|
|
|
|
|
|
res.status(500).json({ message: error.message || '确认分配失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-08-28 09:14:56 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /api/matching-admin/cancel-allocation/{allocationId}:
|
|
|
|
|
|
* post:
|
|
|
|
|
|
* summary: 管理员取消分配
|
|
|
|
|
|
* tags: [MatchingAdmin]
|
|
|
|
|
|
* security:
|
|
|
|
|
|
* - bearerAuth: []
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: allocationId
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* description: 分配ID
|
|
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
|
|
|
|
|
* description: 分配取消成功
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: object
|
|
|
|
|
|
* properties:
|
|
|
|
|
|
* success:
|
|
|
|
|
|
* type: boolean
|
|
|
|
|
|
* message:
|
|
|
|
|
|
* type: string
|
|
|
|
|
|
* 401:
|
|
|
|
|
|
* description: 未授权
|
|
|
|
|
|
* 403:
|
|
|
|
|
|
* description: 无管理员权限
|
|
|
|
|
|
* 404:
|
|
|
|
|
|
* description: 分配不存在或状态不是待处理
|
|
|
|
|
|
* 500:
|
|
|
|
|
|
* description: 服务器错误
|
|
|
|
|
|
*/
|
|
|
|
|
|
router.post('/cancel-allocation/:allocationId', auth, adminAuth, async (req, res) => {
|
2025-08-26 10:06:23 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const { allocationId } = req.params;
|
|
|
|
|
|
const adminId = req.user.id;
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await db.getDB().getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.query('START TRANSACTION');
|
|
|
|
|
|
|
|
|
|
|
|
// 检查分配是否存在且状态为pending
|
|
|
|
|
|
const [allocations] = await connection.execute(
|
|
|
|
|
|
`SELECT oa.*, u_from.username as from_username, u_to.username as to_username
|
|
|
|
|
|
FROM transfers oa
|
|
|
|
|
|
JOIN users u_from ON oa.from_user_id = u_from.id
|
|
|
|
|
|
JOIN users u_to ON oa.to_user_id = u_to.id
|
|
|
|
|
|
WHERE oa.source_type = 'allocation' AND oa.id = ? AND oa.status = 'pending'`,
|
|
|
|
|
|
[allocationId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (allocations.length === 0) {
|
|
|
|
|
|
await connection.query('ROLLBACK');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(404).json({ message: '分配不存在或状态不是待处理' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const allocation = allocations[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间
|
|
|
|
|
|
const currentTime = new Date();
|
|
|
|
|
|
|
|
|
|
|
|
// 更新分配状态为已取消
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE transfers SET status = "cancelled", updated_at = ? WHERE id = ?',
|
|
|
|
|
|
[currentTime, allocationId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 记录管理员操作日志
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'INSERT INTO admin_operation_logs (admin_id, operation_type, target_type, target_id, description, created_at) VALUES (?, "cancel_allocation", "allocation", ?, ?, ?)',
|
|
|
|
|
|
[adminId, allocationId, `管理员取消分配:${allocation.from_username} -> ${allocation.to_username},金额:${allocation.amount}元`, currentTime]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await connection.query('COMMIT');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '分配已取消'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
} catch (innerError) {
|
|
|
|
|
|
await connection.query('ROLLBACK');
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
throw innerError;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('取消分配失败:', error);
|
|
|
|
|
|
res.status(500).json({ message: error.message || '取消分配失败' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|