fix: close sms scheduling and billing gaps
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export interface SubmitCertificationDto {
|
||||
tenantId: string;
|
||||
companyName: string;
|
||||
licenseNo?: string;
|
||||
contactName?: string;
|
||||
contactPhone?: string;
|
||||
materials?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ReviewCertificationDto {
|
||||
reviewerId?: string;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CertificationService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
list(tenantId?: string, status?: string) {
|
||||
return this.prisma.enterpriseCertification.findMany({
|
||||
where: { tenantId, status },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 100,
|
||||
});
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.prisma.enterpriseCertification.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
if (data.reviewerId) {
|
||||
const reviewer = await this.prisma.user.findUnique({ where: { id: data.reviewerId }, select: { id: 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user