|
|
/**
|
|
|
* 会员控制器
|
|
|
*
|
|
|
* 功能:处理会员管理的业务逻辑
|
|
|
*/
|
|
|
|
|
|
const { Member, Coupon } = require('../models/Member');
|
|
|
const { validateRequired, validateBatch } = require('../utils/validator');
|
|
|
const { success } = require('../utils/response');
|
|
|
const { BusinessError, ValidationError, AuthorizationError } = require('../middleware/errorHandler');
|
|
|
|
|
|
// ========================================
|
|
|
// 会员相关控制器
|
|
|
// ========================================
|
|
|
|
|
|
/**
|
|
|
* 获取会员列表(分页、筛选)
|
|
|
* GET /api/members
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const getMembers = async (req, res, next) => {
|
|
|
try {
|
|
|
const {
|
|
|
page = 1,
|
|
|
pageSize = 10,
|
|
|
level,
|
|
|
search
|
|
|
} = req.query;
|
|
|
|
|
|
const result = await Member.findAll({
|
|
|
page: parseInt(page),
|
|
|
pageSize: parseInt(pageSize),
|
|
|
level,
|
|
|
search
|
|
|
});
|
|
|
|
|
|
res.json(success({
|
|
|
list: result.list,
|
|
|
pagination: {
|
|
|
total: result.total,
|
|
|
page: result.page,
|
|
|
pageSize: result.pageSize,
|
|
|
totalPages: Math.ceil(result.total / result.pageSize)
|
|
|
}
|
|
|
}, '获取会员列表成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取会员详情
|
|
|
* GET /api/members/:userId
|
|
|
*/
|
|
|
const getMemberByUserId = async (req, res, next) => {
|
|
|
try {
|
|
|
const { userId } = req.params;
|
|
|
|
|
|
// 权限控制:普通用户只能查看自己的会员信息
|
|
|
if (req.user.role === 'customer' && parseInt(userId) !== req.user.id) {
|
|
|
throw new AuthorizationError('您没有权限查看此会员信息');
|
|
|
}
|
|
|
|
|
|
const member = await Member.findByUserId(userId);
|
|
|
if (!member) {
|
|
|
throw new BusinessError('会员信息不存在', 404);
|
|
|
}
|
|
|
|
|
|
res.json(success(member, '获取会员详情成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取我的会员信息
|
|
|
* GET /api/members/my/info
|
|
|
*/
|
|
|
const getMyMemberInfo = async (req, res, next) => {
|
|
|
try {
|
|
|
let member = await Member.findByUserId(req.user.id);
|
|
|
|
|
|
// 如果用户还不是会员,自动创建会员资格
|
|
|
if (!member) {
|
|
|
member = await Member.create(req.user.id);
|
|
|
}
|
|
|
|
|
|
// 获取会员折扣信息
|
|
|
const discount = Member.getDiscount(member.level);
|
|
|
|
|
|
res.json(success({
|
|
|
...member,
|
|
|
discount,
|
|
|
discount_percent: Math.round((1 - discount) * 100) // 折扣百分比
|
|
|
}, '获取我的会员信息成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 创建会员
|
|
|
* POST /api/members
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const createMember = async (req, res, next) => {
|
|
|
try {
|
|
|
const { user_id } = req.body;
|
|
|
|
|
|
// 数据验证
|
|
|
const validation = validateRequired(user_id, '用户ID');
|
|
|
if (!validation.valid) {
|
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
|
}
|
|
|
|
|
|
// 检查用户是否已是会员
|
|
|
const isMember = await Member.isMember(user_id);
|
|
|
if (isMember) {
|
|
|
throw new BusinessError('该用户已是会员');
|
|
|
}
|
|
|
|
|
|
const member = await Member.create(user_id);
|
|
|
|
|
|
res.status(201).json(success(member, '创建会员成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 更新会员等级
|
|
|
* PUT /api/members/:userId/level
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const updateMemberLevel = async (req, res, next) => {
|
|
|
try {
|
|
|
const { userId } = req.params;
|
|
|
const { level } = req.body;
|
|
|
|
|
|
// 数据验证
|
|
|
const validation = validateRequired(level, '会员等级');
|
|
|
if (!validation.valid) {
|
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
|
}
|
|
|
|
|
|
// 验证等级值
|
|
|
const validLevels = ['bronze', 'silver', 'gold', 'diamond'];
|
|
|
if (!validLevels.includes(level)) {
|
|
|
throw new ValidationError(`会员等级必须是以下之一: ${validLevels.join(', ')}`);
|
|
|
}
|
|
|
|
|
|
const member = await Member.updateLevel(userId, level);
|
|
|
|
|
|
res.json(success(member, '更新会员等级成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 增加会员积分
|
|
|
* POST /api/members/:userId/points/add
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const addMemberPoints = async (req, res, next) => {
|
|
|
try {
|
|
|
const { userId } = req.params;
|
|
|
const { points, reason } = req.body;
|
|
|
|
|
|
// 数据验证
|
|
|
const validation = validateRequired(points, '积分数量');
|
|
|
if (!validation.valid) {
|
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
|
}
|
|
|
|
|
|
if (parseInt(points) <= 0) {
|
|
|
throw new ValidationError('积分数量必须大于0');
|
|
|
}
|
|
|
|
|
|
const member = await Member.addPoints(
|
|
|
userId,
|
|
|
parseInt(points),
|
|
|
reason || '管理员手动添加'
|
|
|
);
|
|
|
|
|
|
res.json(success(member, '增加积分成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 扣减会员积分
|
|
|
* POST /api/members/:userId/points/deduct
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const deductMemberPoints = async (req, res, next) => {
|
|
|
try {
|
|
|
const { userId } = req.params;
|
|
|
const { points, reason } = req.body;
|
|
|
|
|
|
// 数据验证
|
|
|
const validation = validateRequired(points, '积分数量');
|
|
|
if (!validation.valid) {
|
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
|
}
|
|
|
|
|
|
if (parseInt(points) <= 0) {
|
|
|
throw new ValidationError('积分数量必须大于0');
|
|
|
}
|
|
|
|
|
|
const member = await Member.deductPoints(
|
|
|
userId,
|
|
|
parseInt(points),
|
|
|
reason || '管理员手动扣减'
|
|
|
);
|
|
|
|
|
|
res.json(success(member, '扣减积分成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取会员统计信息
|
|
|
* GET /api/members/statistics
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const getMemberStatistics = async (req, res, next) => {
|
|
|
try {
|
|
|
const stats = await Member.getStatistics();
|
|
|
|
|
|
res.json(success(stats, '获取会员统计成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// ========================================
|
|
|
// 优惠券相关控制器
|
|
|
// ========================================
|
|
|
|
|
|
/**
|
|
|
* 获取优惠券列表
|
|
|
* GET /api/members/coupons
|
|
|
*/
|
|
|
const getCoupons = async (req, res, next) => {
|
|
|
try {
|
|
|
const {
|
|
|
page = 1,
|
|
|
pageSize = 10,
|
|
|
type,
|
|
|
is_active
|
|
|
} = req.query;
|
|
|
|
|
|
const result = await Coupon.findAll({
|
|
|
page: parseInt(page),
|
|
|
pageSize: parseInt(pageSize),
|
|
|
type,
|
|
|
is_active: is_active !== undefined ? parseInt(is_active) : undefined
|
|
|
});
|
|
|
|
|
|
res.json(success({
|
|
|
list: result.list,
|
|
|
pagination: {
|
|
|
total: result.total,
|
|
|
page: result.page,
|
|
|
pageSize: result.pageSize,
|
|
|
totalPages: Math.ceil(result.total / result.pageSize)
|
|
|
}
|
|
|
}, '获取优惠券列表成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取优惠券详情
|
|
|
* GET /api/members/coupons/:id
|
|
|
*/
|
|
|
const getCouponById = async (req, res, next) => {
|
|
|
try {
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const coupon = await Coupon.findById(id);
|
|
|
if (!coupon) {
|
|
|
throw new BusinessError('优惠券不存在', 404);
|
|
|
}
|
|
|
|
|
|
res.json(success(coupon, '获取优惠券详情成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 创建优惠券
|
|
|
* POST /api/members/coupons
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const createCoupon = async (req, res, next) => {
|
|
|
try {
|
|
|
const {
|
|
|
name,
|
|
|
type,
|
|
|
discount_value,
|
|
|
min_amount,
|
|
|
max_discount,
|
|
|
valid_from,
|
|
|
valid_to,
|
|
|
total_quantity,
|
|
|
points_required,
|
|
|
is_active
|
|
|
} = req.body;
|
|
|
|
|
|
// 数据验证
|
|
|
const validation = validateBatch([
|
|
|
validateRequired(name, '优惠券名称'),
|
|
|
validateRequired(type, '优惠券类型'),
|
|
|
validateRequired(discount_value, '优惠值'),
|
|
|
validateRequired(valid_from, '有效期开始时间'),
|
|
|
validateRequired(valid_to, '有效期结束时间'),
|
|
|
validateRequired(total_quantity, '发放数量')
|
|
|
]);
|
|
|
|
|
|
if (!validation.valid) {
|
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
|
}
|
|
|
|
|
|
// 验证优惠券类型
|
|
|
const validTypes = ['discount', 'cash'];
|
|
|
if (!validTypes.includes(type)) {
|
|
|
throw new ValidationError('优惠券类型必须是 discount 或 cash');
|
|
|
}
|
|
|
|
|
|
// 验证优惠值
|
|
|
if (parseFloat(discount_value) <= 0) {
|
|
|
throw new ValidationError('优惠值必须大于0');
|
|
|
}
|
|
|
|
|
|
if (type === 'discount' && parseFloat(discount_value) >= 1) {
|
|
|
throw new ValidationError('折扣类型的优惠值必须小于1(如0.9表示9折)');
|
|
|
}
|
|
|
|
|
|
// 验证数量
|
|
|
if (parseInt(total_quantity) <= 0) {
|
|
|
throw new ValidationError('发放数量必须大于0');
|
|
|
}
|
|
|
|
|
|
const coupon = await Coupon.create({
|
|
|
name,
|
|
|
type,
|
|
|
discount_value: parseFloat(discount_value),
|
|
|
min_amount: min_amount ? parseFloat(min_amount) : null,
|
|
|
max_discount: max_discount ? parseFloat(max_discount) : null,
|
|
|
valid_from,
|
|
|
valid_to,
|
|
|
total_quantity: parseInt(total_quantity),
|
|
|
points_required: points_required ? parseInt(points_required) : 0,
|
|
|
is_active: is_active !== undefined ? parseInt(is_active) : 1
|
|
|
});
|
|
|
|
|
|
res.status(201).json(success(coupon, '创建优惠券成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 更新优惠券
|
|
|
* PUT /api/members/coupons/:id
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const updateCoupon = async (req, res, next) => {
|
|
|
try {
|
|
|
const { id } = req.params;
|
|
|
const {
|
|
|
name,
|
|
|
discount_value,
|
|
|
min_amount,
|
|
|
max_discount,
|
|
|
valid_from,
|
|
|
valid_to,
|
|
|
total_quantity,
|
|
|
points_required,
|
|
|
is_active
|
|
|
} = req.body;
|
|
|
|
|
|
// 检查优惠券是否存在
|
|
|
const coupon = await Coupon.findById(id);
|
|
|
if (!coupon) {
|
|
|
throw new BusinessError('优惠券不存在', 404);
|
|
|
}
|
|
|
|
|
|
const updatedCoupon = await Coupon.update(id, {
|
|
|
name,
|
|
|
discount_value: discount_value !== undefined ? parseFloat(discount_value) : undefined,
|
|
|
min_amount: min_amount !== undefined ? (min_amount ? parseFloat(min_amount) : null) : undefined,
|
|
|
max_discount: max_discount !== undefined ? (max_discount ? parseFloat(max_discount) : null) : undefined,
|
|
|
valid_from,
|
|
|
valid_to,
|
|
|
total_quantity: total_quantity !== undefined ? parseInt(total_quantity) : undefined,
|
|
|
points_required: points_required !== undefined ? parseInt(points_required) : undefined,
|
|
|
is_active: is_active !== undefined ? parseInt(is_active) : undefined
|
|
|
});
|
|
|
|
|
|
res.json(success(updatedCoupon, '更新优惠券成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 删除优惠券
|
|
|
* DELETE /api/members/coupons/:id
|
|
|
* 需要管理员权限
|
|
|
*/
|
|
|
const deleteCoupon = async (req, res, next) => {
|
|
|
try {
|
|
|
const { id } = req.params;
|
|
|
|
|
|
// 检查优惠券是否存在
|
|
|
const coupon = await Coupon.findById(id);
|
|
|
if (!coupon) {
|
|
|
throw new BusinessError('优惠券不存在', 404);
|
|
|
}
|
|
|
|
|
|
await Coupon.delete(id);
|
|
|
|
|
|
res.json(success(null, '删除优惠券成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 领取优惠券
|
|
|
* POST /api/members/coupons/:id/claim
|
|
|
*/
|
|
|
const claimCoupon = async (req, res, next) => {
|
|
|
try {
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const userCoupon = await Coupon.claimByUser(req.user.id, id);
|
|
|
|
|
|
res.status(201).json(success(userCoupon, '领取优惠券成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取我的优惠券
|
|
|
* GET /api/members/my/coupons
|
|
|
*/
|
|
|
const getMyCoupons = async (req, res, next) => {
|
|
|
try {
|
|
|
const { status } = req.query;
|
|
|
|
|
|
const coupons = await Coupon.getUserCoupons(req.user.id, status || null);
|
|
|
|
|
|
res.json(success({
|
|
|
count: coupons.length,
|
|
|
coupons
|
|
|
}, '获取我的优惠券成功'));
|
|
|
} catch (err) {
|
|
|
next(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 导出所有控制器函数
|
|
|
module.exports = {
|
|
|
// 会员相关
|
|
|
getMembers,
|
|
|
getMemberByUserId,
|
|
|
getMyMemberInfo,
|
|
|
createMember,
|
|
|
updateMemberLevel,
|
|
|
addMemberPoints,
|
|
|
deductMemberPoints,
|
|
|
getMemberStatistics,
|
|
|
|
|
|
// 优惠券相关
|
|
|
getCoupons,
|
|
|
getCouponById,
|
|
|
createCoupon,
|
|
|
updateCoupon,
|
|
|
deleteCoupon,
|
|
|
claimCoupon,
|
|
|
getMyCoupons
|
|
|
};
|