Files
Public/apps/api/prisma/fix-expense-applicants.ts
T
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

126 lines
4.6 KiB
TypeScript

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function ensureUser(name: string, username: string, deptName: string, employeeNo: string, passwordHash: string) {
const existing = await prisma.user.findFirst({ where: { OR: [{ username }, { displayName: name }] } });
if (existing) return existing.id;
const dept =
(await prisma.department.findFirst({ where: { name: deptName } })) ||
(await prisma.department.findFirst({ where: { name: { contains: '研发' } } })) ||
(await prisma.department.findFirst({ where: { parentId: { not: null } } }));
if (!dept) throw new Error(`没有部门可挂 ${name}`);
const empRole = await prisma.role.findFirst({ where: { code: 'employee' } });
if (!empRole) throw new Error('没有 employee 角色');
let emp = await prisma.employee.findFirst({ where: { OR: [{ name }, { employeeNo }] } });
if (!emp) {
emp = await prisma.employee.create({
data: {
name,
employeeNo,
departmentId: dept.id,
title: '员工',
employmentStatus: 'LEFT',
remark: `OA 报销有此人(原部门 ${deptName}),人事花名册未导入,补建后挂单据,账号停用。`,
},
});
}
const user = await prisma.user.create({
data: {
username,
displayName: name,
passwordHash,
status: 'DISABLED',
employeeId: emp.id,
},
});
await prisma.userRole.create({ data: { userId: user.id, roleId: empRole.id } });
console.log('created leftover', name, user.id, 'dept', dept.name);
return user.id;
}
async function main() {
const admin = await prisma.user.findFirst({ where: { username: 'admin' } });
if (!admin) throw new Error('no admin');
await ensureUser('李乐菡', 'lilehan', '项目一部', 'E0115', admin.passwordHash);
const users = await prisma.user.findMany();
const byName = new Map(users.map((u) => [u.displayName, u.id]));
const ALIAS: Record<string, string> = { : '张婧妍' };
for (const [from, to] of Object.entries(ALIAS)) {
const uid = byName.get(to);
if (uid) byName.set(from, uid);
}
const expenses = await prisma.expenseClaim.findMany({ select: { id: true, remark: true, applicantId: true, status: true } });
let n = 0;
for (const e of expenses) {
const name = (e.remark || '').split(' / ')[0]?.trim();
if (!name || name === '系统管理员') continue;
const uid = byName.get(name);
if (!uid || uid === e.applicantId) continue;
await prisma.expenseClaim.update({ where: { id: e.id }, data: { applicantId: uid } });
n += 1;
}
const loans = await prisma.loanRecord.findMany({ select: { id: true, remark: true, applicantId: true } });
let ln = 0;
for (const e of loans) {
const name = (e.remark || '').split(' / ')[0]?.trim();
if (!name || name === '系统管理员') continue;
const uid = byName.get(name);
if (!uid || uid === e.applicantId) continue;
await prisma.loanRecord.update({ where: { id: e.id }, data: { applicantId: uid } });
ln += 1;
}
const approvers = await prisma.user.findMany({
where: { status: 'ACTIVE', roles: { some: { role: { code: { in: ['admin', 'finance', 'owner'] } } } } },
});
const pendingExp = await prisma.expenseClaim.findMany({ where: { status: 'PENDING' } });
const pendingLoan = await prisma.loanRecord.findMany({ where: { status: 'PENDING' } });
let tasks = 0;
for (const row of pendingExp) {
for (const u of approvers) {
const hit = await prisma.approvalTask.findFirst({
where: { bizType: 'EXPENSE_APPROVAL', bizId: row.id, assigneeId: u.id, status: 'PENDING' },
});
if (hit) continue;
await prisma.approvalTask.create({
data: {
title: `报销确认:${row.claimNo}`,
bizType: 'EXPENSE_APPROVAL',
bizId: row.id,
assigneeId: u.id,
},
});
tasks += 1;
}
}
for (const row of pendingLoan) {
for (const u of approvers) {
const hit = await prisma.approvalTask.findFirst({
where: { bizType: 'LOAN_APPROVAL', bizId: row.id, assigneeId: u.id, status: 'PENDING' },
});
if (hit) continue;
await prisma.approvalTask.create({
data: {
title: `借款确认:${row.loanNo}`,
bizType: 'LOAN_APPROVAL',
bizId: row.id,
assigneeId: u.id,
},
});
tasks += 1;
}
}
const left = await prisma.expenseClaim.count({ where: { applicantId: admin.id } });
console.log(JSON.stringify({ rematchedExpenses: n, rematchedLoans: ln, approvalTasks: tasks, adminStillHolds: left }));
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => prisma.$disconnect());