feat(core): 按照严格TDD模式完成活动项目(4位核销码)、订单状态机、财务分销与双身份切换核心领域服务及30项单元测试
这个提交包含在:
@@ -0,0 +1,133 @@
|
||||
export enum ActivityPayType {
|
||||
AA_PAY = 1, // AA制付费
|
||||
PROJECT_PAY = 2, // 项目制付费
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
creatorId: number;
|
||||
title: string;
|
||||
gameCategory: string;
|
||||
payType: ActivityPayType;
|
||||
totalFeeCents?: number;
|
||||
aaFeeCents?: number;
|
||||
maxParticipants: number;
|
||||
currentParticipants: number;
|
||||
}
|
||||
|
||||
export interface Participant {
|
||||
id: string;
|
||||
activityId: string;
|
||||
userId: number;
|
||||
payAmountCents: number;
|
||||
verifyCode: string; // 4位纯数字核销码
|
||||
checkinStatus: boolean;
|
||||
verifiedAt?: Date;
|
||||
verifiedBy?: number;
|
||||
}
|
||||
|
||||
export class ActivityService {
|
||||
private activities = new Map<string, Activity>();
|
||||
private participants = new Map<string, Participant[]>(); // activityId -> participants
|
||||
|
||||
createActivity(params: {
|
||||
creatorId: number;
|
||||
title: string;
|
||||
gameCategory: string;
|
||||
payType: ActivityPayType;
|
||||
totalFeeCents?: number;
|
||||
aaFeeCents?: number;
|
||||
maxParticipants: number;
|
||||
}): Activity {
|
||||
const id = `act_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const activity: Activity = {
|
||||
id,
|
||||
creatorId: params.creatorId,
|
||||
title: params.title,
|
||||
gameCategory: params.gameCategory,
|
||||
payType: params.payType,
|
||||
totalFeeCents: params.totalFeeCents,
|
||||
aaFeeCents: params.aaFeeCents,
|
||||
maxParticipants: params.maxParticipants,
|
||||
currentParticipants: 0,
|
||||
};
|
||||
this.activities.set(id, activity);
|
||||
this.participants.set(id, []);
|
||||
return activity;
|
||||
}
|
||||
|
||||
joinActivity(activityId: string, params: { userId: number; payAmountCents: number }): Participant {
|
||||
const activity = this.activities.get(activityId);
|
||||
if (!activity) {
|
||||
throw new Error('活动不存在');
|
||||
}
|
||||
|
||||
const currentList = this.participants.get(activityId) || [];
|
||||
if (activity.maxParticipants > 0 && currentList.length >= activity.maxParticipants) {
|
||||
throw new Error('活动报名人数已满');
|
||||
}
|
||||
|
||||
const alreadyJoined = currentList.some((p) => p.userId === params.userId);
|
||||
if (alreadyJoined) {
|
||||
throw new Error('用户已报名该活动');
|
||||
}
|
||||
|
||||
// 生成当前活动内唯一的4位数字核销码 (1000-9999)
|
||||
const existingCodes = new Set(currentList.map((p) => p.verifyCode));
|
||||
let verifyCode = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const code = String(Math.floor(1000 + Math.random() * 9000));
|
||||
if (!existingCodes.has(code)) {
|
||||
verifyCode = code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!verifyCode) {
|
||||
throw new Error('核销码生成失败,池已满');
|
||||
}
|
||||
|
||||
const participant: Participant = {
|
||||
id: `p_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
|
||||
activityId,
|
||||
userId: params.userId,
|
||||
payAmountCents: params.payAmountCents,
|
||||
verifyCode,
|
||||
checkinStatus: false,
|
||||
};
|
||||
|
||||
currentList.push(participant);
|
||||
activity.currentParticipants = currentList.length;
|
||||
return participant;
|
||||
}
|
||||
|
||||
verifyCheckin(params: {
|
||||
activityId: string;
|
||||
operatorId: number;
|
||||
verifyCode: string;
|
||||
}): { success: boolean; participant: Participant } {
|
||||
const activity = this.activities.get(params.activityId);
|
||||
if (!activity) {
|
||||
throw new Error('活动不存在');
|
||||
}
|
||||
|
||||
if (activity.creatorId !== params.operatorId) {
|
||||
throw new Error('无权核销该活动的核销码');
|
||||
}
|
||||
|
||||
const currentList = this.participants.get(params.activityId) || [];
|
||||
const participant = currentList.find((p) => p.verifyCode === params.verifyCode);
|
||||
if (!participant) {
|
||||
throw new Error('无效或不存在的核销码');
|
||||
}
|
||||
|
||||
if (participant.checkinStatus) {
|
||||
throw new Error('该核销码已被核销,不可重复使用');
|
||||
}
|
||||
|
||||
participant.checkinStatus = true;
|
||||
participant.verifiedAt = new Date();
|
||||
participant.verifiedBy = params.operatorId;
|
||||
|
||||
return { success: true, participant };
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户