325 lines
8.5 KiB
TypeScript
325 lines
8.5 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { DatabaseService } from "../database/database.service";
|
|
|
|
@Injectable()
|
|
export class CompaniesService {
|
|
constructor(private dbService: DatabaseService) {}
|
|
|
|
async findAll(page: number, limit: number, search: string) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const offset = (page - 1) * limit;
|
|
|
|
const where = search
|
|
? {
|
|
companyName: { contains: search },
|
|
}
|
|
: undefined;
|
|
|
|
const [companies, total] = await Promise.all([
|
|
prisma.company.findMany({
|
|
where,
|
|
include: {
|
|
serials: true,
|
|
},
|
|
orderBy: { updatedAt: "desc" },
|
|
skip: offset,
|
|
take: limit,
|
|
}),
|
|
prisma.company.count({ where }),
|
|
]);
|
|
|
|
const totalPages = Math.ceil(total / limit);
|
|
|
|
return {
|
|
message: "获取企业列表成功",
|
|
data: companies.map((company: any) => ({
|
|
companyName: company.companyName,
|
|
firstCreated: company.createdAt,
|
|
lastCreated: company.updatedAt,
|
|
serialCount: company.serials.length,
|
|
activeCount: company.serials.filter((s: any) => s.isActive).length,
|
|
status: company.isActive ? "active" : "disabled",
|
|
})),
|
|
pagination: {
|
|
page: parseInt(page.toString()),
|
|
limit: parseInt(limit.toString()),
|
|
total,
|
|
totalPages,
|
|
},
|
|
};
|
|
}
|
|
|
|
async findOne(companyName: string, page: number, limit: number) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const offset = (page - 1) * limit;
|
|
|
|
const company = await prisma.company.findUnique({
|
|
where: { companyName },
|
|
include: {
|
|
serials: {
|
|
include: {
|
|
user: {
|
|
select: { name: true },
|
|
},
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!company) {
|
|
throw new Error("企业不存在");
|
|
}
|
|
|
|
const now = new Date();
|
|
const serialCount = company.serials.length;
|
|
const activeCount = company.serials.filter(
|
|
(s) => s.isActive && (!s.validUntil || s.validUntil > now),
|
|
).length;
|
|
const disabledCount = company.serials.filter((s) => !s.isActive).length;
|
|
const expiredCount = company.serials.filter(
|
|
(s) => s.validUntil && s.validUntil <= now,
|
|
).length;
|
|
|
|
const monthlyStatsMap = new Map<string, number>();
|
|
|
|
for (let i = 11; i >= 0; i--) {
|
|
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
|
const monthKey = date.toISOString().slice(0, 7);
|
|
const count = company.serials.filter((s) => {
|
|
const createdAt = new Date(s.createdAt);
|
|
return (
|
|
createdAt.getFullYear() === date.getFullYear() &&
|
|
createdAt.getMonth() === date.getMonth()
|
|
);
|
|
}).length;
|
|
|
|
if (count > 0) {
|
|
monthlyStatsMap.set(monthKey, count);
|
|
}
|
|
}
|
|
|
|
const paginatedSerials = company.serials.slice(offset, offset + limit);
|
|
|
|
return {
|
|
message: "获取企业详情成功",
|
|
data: {
|
|
companyName: companyName,
|
|
serialCount,
|
|
activeCount,
|
|
disabledCount,
|
|
expiredCount,
|
|
firstCreated: company.createdAt,
|
|
lastCreated: company.updatedAt,
|
|
status: company.isActive ? "active" : "disabled",
|
|
serials: paginatedSerials.map((s) => ({
|
|
serialNumber: s.serialNumber,
|
|
validUntil: s.validUntil,
|
|
isActive: s.isActive,
|
|
createdAt: s.createdAt,
|
|
createdBy: s.user?.name,
|
|
})),
|
|
monthlyStats: Array.from(monthlyStatsMap.entries()).map(
|
|
([month, count]) => ({ month, count }),
|
|
),
|
|
},
|
|
};
|
|
}
|
|
|
|
async update(companyName: string, newCompanyName: string) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const existingCompany = await prisma.serial.count({
|
|
where: { companyName },
|
|
});
|
|
|
|
if (existingCompany === 0) {
|
|
throw new Error("企业不存在");
|
|
}
|
|
|
|
const duplicateCompany = await prisma.serial.count({
|
|
where: { companyName: newCompanyName },
|
|
});
|
|
|
|
if (duplicateCompany > 0) {
|
|
throw new Error("企业名称已存在");
|
|
}
|
|
|
|
await prisma.serial.updateMany({
|
|
where: { companyName },
|
|
data: { companyName: newCompanyName },
|
|
});
|
|
|
|
return {
|
|
message: "企业名称更新成功",
|
|
data: {
|
|
oldCompanyName: companyName,
|
|
newCompanyName,
|
|
},
|
|
};
|
|
}
|
|
|
|
async delete(companyName: string) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const existingCompany = await prisma.company.findUnique({
|
|
where: { companyName },
|
|
});
|
|
|
|
if (!existingCompany) {
|
|
throw new Error("企业不存在");
|
|
}
|
|
|
|
const deleteResult = await prisma.$transaction(async (tx) => {
|
|
const serialDeleteCount = await tx.serial.deleteMany({
|
|
where: { companyName },
|
|
});
|
|
|
|
await tx.company.delete({
|
|
where: { companyName },
|
|
});
|
|
|
|
return serialDeleteCount.count;
|
|
});
|
|
|
|
return {
|
|
message: "企业已完全删除,所有相关序列号已删除",
|
|
data: {
|
|
companyName: companyName,
|
|
deletedSerialCount: deleteResult,
|
|
deletedCompanyCount: 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
async deleteSerial(companyName: string, serialNumber: string) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const serial = await prisma.serial.findFirst({
|
|
where: {
|
|
serialNumber: serialNumber.toUpperCase(),
|
|
companyName,
|
|
},
|
|
});
|
|
|
|
if (!serial) {
|
|
throw new Error("序列号不存在或不属于该企业");
|
|
}
|
|
|
|
await prisma.serial.delete({
|
|
where: { serialNumber: serialNumber.toUpperCase() },
|
|
});
|
|
|
|
return {
|
|
message: "序列号已成功删除",
|
|
data: {
|
|
serialNumber: serialNumber.toUpperCase(),
|
|
companyName,
|
|
},
|
|
};
|
|
}
|
|
|
|
async revoke(companyName: string) {
|
|
const prisma = this.dbService.getPrisma();
|
|
const existingCompany = await prisma.serial.count({
|
|
where: { companyName },
|
|
});
|
|
|
|
if (existingCompany === 0) {
|
|
throw new Error("企业不存在");
|
|
}
|
|
|
|
await prisma.serial.updateMany({
|
|
where: { companyName },
|
|
data: { isActive: false },
|
|
});
|
|
|
|
return {
|
|
message: "企业已吊销,所有序列号已失效",
|
|
data: {
|
|
companyName: companyName,
|
|
},
|
|
};
|
|
}
|
|
|
|
async getStats() {
|
|
const prisma = this.dbService.getPrisma();
|
|
const now = new Date();
|
|
|
|
const [companies, serials, recentCompanies, recentSerials] =
|
|
await Promise.all([
|
|
prisma.company.findMany(),
|
|
prisma.serial.findMany({
|
|
include: {
|
|
company: true,
|
|
},
|
|
}),
|
|
prisma.company.findMany({
|
|
orderBy: { updatedAt: "desc" },
|
|
take: 10,
|
|
}),
|
|
prisma.serial.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
take: 10,
|
|
}),
|
|
]);
|
|
|
|
const companyCount = companies.length;
|
|
const serialCount = serials.length;
|
|
const activeCount = serials.filter(
|
|
(s) => s.isActive && (!s.validUntil || s.validUntil > now),
|
|
).length;
|
|
const inactiveCount = serialCount - activeCount;
|
|
|
|
const monthlyStats: Array<{
|
|
month: string;
|
|
company_count: number;
|
|
serial_count: number;
|
|
}> = [];
|
|
const nowYear = now.getFullYear();
|
|
const nowMonth = now.getMonth();
|
|
|
|
for (let i = 11; i >= 0; i--) {
|
|
const date = new Date(nowYear, nowMonth - i, 1);
|
|
const monthStr = date.toISOString().slice(0, 7);
|
|
const monthSerials = serials.filter((s) => {
|
|
const createdAt = new Date(s.createdAt);
|
|
return (
|
|
createdAt.getFullYear() === date.getFullYear() &&
|
|
createdAt.getMonth() === date.getMonth()
|
|
);
|
|
});
|
|
const uniqueCompanies = new Set(monthSerials.map((s) => s.companyName));
|
|
|
|
if (monthSerials.length > 0) {
|
|
monthlyStats.push({
|
|
month: monthStr,
|
|
company_count: uniqueCompanies.size,
|
|
serial_count: monthSerials.length,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
message: "获取统计数据成功",
|
|
data: {
|
|
overview: {
|
|
totalCompanies: companyCount,
|
|
totalSerials: serialCount,
|
|
activeSerials: activeCount,
|
|
inactiveSerials: inactiveCount,
|
|
},
|
|
monthlyStats,
|
|
recentCompanies: recentCompanies.map((c) => ({
|
|
companyName: c.companyName,
|
|
lastCreated: c.updatedAt,
|
|
status: c.isActive ? "active" : "disabled",
|
|
})),
|
|
recentSerials: recentSerials.map((s) => ({
|
|
serialNumber: s.serialNumber,
|
|
companyName: s.companyName,
|
|
isActive: s.isActive,
|
|
createdAt: s.createdAt,
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
}
|