282 lines
7.0 KiB
TypeScript
282 lines
7.0 KiB
TypeScript
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<SerialListItem[]> {
|
|
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(),
|
|
},
|
|
};
|
|
}
|
|
}
|