origin/apps/server/src/models/transform/transform.service.ts

542 lines
18 KiB
TypeScript
Raw Normal View History

2024-12-30 08:26:40 +08:00
import { Injectable, Logger } from '@nestjs/common';
import * as ExcelJS from 'exceljs';
2025-02-06 16:32:52 +08:00
import { TransformMethodSchema, db, Prisma, Staff } from '@nice/common';
2024-12-30 08:26:40 +08:00
import dayjs from 'dayjs';
import * as argon2 from 'argon2';
import { TaxonomyService } from '@server/models/taxonomy/taxonomy.service';
import { uploadFile } from '@server/utils/tool';
import { DepartmentService } from '../department/department.service';
import { StaffService } from '../staff/staff.service';
import { z, ZodError } from 'zod';
import { deleteByPattern } from '@server/utils/redis/utils';
class TreeNode {
value: string;
children: TreeNode[];
constructor(value: string) {
this.value = value;
this.children = [];
}
addChild(childValue: string): TreeNode {
let newChild = undefined;
if (this.children.findIndex((child) => child.value === childValue) === -1) {
newChild = new TreeNode(childValue);
this.children.push(newChild);
}
return this.children.find((child) => child.value === childValue);
}
}
@Injectable()
export class TransformService {
constructor(
private readonly departmentService: DepartmentService,
private readonly staffService: StaffService,
private readonly taxonomyService: TaxonomyService,
) {}
private readonly logger = new Logger(TransformService.name);
excelDateToISO(excelDate: number) {
// 设置 Excel 序列号的起点
const startDate = dayjs('1899-12-31');
// 加上 Excel 中的天数注意必须减去2因为 Excel 错误地把1900年当作闰年
const date = startDate.add(excelDate, 'day');
// 转换为 ISO 字符串
return date.toDate();
}
async getDepts(domainId: string, cellStr: string) {
const pattern = /[\s、,.。;\n]+/;
const depts: string[] = [];
if (pattern.test(cellStr)) {
const deptNames = cellStr.split(pattern);
for (const name of deptNames) {
const dept = await this.departmentService.findInDomain(domainId, name);
if (dept) depts.push(dept.id);
}
} else {
const dept = await this.departmentService.findInDomain(domainId, cellStr);
if (dept) depts.push(dept.id);
}
if (depts.length === 0) {
this.logger.error(`未找到单位:${cellStr}`);
}
return depts;
}
async getStaffs(deptIds: string[], cellStr: string) {
const staffs: string[] = [];
const pattern = /[\s、,.。;\n]+/;
const allStaffsArrays = await Promise.all(
deptIds.map((deptId) => this.staffService.findByDept({ deptId })),
);
const combinedStaffs = allStaffsArrays.reduce(
(acc, curr) => acc.concat(curr),
[],
);
if (pattern.test(cellStr)) {
const staffNames = cellStr.split(pattern);
for (const name of staffNames) {
if (
combinedStaffs.map((staff, index) => staff?.showname).includes(name)
) {
const staffWithName = combinedStaffs.find(
(staff) => staff?.showname === name,
);
if (staffWithName) {
// 将该员工的 ID 添加到 staffIds 数组中
staffs.push(staffWithName.id);
}
}
// if (staff) staffs.push(staff.staffId);
}
} else {
// const staff = await this.lanxin.getStaffsByDepartment(deptIds);
// if (staff) staffs.push(staff.staffId);
if (
combinedStaffs.map((staff, index) => staff?.showname).includes(cellStr)
) {
const staffWithName = combinedStaffs.find(
(staff) => staff?.showname === cellStr,
);
if (staffWithName) {
// 将该员工的 ID 添加到 staffIds 数组中
staffs.push(staffWithName.id);
}
}
}
if (staffs.length === 0) {
this.logger.error(`未找到人员:${cellStr}`);
}
return staffs;
}
buildTree(data: string[][]): TreeNode {
const root = new TreeNode('root');
try {
for (const path of data) {
let currentNode = root;
for (const value of path) {
currentNode = currentNode.addChild(value);
}
}
return root;
} catch (error) {
console.error(error);
}
}
async generateTreeFromFile(file: Buffer): Promise<{ tree: TreeNode }> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(file);
const worksheet = workbook.getWorksheet(1);
const data: string[][] = [];
worksheet.eachRow((row, rowNumber) => {
if (rowNumber > 1) {
// Skip header row if any
try {
const rowData: string[] = (row.values as string[])
.slice(2)
.map((cell) => (cell || '').toString());
data.push(rowData.map((value) => value.trim()));
} catch (error) {
throw new Error(`发生报错!位于第${rowNumber}行,错误: ${error}`);
}
}
});
// Fill forward values
for (let i = 1; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
if (!data[i][j]) data[i][j] = data[i - 1][j];
}
}
return { tree: this.buildTree(data) };
}
printTree(node: TreeNode, level: number = 0): void {
const indent = ' '.repeat(level);
for (const child of node.children) {
this.printTree(child, level + 1);
}
}
swapKeyValue<T extends Record<string, string>>(
input: T,
): { [K in T[keyof T]]: Extract<keyof T, string> } {
const result: Partial<{ [K in T[keyof T]]: Extract<keyof T, string> }> = {};
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
const value = input[key];
result[value] = key;
}
}
return result as { [K in T[keyof T]]: Extract<keyof T, string> };
}
isEmptyRow(row: any) {
return row.every((cell: any) => {
return !cell || cell.toString().trim() === '';
});
}
2025-02-06 16:32:52 +08:00
2024-12-30 08:26:40 +08:00
async importStaffs(data: z.infer<typeof TransformMethodSchema.importStaffs>) {
const { base64, domainId } = data;
this.logger.log('开始');
const buffer = Buffer.from(base64, 'base64');
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(buffer);
const importsStaffMethodSchema = z.object({
name: z.string(),
phoneNumber: z.string().regex(/^\d+$/), // Assuming phone numbers should be numeric
deptName: z.string(),
});
const worksheet = workbook.getWorksheet(1); // Assuming the data is in the first sheet
const staffs: { name: string; phoneNumber: string; deptName: string }[] =
[];
worksheet.eachRow((row, rowNumber) => {
if (rowNumber > 1) {
// Assuming the first row is headers
const name = row.getCell(1).value as string;
const phoneNumber = row.getCell(2).value.toString() as string;
const deptName = row.getCell(3).value as string;
try {
importsStaffMethodSchema.parse({ name, phoneNumber, deptName });
staffs.push({ name, phoneNumber, deptName });
} catch (error) {
throw new Error(`发生报错!位于第${rowNumber}行,错误: ${error}`);
}
}
});
// 获取所有唯一的部门名称
const uniqueDeptNames = [...new Set(staffs.map((staff) => staff.deptName))];
// 获取所有部门名称对应的部门ID
const deptIdsMap = await this.departmentService.getDeptIdsByNames(
uniqueDeptNames,
domainId,
);
const count = await db.staff.count();
const hashedPassword = await argon2.hash('123456');
// 为员工数据添加部门ID
const staffsToCreate = staffs.map((staff, index) => ({
showname: staff.name,
username: staff.phoneNumber,
phoneNumber: staff.phoneNumber,
password: hashedPassword,
deptId: deptIdsMap[staff.deptName],
domainId,
order: index + count,
}));
// 批量创建员工数据
const createdStaffs = await db.staff.createMany({
data: staffsToCreate,
});
await deleteByPattern('row-*');
return createdStaffs;
}
async importTerms(
staff: Staff,
data: z.infer<typeof TransformMethodSchema.importTerms>,
) {
const { base64, domainId, taxonomyId, parentId } = data;
this.logger.log('开始');
await db.$transaction(async (tx) => {
const buffer = Buffer.from(base64, 'base64');
const { tree: root } = await this.generateTreeFromFile(buffer);
this.printTree(root);
const termsData: Prisma.TermCreateManyInput[] = [];
const termAncestriesData: Prisma.TermAncestryCreateManyInput[] = [];
if (!taxonomyId) {
throw new Error('未指定分类!');
}
this.logger.log('存在taxonomyId');
const taxonomy = await tx.taxonomy.findUnique({
where: { id: taxonomyId },
});
if (!taxonomy) {
throw new Error('未找到对应分类');
}
const count = await tx.term.count({ where: { taxonomyId: taxonomyId } });
let termIndex = 0;
this.logger.log(count);
const gatherTermsData = async (nodes: TreeNode[], depth = 0) => {
let currentIndex = 0;
for (const node of nodes) {
const termData = {
name: node.value,
taxonomyId: taxonomyId,
domainId: domainId,
createdBy: staff.id,
order: count + termIndex + 1,
};
termsData.push(termData);
termIndex++;
// Debug: Log term data preparation
await gatherTermsData(node.children, depth + 1);
currentIndex++;
}
};
await gatherTermsData(root.children);
let createdTerms: { id: string; name: string }[] = [];
try {
createdTerms = await tx.term.createManyAndReturn({
data: termsData,
select: { id: true, name: true },
});
// Debug: Log created terms
} catch (error) {
console.error('创建Terms报错:', error);
throw new Error('创建失败');
}
const termsUpdate = [];
const gatherAncestryData = (
nodes: TreeNode[],
ancestors: string[] = parentId ? [null, parentId] : [null],
depth = 0,
) => {
let currentIndex = 0;
for (const node of nodes) {
// if (depth !== 0) {
const dept = createdTerms.find((dept) => dept.name === node.value);
if (dept) {
termsUpdate.push({
where: { id: dept.id },
data: { parentId: ancestors[ancestors.length - 1] },
});
for (let i = 0; i < ancestors.length; i++) {
const ancestryData = {
ancestorId: ancestors[i],
descendantId: dept.id,
relDepth: depth - i + 1,
};
termAncestriesData.push(ancestryData);
}
const newAncestors = [...ancestors, dept.id];
gatherAncestryData(node.children, newAncestors, depth + 1);
}
currentIndex++;
}
// console.log(`depth:${depth}`);
// for (const node of nodes) {
// if (depth !== 0) {
// const term = createdTerms.find((term) => term.name === node.value);
// if (term) {
// termsUpdate.push({
// where: { id: term.id },
// data: { parentId: ancestors[ancestors.length - 1] },
// });
// for (let i = 0; i < ancestors.length; i++) {
// const ancestryData = {
// ancestorId: ancestors[i],
// descendantId: term.id,
// relDepth: depth - i,
// };
// termAncestriesData.push(ancestryData);
// console.log(`准备好的闭包表数据ATermAncestryData:`, ancestryData);
// }
// const newAncestors = [...ancestors, term.id];
// gatherAncestryData(node.children, newAncestors, depth + 1);
// }
// } else {
// gatherAncestryData(
// node.children,
// [createdTerms.find((term) => term.name === node.value).id],
// depth + 1,
// );
// }
// currentIndex++;
// }
};
gatherAncestryData(root.children);
this.logger.log('准备好闭包表数据 Ancestries Data:', termAncestriesData);
try {
const updatePromises = termsUpdate.map((update) =>
tx.term.update(update),
);
await Promise.all(updatePromises);
await tx.termAncestry.createMany({ data: termAncestriesData });
const allTerm = await tx.term.findMany({
where: {
id: {
in: createdTerms.map((termt) => termt.id),
},
},
select: {
id: true,
children: {
where: { deletedAt: null },
select: { id: true, deletedAt: true },
},
},
});
for (const term of allTerm) {
await tx.term.update({
where: {
id: term.id,
},
data: {
hasChildren: term.children.length > 0,
},
});
}
await deleteByPattern('row-*');
return { count: createdTerms.length };
} catch (error) {
console.error('Error 更新Term或者创建Terms闭包表失败:', error);
throw new Error('更新术语信息或者创建术语闭包表失败');
}
});
//prisma的特性create之后填入了对应id需要做一次这个查询才会填入相应值
const termAncestries = await db.termAncestry.findMany({
include: {
ancestor: true,
descendant: true,
},
});
}
async importDepts(
staff: Staff,
data: z.infer<typeof TransformMethodSchema.importDepts>,
) {
const { base64, domainId, parentId } = data;
// this.logger.log('开始', parentId);
const buffer = Buffer.from(base64, 'base64');
await db.$transaction(async (tx) => {
const { tree: root } = await this.generateTreeFromFile(buffer);
this.printTree(root);
const deptsData: Prisma.DepartmentCreateManyInput[] = [];
const deptAncestriesData: Prisma.DeptAncestryCreateManyInput[] = [];
const count = await tx.department.count({ where: {} });
let deptIndex = 0;
// this.logger.log(count);
const gatherDeptsData = async (
nodes: TreeNode[],
depth = 0,
dept?: string,
) => {
let currentIndex = 0;
for (const node of nodes) {
const deptData = {
name: node.value,
// taxonomyId: taxonomyId,
domainId: domainId,
// createdBy: staff.id,
order: count + deptIndex + 1,
};
deptsData.push(deptData);
deptIndex++;
// Debug: Log term data preparation
await gatherDeptsData(node.children, depth + 1);
currentIndex++;
}
};
await gatherDeptsData(root.children);
let createdDepts: { id: string; name: string }[] = [];
try {
createdDepts = await tx.department.createManyAndReturn({
data: deptsData,
select: { id: true, name: true },
});
// Debug: Log created terms
} catch (error) {
console.error('创建Depts报错:', error);
throw new Error('创建失败');
}
const deptsUpdate = [];
const gatherAncestryData = (
nodes: TreeNode[],
ancestors: string[] = parentId ? [null, parentId] : [null],
depth = 0,
) => {
let currentIndex = 0;
for (const node of nodes) {
// if (depth !== 0) {
const dept = createdDepts.find((dept) => dept.name === node.value);
if (dept) {
deptsUpdate.push({
where: { id: dept.id },
data: { parentId: ancestors[ancestors.length - 1] },
});
for (let i = 0; i < ancestors.length; i++) {
const ancestryData = {
ancestorId: ancestors[i],
descendantId: dept.id,
relDepth: depth - i + 1,
};
deptAncestriesData.push(ancestryData);
}
const newAncestors = [...ancestors, dept.id];
gatherAncestryData(node.children, newAncestors, depth + 1);
}
currentIndex++;
}
};
gatherAncestryData(root?.children);
this.logger.log('准备好闭包表数据 Ancestries Data:', deptAncestriesData);
try {
const updatePromises = deptsUpdate.map((update) =>
tx.department.update(update),
);
await Promise.all(updatePromises);
await tx.deptAncestry.createMany({ data: deptAncestriesData });
const allDept = await tx.department.findMany({
where: {
id: {
in: createdDepts.map((dept) => dept.id),
},
},
select: {
id: true,
children: {
where: { deletedAt: null },
select: { id: true, deletedAt: true },
},
},
});
for (const dept of allDept) {
await tx.department.update({
where: {
id: dept.id,
},
data: {
hasChildren: dept.children.length > 0,
},
});
}
await deleteByPattern('row-*');
return { count: createdDepts.length };
} catch (error) {
console.error('Error 更新Dept或者创建Depts闭包表失败:', error);
throw new Error('更新单位信息或者创建单位闭包表失败');
}
});
//prisma的特性create之后填入了对应id需要做一次这个查询才会填入相应值
// const deptAncestries = db.deptAncestry.findMany({
// include: {
// ancestor: true,
// descendant: true,
// },
// });
}
}