import { Injectable } from "@nestjs/common"; import { DatabaseService } from "../database/database.service"; import * as QRCode from "qrcode"; import { Serial, SerialListItem } from "./dto"; @Injectable() export class SerialsService { constructor(private dbService: DatabaseService) {} async generate( companyName: string, quantity: number, validDays: number, userId: number, serialPrefix?: string, ): Promise { const prisma = this.dbService.getPrisma(); const validUntil = new Date(); validUntil.setDate(validUntil.getDate() + validDays); const existingCompany = await prisma.company.findUnique({ where: { companyName }, }); if (!existingCompany) { await prisma.company.create({ data: { companyName, isActive: true }, }); } const serials: SerialListItem[] = []; const prefix = serialPrefix ? serialPrefix.toUpperCase().replace(/[^A-Z0-9]/g, "") : "BF" + new Date().getFullYear().toString().substr(2); for (let i = 0; i < quantity; i++) { const randomPart = Math.floor(Math.random() * 1000000) .toString() .padStart(6, "0"); const serialNumber = `${prefix}${randomPart}`; await prisma.serial.create({ data: { serialNumber, companyName, validUntil, createdBy: userId, isActive: true, }, }); serials.push({ serialNumber, companyName, validUntil: validUntil.toISOString(), isActive: true, createdAt: new Date().toISOString(), }); } return serials; } async generateQRCode( serialNumber: string, baseUrl?: string, requestHost?: string, protocol?: string, ) { const prisma = this.dbService.getPrisma(); const serial = await prisma.serial.findUnique({ where: { serialNumber: serialNumber.toUpperCase() }, include: { user: { select: { name: true }, }, }, }); if (!serial) { throw new Error("序列号不存在"); } if (!serial.isActive) { throw new Error("序列号已被禁用"); } if (serial.validUntil && new Date(serial.validUntil) < new Date()) { throw new Error("序列号已过期"); } if (!baseUrl) { baseUrl = `${protocol}://${requestHost}/query.html`; } const queryUrl = baseUrl.includes("?") ? `${baseUrl}&serial=${serial.serialNumber}` : `${baseUrl}?serial=${serial.serialNumber}`; const qrCodeData = await QRCode.toDataURL(queryUrl, { width: 200, color: { dark: "#165DFF", light: "#ffffff", }, }); return { message: "二维码生成成功", qrCodeData, queryUrl, serialNumber: serial.serialNumber, companyName: serial.companyName, validUntil: serial.validUntil, }; } async query(serialNumber: string) { const prisma = this.dbService.getPrisma(); const serial = await prisma.serial.findUnique({ where: { serialNumber: serialNumber.toUpperCase() }, include: { user: { select: { name: true }, }, }, }); if (!serial) { throw new Error("序列号不存在"); } if (serial.validUntil && new Date(serial.validUntil) < new Date()) { throw new Error("序列号已过期"); } return { message: "查询成功", serial: { serialNumber: serial.serialNumber, companyName: serial.companyName, validUntil: serial.validUntil, status: serial.isActive ? "active" : "disabled", isActive: serial.isActive, createdAt: serial.createdAt, createdBy: serial.user?.name, }, }; } async findAll(page: number, limit: number, search: string) { const prisma = this.dbService.getPrisma(); const offset = (page - 1) * limit; const where = search ? { OR: [ { serialNumber: { contains: search } }, { companyName: { contains: search } }, ], } : undefined; const [serials, total] = await Promise.all([ prisma.serial.findMany({ where, include: { user: { select: { name: true }, }, }, orderBy: { createdAt: "desc" }, skip: offset, take: limit, }), prisma.serial.count({ where }), ]); const totalPages = Math.ceil(total / limit); return { message: "获取序列号列表成功", data: serials.map((s) => ({ serialNumber: s.serialNumber, companyName: s.companyName, validUntil: s.validUntil, isActive: s.isActive, createdAt: s.createdAt, createdBy: s.user?.name, })), pagination: { page: parseInt(page.toString()), limit: parseInt(limit.toString()), total, totalPages, }, }; } async update( serialNumber: string, updateData: { companyName?: string; validUntil?: string; isActive?: boolean; }, ) { const prisma = this.dbService.getPrisma(); const existingSerial = await prisma.serial.findUnique({ where: { serialNumber: serialNumber.toUpperCase() }, }); if (!existingSerial) { throw new Error("序列号不存在"); } const updateFields: any = {}; if (updateData.companyName !== undefined) { updateFields.companyName = updateData.companyName; } if (updateData.validUntil !== undefined) { updateFields.validUntil = new Date(updateData.validUntil); } if (updateData.isActive !== undefined) { updateFields.isActive = updateData.isActive; } if (Object.keys(updateFields).length === 0) { throw new Error("没有提供更新字段"); } const updatedSerial = await prisma.serial.update({ where: { serialNumber: serialNumber.toUpperCase() }, data: updateFields, include: { user: { select: { name: true }, }, }, }); return { message: "序列号更新成功", serial: { serialNumber: updatedSerial.serialNumber, companyName: updatedSerial.companyName, validUntil: updatedSerial.validUntil, isActive: updatedSerial.isActive, createdAt: updatedSerial.createdAt, updatedAt: updatedSerial.updatedAt, createdBy: updatedSerial.user?.name, }, }; } async revoke(serialNumber: string) { const prisma = this.dbService.getPrisma(); const existingSerial = await prisma.serial.findUnique({ where: { serialNumber: serialNumber.toUpperCase() }, }); if (!existingSerial) { throw new Error("序列号不存在"); } if (!existingSerial.isActive) { throw new Error("序列号已被吊销"); } await prisma.serial.update({ where: { serialNumber: serialNumber.toUpperCase() }, data: { isActive: false }, }); return { message: "序列号已吊销", data: { serialNumber: serialNumber.toUpperCase(), }, }; } }