feat: 账户关联根部门 + 员工新增自动带出账户

1. 账户管理:新建/编辑时可勾选关联 level=0 根部门(公司/分公司/子公司)
2. 后端新增 API:
   - PUT /social/accounts/:id/departments 批量关联根部门
   - GET /social/accounts/:id/departments 查询已关联部门
   - GET /social/department-account/:departmentId 按部门带出适用账户+标准
3. 部门更新 API 支持 socialAccountId/housingAccountId 字段
4. 员工新增表单:选定部门后自动带出社保公积金账户,可手动调整
5. 参保记录创建时写入 accountId

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-16 13:59:06 +08:00
parent 63b6c9dcc7
commit 014c94e482
7 changed files with 799 additions and 10 deletions
+6 -1
View File
@@ -16,7 +16,10 @@ const createDeptSchema = z.object({
description: z.string().max(200).optional(),
})
const updateDeptSchema = createDeptSchema.partial()
const updateDeptSchema = createDeptSchema.partial().extend({
socialAccountId: z.string().nullable().optional(),
housingAccountId: z.string().nullable().optional(),
})
/** 获取部门树 */
router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
@@ -109,6 +112,8 @@ router.put('/:id', authMiddleware, async (req: AuthRequest, res, next) => {
...(data.sortOrder !== undefined ? { sortOrder: data.sortOrder } : {}),
...(data.description !== undefined ? { description: data.description } : {}),
...(level !== undefined ? { level } : {}),
...(data.socialAccountId !== undefined ? { socialAccountId: data.socialAccountId } : {}),
...(data.housingAccountId !== undefined ? { housingAccountId: data.housingAccountId } : {}),
},
})
res.json({ success: true, data: dept })
+97
View File
@@ -231,6 +231,103 @@ router.post('/accounts/:accountId/standards', async (req: AuthRequest, res: Resp
} catch (err) { next(err) }
})
// 账户关联根部门(level=0)批量设置
router.put('/accounts/:id/departments', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const { id } = req.params
const { departmentIds } = req.body as { departmentIds: string[] }
const account = await prisma.socialAccount.findFirst({ where: { id, orgId } })
if (!account) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '账户不存在' } })
// 先清除该账户的所有部门关联
const field = account.type === 'SOCIAL' ? 'socialAccountId' : 'housingAccountId'
await prisma.department.updateMany({
where: { orgId, [field]: id },
data: { [field]: null },
})
// 批量设置新关联(仅 level=0 根部门)
if (departmentIds && departmentIds.length > 0) {
await prisma.department.updateMany({
where: { id: { in: departmentIds }, orgId, level: 0 },
data: { [field]: id },
})
}
res.json({ success: true, data: { message: `已关联 ${departmentIds?.length || 0} 个根部门` } })
} catch (err) { next(err) }
})
// 获取账户已关联的根部门列表
router.get('/accounts/:id/departments', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const { id } = req.params
const account = await prisma.socialAccount.findFirst({ where: { id, orgId } })
if (!account) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '账户不存在' } })
const field = account.type === 'SOCIAL' ? 'socialAccountId' : 'housingAccountId'
const departments = await prisma.department.findMany({
where: { orgId, level: 0, [field]: id },
select: { id: true, name: true },
})
res.json({ success: true, data: departments })
} catch (err) { next(err) }
})
// 按部门获取适用账户(选部门时自动带出)
router.get('/department-account/:departmentId', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
const orgId = req.user!.orgId
const { departmentId } = req.params
// 向上找到 level=0 的根部门
let currentDept: any = await prisma.department.findFirst({ where: { id: departmentId, orgId } })
if (!currentDept) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '部门不存在' } })
while (currentDept && currentDept.level > 0 && currentDept.parentId) {
currentDept = await prisma.department.findUnique({ where: { id: currentDept.parentId } })
}
const rootDeptId = currentDept?.id || null
let socialAccount: any = null
let housingAccount: any = null
if (rootDeptId) {
const rootDept = await prisma.department.findUnique({
where: { id: rootDeptId },
include: { socialAccount: true, housingAccount: true },
})
socialAccount = rootDept?.socialAccount || null
housingAccount = rootDept?.housingAccount || null
}
// 回退到公司默认账户
if (!socialAccount) {
socialAccount = await prisma.socialAccount.findFirst({ where: { orgId, type: 'SOCIAL', isDefault: true } })
}
if (!housingAccount) {
housingAccount = await prisma.socialAccount.findFirst({ where: { orgId, type: 'HOUSING', isDefault: true } })
}
// 获取当前生效标准
const currentMonth = new Date().toISOString().slice(0, 7)
let socialStandard: any = null
let housingStandard: any = null
if (socialAccount) {
socialStandard = await prisma.socialYearStandard.findFirst({
where: { accountId: socialAccount.id, isCurrent: true },
orderBy: { effectiveFrom: 'desc' },
})
}
if (housingAccount) {
housingStandard = await prisma.socialYearStandard.findFirst({
where: { accountId: housingAccount.id, isCurrent: true },
orderBy: { effectiveFrom: 'desc' },
})
}
res.json({ success: true, data: { socialAccount, housingAccount, socialStandard, housingStandard } })
} catch (err) { next(err) }
})
// 按员工获取适用账户(通过根部门 level=0 继承,不向下到普通部门)
router.get('/employee-account/:employeeId', async (req: AuthRequest, res: Response, next: NextFunction) => {
try {
+2
View File
@@ -396,6 +396,7 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
changeType: 'ONBOARDING',
createdBy: userId,
city: data.city || '北京',
accountId: data.socialAccountId || null,
},
})
@@ -409,6 +410,7 @@ export async function createEmployee(orgId: string, userId: string, data: any) {
changeType: 'ONBOARDING',
createdBy: userId,
city: data.city || '北京',
accountId: data.housingAccountId || null,
},
})