import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { shanghaiDateRange } from '../common/shanghai-date-range'; export interface SubmitCertificationDto { tenantId: string; companyName: string; licenseNo?: string; contactName?: string; contactPhone?: string; materials?: Record; } export interface ReviewCertificationDto { reviewerId?: string; reason?: string; } @Injectable() export class CertificationService { constructor(private readonly prisma: PrismaService) {} async list(tenantId?: string, status?: string, keyword?: string, submittedAtFrom?: string, submittedAtTo?: string) { const records = await this.prisma.enterpriseCertification.findMany({ where: { tenantId, status: status && status !== 'all' ? status : undefined, submittedAt: shanghaiDateRange(submittedAtFrom, submittedAtTo), OR: keyword ? [ { companyName: { contains: keyword } }, { licenseNo: { contains: keyword } }, { contactName: { contains: keyword } }, { contactPhone: { contains: keyword } }, { tenant: { name: { contains: keyword } } }, ] : undefined, }, include: { tenant: true }, orderBy: { createdAt: 'desc' }, }); return this.attachReviewers(records); } async get(id: string) { const certification = await this.prisma.enterpriseCertification.findUnique({ where: { id }, include: { tenant: true }, }); if (!certification) { throw new NotFoundException('Enterprise certification not found'); } return (await this.attachReviewers([certification]))[0]; } async submit(data: SubmitCertificationDto) { const tenant = await this.prisma.tenant.findUnique({ where: { id: data.tenantId } }); if (!tenant) { throw new NotFoundException('Tenant not found'); } const certification = await this.prisma.enterpriseCertification.create({ data: { tenantId: data.tenantId, companyName: data.companyName, licenseNo: data.licenseNo, contactName: data.contactName, contactPhone: data.contactPhone, materials: data.materials as Prisma.InputJsonValue | undefined, status: 'pending', }, }); await this.prisma.tenant.update({ where: { id: data.tenantId }, data: { certificationStatus: 'pending' }, }); await this.prisma.operationLog.create({ data: { tenantId: data.tenantId, action: 'enterprise_certification.submit', resource: 'enterprise_certification', resourceId: certification.id, detail: { companyName: data.companyName } as Prisma.InputJsonValue, }, }); return certification; } approve(id: string, data: ReviewCertificationDto) { return this.review(id, 'approved', data); } reject(id: string, data: ReviewCertificationDto) { if (!data.reason) { throw new BadRequestException('Reject reason is required'); } return this.review(id, 'rejected', data); } private async review(id: string, status: 'approved' | 'rejected', data: ReviewCertificationDto) { const certification = await this.prisma.enterpriseCertification.findUnique({ where: { id } }); if (!certification) { throw new NotFoundException('Enterprise certification not found'); } let reviewer: { id: string; username: string; displayName: string } | null = null; if (data.reviewerId) { 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'); } } const updated = await this.prisma.enterpriseCertification.update({ where: { id }, data: { status, rejectReason: status === 'rejected' ? data.reason : null, reviewerId: data.reviewerId, reviewedAt: new Date(), }, }); await this.prisma.tenant.update({ where: { id: certification.tenantId }, data: { certificationStatus: status }, }); await this.prisma.operationLog.create({ data: { tenantId: certification.tenantId, userId: data.reviewerId, action: `enterprise_certification.${status}`, resource: 'enterprise_certification', resourceId: id, detail: { reason: data.reason } as Prisma.InputJsonValue, }, }); return { ...updated, reviewer }; } private async attachReviewers(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, })); } }