feat(challenges): 新增挑战类型字段并重构进度上报逻辑
- 数据库新增 type 列区分 water/exercise/diet/mood/sleep/weight 六类挑战 - 进度上报由增量模式改为绝对值模式,字段 increment_value → reportedValue - 服务层按 challenge.targetValue 判断当日是否完成,再按 minimumCheckInDays 统计总进度 - 相关 DTO 与模型同步更新,支持新类型返回 BREAKING CHANGE: 上报接口字段由 increment 改为 value,且为当日绝对值
This commit is contained in:
@@ -96,6 +96,7 @@ export class ChallengesService {
|
||||
minimumCheckInDays: completionTarget,
|
||||
progress,
|
||||
isJoined: Boolean(participation),
|
||||
type: challenge.type,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -199,6 +200,7 @@ export class ChallengesService {
|
||||
progress,
|
||||
rankings,
|
||||
userRank,
|
||||
type: challenge.type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -267,6 +269,7 @@ export class ChallengesService {
|
||||
[Op.ne]: ChallengeParticipantStatus.LEFT,
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
if (!participant) {
|
||||
@@ -302,40 +305,45 @@ export class ChallengesService {
|
||||
userId,
|
||||
status: ChallengeParticipantStatus.ACTIVE,
|
||||
},
|
||||
include: [{
|
||||
model: Challenge,
|
||||
as: 'challenge',
|
||||
attributes: ['minimumCheckInDays', 'targetValue'],
|
||||
}],
|
||||
});
|
||||
|
||||
this.winstonLogger.info('start report progress', {
|
||||
context: 'reportProgress',
|
||||
userId,
|
||||
challengeId,
|
||||
participant,
|
||||
});
|
||||
|
||||
if (!participant) {
|
||||
throw new NotFoundException('请先加入挑战');
|
||||
}
|
||||
|
||||
const completionTarget = challenge.minimumCheckInDays
|
||||
// 如果要完成当日挑战,最低的上报数据
|
||||
const reportCompletedValue = challenge.targetValue
|
||||
|
||||
if (completionTarget <= 0) {
|
||||
if (reportCompletedValue <= 0) {
|
||||
throw new BadRequestException('挑战配置存在问题,请联系管理员');
|
||||
}
|
||||
|
||||
const increment = dto.increment ?? 1;
|
||||
if (increment < 1) {
|
||||
throw new BadRequestException('进度增量必须大于 0');
|
||||
if (dto.value === undefined || dto.value === null) {
|
||||
throw new BadRequestException('缺少上报的进度数据');
|
||||
}
|
||||
|
||||
let participantNeedsSave = false;
|
||||
if (participant.targetValue !== completionTarget) {
|
||||
participant.targetValue = completionTarget;
|
||||
participantNeedsSave = true;
|
||||
if (dto.value < 0) {
|
||||
throw new BadRequestException('进度数据必须大于等于 0');
|
||||
}
|
||||
|
||||
if (participant.progressValue >= completionTarget && participant.status !== ChallengeParticipantStatus.COMPLETED) {
|
||||
participant.status = ChallengeParticipantStatus.COMPLETED;
|
||||
participantNeedsSave = true;
|
||||
}
|
||||
const reportedValue = dto.value;
|
||||
|
||||
|
||||
const reportDate = dayjs().format('YYYY-MM-DD');
|
||||
const now = new Date();
|
||||
const remainingCapacity = Math.max(completionTarget - participant.progressValue, 0);
|
||||
const effectiveIncrement = Math.min(increment, remainingCapacity);
|
||||
|
||||
let created = false;
|
||||
try {
|
||||
const [report, wasCreated] = await this.progressReportModel.findOrCreate({
|
||||
where: {
|
||||
@@ -344,52 +352,53 @@ export class ChallengesService {
|
||||
reportDate,
|
||||
},
|
||||
defaults: {
|
||||
incrementValue: effectiveIncrement,
|
||||
reportedValue,
|
||||
reportedAt: now,
|
||||
},
|
||||
});
|
||||
|
||||
created = wasCreated;
|
||||
|
||||
if (wasCreated) {
|
||||
await report.update({
|
||||
incrementValue: effectiveIncrement,
|
||||
reportedAt: now,
|
||||
});
|
||||
if (report.reportedValue !== reportedValue) {
|
||||
await report.update({
|
||||
reportedValue,
|
||||
reportedAt: now,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (report.reportedValue !== reportedValue) {
|
||||
report.reportedAt = now;
|
||||
report.reportedValue = reportedValue;
|
||||
await report.save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (report.reportedValue >= reportCompletedValue && !dayjs(participant.lastProgressAt).isSame(dayjs(), 'd')) {
|
||||
participant.progressValue++
|
||||
participant.lastProgressAt = now;
|
||||
}
|
||||
|
||||
if (participant.progressValue >= (participant.challenge?.minimumCheckInDays || 0) && participant.status !== ChallengeParticipantStatus.COMPLETED) {
|
||||
participant.status = ChallengeParticipantStatus.COMPLETED;
|
||||
}
|
||||
|
||||
await participant.save();
|
||||
|
||||
this.winstonLogger.info('end report progress', {
|
||||
context: 'reportProgress',
|
||||
userId,
|
||||
challengeId,
|
||||
participant,
|
||||
});
|
||||
|
||||
|
||||
return this.buildChallengeProgress(participant.progressValue, participant.targetValue);
|
||||
} catch (error) {
|
||||
if (error instanceof UniqueConstraintError) {
|
||||
if (participantNeedsSave) {
|
||||
await participant.save();
|
||||
}
|
||||
return this.buildChallengeProgress(participant.progressValue, completionTarget);
|
||||
return this.buildChallengeProgress(participant.progressValue, participant.targetValue);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!created) {
|
||||
if (participantNeedsSave) {
|
||||
await participant.save();
|
||||
}
|
||||
return this.buildChallengeProgress(participant.progressValue, completionTarget);
|
||||
}
|
||||
|
||||
if (effectiveIncrement > 0) {
|
||||
const newProgress = Math.min(participant.progressValue + effectiveIncrement, completionTarget);
|
||||
participant.progressValue = newProgress;
|
||||
participant.lastProgressAt = now;
|
||||
|
||||
if (participant.progressValue >= completionTarget) {
|
||||
participant.status = ChallengeParticipantStatus.COMPLETED;
|
||||
}
|
||||
participantNeedsSave = true;
|
||||
}
|
||||
|
||||
if (participantNeedsSave) {
|
||||
await participant.save();
|
||||
}
|
||||
|
||||
return this.buildChallengeProgress(participant.progressValue, completionTarget);
|
||||
}
|
||||
|
||||
private buildChallengeProgress(completed: number, target: number, unit = '天'): ChallengeProgressDto {
|
||||
|
||||
Reference in New Issue
Block a user