feat: appove and go

This commit is contained in:
thanhnv
2026-07-19 09:37:16 +07:00
parent 13fae3e6c3
commit 709b6cccd6
24 changed files with 1245 additions and 70 deletions
@@ -70,9 +70,9 @@ export class KeyResultsService {
createdById: user.sub,
},
});
await this.recalculateObjectiveStatus(existing.objectiveId, tx);
return keyResult;
});
await this.recalculateObjectiveStatus(existing.objectiveId);
return updated;
}
@@ -88,14 +88,17 @@ export class KeyResultsService {
}
}
private async recalculateObjectiveStatus(objectiveId: number): Promise<void> {
const keyResults = await this.prisma.keyResult.findMany({ where: { objectiveId } });
private async recalculateObjectiveStatus(
objectiveId: number,
tx: Prisma.TransactionClient = this.prisma,
): Promise<void> {
const keyResults = await tx.keyResult.findMany({ where: { objectiveId } });
const average =
keyResults.length === 0
? 0
: Math.round(keyResults.reduce((total, keyResult) => total + keyResult.progress, 0) / keyResults.length);
const status =
average === 0 ? 'NOT_STARTED' : average >= 100 ? 'COMPLETED' : 'IN_PROGRESS';
await this.prisma.objective.update({ where: { id: objectiveId }, data: { status } });
await tx.objective.update({ where: { id: objectiveId }, data: { status } });
}
}
+35
View File
@@ -312,6 +312,41 @@ test('KeyResultsService.updateProgress non-existent KR throws NotFoundException'
}
});
test('KeyResultsService.updateProgress rolls back progress when status recalculation fails', async () => {
const prisma = new PrismaService();
await prisma.$connect();
const service = new KeyResultsService(prisma);
const testable = service as unknown as {
recalculateObjectiveStatus: (objectiveId: number) => Promise<void>;
};
const recalculateObjectiveStatus = testable.recalculateObjectiveStatus.bind(service);
let beforeProgress = 0;
try {
const before = await prisma.keyResult.findUniqueOrThrow({ where: { id: 1 } });
beforeProgress = before.progress;
testable.recalculateObjectiveStatus = async () => {
throw new Error('forced status recalculation failure');
};
await assert.rejects(
() => service.updateProgress(1, { progress: 99, comment: 'must roll back' }, employeeUser),
/forced status recalculation failure/,
);
const after = await prisma.keyResult.findUniqueOrThrow({ where: { id: 1 } });
assert.equal(after.progress, beforeProgress);
const leakedHistory = await prisma.progressUpdate.findMany({
where: { keyResultId: 1, progress: 99, comment: 'must roll back' },
});
assert.equal(leakedHistory.length, 0);
} finally {
testable.recalculateObjectiveStatus = recalculateObjectiveStatus;
await prisma.keyResult.update({ where: { id: 1 }, data: { progress: beforeProgress } });
await prisma.progressUpdate.deleteMany({ where: { keyResultId: 1, comment: 'must roll back' } });
await prisma.$disconnect();
}
});
// ─── Status recalculation (full NOT_STARTED → IN_PROGRESS → COMPLETED cycle) ─
test('KeyResultsService status recalculation: NOT_STARTED → IN_PROGRESS → COMPLETED → IN_PROGRESS', async () => {