Files
gamer/server/routes/records.js
T

239 lines
7.8 KiB
JavaScript
Raw Normal View History

2026-03-12 11:23:10 +08:00
const express = require('express');
const db = require('../db');
const config = require('../config');
const logger = require('../utils/logger');
2026-07-01 16:13:47 +08:00
const {
getMonthRange,
AppError,
validateRequired,
validateNumber,
validateInList,
validatePage,
validateLimit,
validateIsoDate,
optionalString
} = require('../utils/helpers');
2026-03-12 11:23:10 +08:00
const router = express.Router();
// 新增记录
router.post('/', (req, res, next) => {
try {
const { type, category, amount, quantity, unit_price, rebate, boss, partner, source, destination, note, created_at } = req.body;
// 输入验证
validateRequired(req.body, ['type', 'category', 'amount']);
validateInList(type, 'type', ['income', 'expense']);
const allCategories = [...config.categories.income, ...config.categories.expense];
validateInList(category, 'category', allCategories);
2026-07-01 16:13:47 +08:00
// 派单的返点可为负数,因此金额也允许为负数
const validatedAmount = category === '派单'
? validateNumber(amount, '金额')
: validateNumber(amount, '金额', 0);
const validatedQuantity = quantity === undefined || quantity === null || quantity === '' ? null : validateNumber(quantity, '数量', 0);
const validatedUnitPrice = unit_price === undefined || unit_price === null || unit_price === '' ? null : validateNumber(unit_price, '单价', 0);
const validatedRebate = rebate === undefined || rebate === null || rebate === '' ? null : validateNumber(rebate, '返点');
const validatedCreatedAt = validateIsoDate(created_at, '创建时间');
const stmt = db.prepare(`
INSERT INTO records (user_id, type, category, amount, quantity, unit_price, rebate, boss, partner, source, destination, note, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
2026-07-01 16:13:47 +08:00
const result = stmt.run(
req.user.id,
type,
category,
validatedAmount,
validatedQuantity,
validatedUnitPrice,
validatedRebate,
optionalString(boss, '老板', 100),
optionalString(partner, '陪玩', 100),
optionalString(source, '来源', 100),
optionalString(destination, '去向', 100),
optionalString(note, '备注', 500),
validatedCreatedAt
);
logger.info(`用户 ${req.user.username} 新增记录: ${category} ${validatedAmount}`);
res.json({ id: result.lastInsertRowid });
} catch (err) {
next(err);
}
2026-03-12 11:23:10 +08:00
});
// 通用分页查询函数
function queryPagedRecords(userId, year, month, options) {
2026-07-01 16:13:47 +08:00
const page = validatePage(options.page, 1);
const limit = validateLimit(options.limit, config.pagination.defaultLimit, config.pagination.maxLimit);
const offset = (page - 1) * limit;
const { startDate, endDate } = getMonthRange(year, month);
let whereClause = `user_id = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?`;
const params = [userId, startDate, endDate];
if (options.category) {
whereClause += ` AND category = ?`;
params.push(options.category);
}
if (options.type) {
whereClause += ` AND type = ?`;
params.push(options.type);
}
if (options.excludeCategories && options.excludeCategories.length > 0) {
whereClause += ` AND category NOT IN (${options.excludeCategories.map(() => '?').join(',')})`;
params.push(...options.excludeCategories);
}
2026-03-12 11:23:10 +08:00
const total = db.prepare(
`SELECT COUNT(*) as count FROM records WHERE ${whereClause}`
).get(...params).count;
2026-03-12 11:23:10 +08:00
const records = db.prepare(
`SELECT * FROM records WHERE ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`
2026-07-01 16:13:47 +08:00
).all(...params, limit, offset);
2026-03-12 11:23:10 +08:00
2026-07-01 16:13:47 +08:00
return { records, total, page, limit };
}
// 接单列表
router.get('/orders', (req, res) => {
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
2026-03-25 22:12:11 +08:00
category: '接单',
page: req.query.page,
limit: req.query.limit
});
res.json(result);
2026-03-12 11:23:10 +08:00
});
// 红包及其他收入列表
router.get('/other-income', (req, res) => {
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
type: 'income',
2026-03-25 22:12:11 +08:00
excludeCategories: ['接单', '派单'],
page: req.query.page,
limit: req.query.limit
});
res.json(result);
2026-03-12 11:23:10 +08:00
});
// 红包收入列表
router.get('/red-envelope', (req, res) => {
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
type: 'income',
2026-03-25 22:12:11 +08:00
category: '红包收入',
page: req.query.page,
limit: req.query.limit
});
res.json(result);
2026-03-12 11:23:10 +08:00
});
// 奶茶收入列表
router.get('/milk-tea', (req, res) => {
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
type: 'income',
2026-03-25 22:12:11 +08:00
category: '奶茶',
page: req.query.page,
limit: req.query.limit
});
res.json(result);
2026-03-12 11:23:10 +08:00
});
// 派单列表
router.get('/dispatches', (req, res) => {
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
2026-03-25 22:12:11 +08:00
category: '派单',
page: req.query.page,
limit: req.query.limit
});
res.json(result);
2026-03-12 11:23:10 +08:00
});
// 获取记录列表
router.get('/', (req, res) => {
const { type, page = 1, limit = 20 } = req.query;
2026-07-01 16:13:47 +08:00
const validatedPage = validatePage(page, 1);
const validatedLimit = validateLimit(limit, config.pagination.defaultLimit, config.pagination.maxLimit);
const offset = (validatedPage - 1) * validatedLimit;
2026-03-12 11:23:10 +08:00
let sql = 'SELECT * FROM records WHERE user_id = ?';
const params = [req.user.id];
if (type) {
sql += ' AND type = ?';
params.push(type);
}
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
2026-07-01 16:13:47 +08:00
params.push(validatedLimit, offset);
2026-03-12 11:23:10 +08:00
res.json(db.prepare(sql).all(...params));
});
// 更新记录
router.put('/:id', (req, res, next) => {
try {
const { quantity, unit_price, rebate, amount, boss, partner, source, destination, note, created_at } = req.body;
const id = Number(req.params.id);
if (!id || id <= 0) {
throw new AppError('无效的记录ID', 400);
}
2026-07-01 16:13:47 +08:00
// 获取原有记录以判断类别(派单允许负数金额)
const existing = db.prepare('SELECT category FROM records WHERE id = ? AND user_id = ?').get(id, req.user.id);
if (!existing) {
throw new AppError('记录不存在', 404);
}
const validatedAmount = existing.category === '派单'
? validateNumber(amount, '金额')
: validateNumber(amount, '金额', 0);
const validatedQuantity = quantity === undefined || quantity === null || quantity === '' ? null : validateNumber(quantity, '数量', 0);
const validatedUnitPrice = unit_price === undefined || unit_price === null || unit_price === '' ? null : validateNumber(unit_price, '单价', 0);
const validatedRebate = rebate === undefined || rebate === null || rebate === '' ? null : validateNumber(rebate, '返点');
const validatedCreatedAt = validateIsoDate(created_at, '创建时间');
db.prepare(`
UPDATE records SET quantity = ?, unit_price = ?, rebate = ?, amount = ?, boss = ?, partner = ?, source = ?, destination = ?, note = ?, created_at = ?
WHERE id = ? AND user_id = ?
2026-07-01 16:13:47 +08:00
`).run(
validatedQuantity,
validatedUnitPrice,
validatedRebate,
validatedAmount,
optionalString(boss, '老板', 100),
optionalString(partner, '陪玩', 100),
optionalString(source, '来源', 100),
optionalString(destination, '去向', 100),
optionalString(note, '备注', 500),
validatedCreatedAt,
id,
req.user.id
);
logger.debug(`用户 ${req.user.username} 更新记录: ${id}`);
res.json({ success: true });
} catch (err) {
next(err);
}
2026-03-12 11:23:10 +08:00
});
// 删除记录
router.delete('/:id', (req, res, next) => {
try {
const id = Number(req.params.id);
if (!id || id <= 0) {
throw new AppError('无效的记录ID', 400);
}
db.prepare('DELETE FROM records WHERE id = ? AND user_id = ?').run(id, req.user.id);
logger.info(`用户 ${req.user.username} 删除记录: ${id}`);
res.json({ success: true });
} catch (err) {
next(err);
}
2026-03-12 11:23:10 +08:00
});
module.exports = router;