feat: complete reporting and filing workflows
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { CurrentSessionUserId } from '../auth/current-session-user.decorator';
|
||||
import { TenantId } from '../common/tenant-id.decorator';
|
||||
import { CertificationService, ReviewCertificationDto, SubmitCertificationDto } from './certification.service';
|
||||
|
||||
@@ -35,12 +36,12 @@ export class AdminCertificationController {
|
||||
}
|
||||
|
||||
@Post(':id/approve')
|
||||
approve(@Param('id') id: string, @Body() body: ReviewCertificationDto) {
|
||||
return this.certifications.approve(id, body);
|
||||
approve(@Param('id') id: string, @Body() body: ReviewCertificationDto, @CurrentSessionUserId() reviewerId?: string) {
|
||||
return this.certifications.approve(id, { ...body, reviewerId });
|
||||
}
|
||||
|
||||
@Post(':id/reject')
|
||||
reject(@Param('id') id: string, @Body() body: ReviewCertificationDto) {
|
||||
return this.certifications.reject(id, body);
|
||||
reject(@Param('id') id: string, @Body() body: ReviewCertificationDto, @CurrentSessionUserId() reviewerId?: string) {
|
||||
return this.certifications.reject(id, { ...body, reviewerId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ function createPrismaMock() {
|
||||
},
|
||||
enterpriseCertification: {
|
||||
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'cert-1', ...data })),
|
||||
findMany: jest.fn(),
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
findUnique: jest.fn().mockResolvedValue({ id: 'cert-1', tenantId: 'tenant-1', status: 'pending' }),
|
||||
update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'cert-1', ...data })),
|
||||
},
|
||||
user: {
|
||||
findUnique: jest.fn().mockResolvedValue({ id: 'reviewer-1' }),
|
||||
findUnique: jest.fn().mockResolvedValue({ id: 'reviewer-1', username: 'reviewer', displayName: '审核员' }),
|
||||
findMany: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
operationLog: {
|
||||
create: jest.fn(),
|
||||
@@ -22,6 +23,21 @@ function createPrismaMock() {
|
||||
}
|
||||
|
||||
describe('CertificationService', () => {
|
||||
it('returns the reviewer username with enterprise certification records', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
prisma.enterpriseCertification.findMany.mockResolvedValue([
|
||||
{ id: 'cert-1', tenantId: 'tenant-1', reviewerId: 'reviewer-1' },
|
||||
]);
|
||||
prisma.user.findMany.mockResolvedValue([
|
||||
{ id: 'reviewer-1', username: 'reviewer', displayName: '审核员' },
|
||||
]);
|
||||
const service = new CertificationService(prisma as never);
|
||||
|
||||
await expect(service.list()).resolves.toEqual([
|
||||
expect.objectContaining({ reviewer: expect.objectContaining({ username: 'reviewer' }) }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('submits certification and marks tenant pending', async () => {
|
||||
const prisma = createPrismaMock();
|
||||
const service = new CertificationService(prisma as never);
|
||||
|
||||
@@ -20,8 +20,8 @@ export interface ReviewCertificationDto {
|
||||
export class CertificationService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
list(tenantId?: string, status?: string, keyword?: string) {
|
||||
return this.prisma.enterpriseCertification.findMany({
|
||||
async list(tenantId?: string, status?: string, keyword?: string) {
|
||||
const records = await this.prisma.enterpriseCertification.findMany({
|
||||
where: {
|
||||
tenantId,
|
||||
status: status && status !== 'all' ? status : undefined,
|
||||
@@ -36,6 +36,7 @@ export class CertificationService {
|
||||
include: { tenant: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return this.attachReviewers(records);
|
||||
}
|
||||
|
||||
async get(id: string) {
|
||||
@@ -46,7 +47,7 @@ export class CertificationService {
|
||||
if (!certification) {
|
||||
throw new NotFoundException('Enterprise certification not found');
|
||||
}
|
||||
return certification;
|
||||
return (await this.attachReviewers([certification]))[0];
|
||||
}
|
||||
|
||||
async submit(data: SubmitCertificationDto) {
|
||||
@@ -97,8 +98,12 @@ export class CertificationService {
|
||||
if (!certification) {
|
||||
throw new NotFoundException('Enterprise certification not found');
|
||||
}
|
||||
let reviewer: { id: string; username: string; displayName: string } | null = null;
|
||||
if (data.reviewerId) {
|
||||
const reviewer = await this.prisma.user.findUnique({ where: { id: data.reviewerId }, select: { id: true } });
|
||||
reviewer = await this.prisma.user.findUnique({
|
||||
where: { id: data.reviewerId },
|
||||
select: { id: true, username: true, displayName: true },
|
||||
});
|
||||
if (!reviewer) {
|
||||
throw new BadRequestException('reviewerId does not reference an existing user');
|
||||
}
|
||||
@@ -126,6 +131,19 @@ export class CertificationService {
|
||||
detail: { reason: data.reason } as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
return { ...updated, reviewer };
|
||||
}
|
||||
|
||||
private async attachReviewers<T extends { reviewerId: string | null }>(records: T[]) {
|
||||
const reviewerIds = [...new Set(records.map((record) => record.reviewerId).filter((id): id is string => Boolean(id)))];
|
||||
const reviewers = reviewerIds.length ? await this.prisma.user.findMany({
|
||||
where: { id: { in: reviewerIds } },
|
||||
select: { id: true, username: true, displayName: true },
|
||||
}) : [];
|
||||
const reviewerById = new Map(reviewers.map((reviewer) => [reviewer.id, reviewer]));
|
||||
return records.map((record) => ({
|
||||
...record,
|
||||
reviewer: record.reviewerId ? reviewerById.get(record.reviewerId) ?? null : null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user