import { Injectable, Logger } from '@nestjs/common'; import * as ExcelJS from 'exceljs'; import { TransformMethodSchema, db, Prisma, Staff } from '@nice/common'; 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>( input: T, ): { [K in T[keyof T]]: Extract } { const result: Partial<{ [K in T[keyof T]]: Extract }> = {}; 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 }; } isEmptyRow(row: any) { return row.every((cell: any) => { return !cell || cell.toString().trim() === ''; }); } async importStaffs(data: z.infer) { 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, ) { 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, ) { 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, // }, // }); } }