refactor: 社保公积金Tab重构为账户卡片列表+展开年度标准管理
1. 社保/公积金Tab从"选账户→展示配置"改为"账户卡片列表+展开管理" 2. 每个账户卡片可展开显示:当前标准/最低工资编辑/新建年度标准/版本历史/调基/试算 3. 账户新建/编辑/删除/设默认从设置页面迁移到社保公积金菜单 4. 设置页面去掉"社保公积金账户"Tab 5. "新建版本"改名为"新建年度标准" 6. 新增AccountCard组件独立管理每个账户的展开状态和数据查询 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -254,6 +254,7 @@ router.put('/batches/:id/name', async (req: AuthRequest, res: Response, next: Ne
|
||||
// 创建批次
|
||||
const createBatchSchema = z.object({
|
||||
month: z.string().regex(/^\d{4}-\d{2}$/),
|
||||
payMonth: z.string().regex(/^\d{4}-\d{2}$/).optional(),
|
||||
type: z.enum(['REGULAR', 'TERMINATION', 'BONUS', 'SEVERANCE']).default('REGULAR'),
|
||||
mode: z.enum(['copy_last', 'blank_employees', 'blank_all', 'copy_batch', 'custom']).default('copy_last'),
|
||||
sourceBatchId: z.string().optional(),
|
||||
@@ -264,7 +265,7 @@ const createBatchSchema = z.object({
|
||||
|
||||
router.post('/batches', async (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { month, type, mode, sourceBatchId, employeeIds, name, remark } = createBatchSchema.parse(req.body)
|
||||
const { month, payMonth, type, mode, sourceBatchId, employeeIds, name, remark } = createBatchSchema.parse(req.body)
|
||||
const orgId = req.user!.orgId
|
||||
|
||||
// 查询当月最大批次号,避免删除后 count 不准导致唯一键冲突
|
||||
@@ -339,6 +340,8 @@ router.post('/batches', async (req: AuthRequest, res: Response, next: NextFuncti
|
||||
{ status: 'ACTIVE' },
|
||||
{ status: 'RESIGNED', updatedAt: { gte: monthStart, lte: monthEnd } },
|
||||
],
|
||||
// 预入职员工不进入薪资批次
|
||||
NOT: { status: 'PRE_ONBOARD' },
|
||||
},
|
||||
include: { contracts: { orderBy: { createdAt: 'desc' }, take: 1 } },
|
||||
})
|
||||
@@ -350,6 +353,7 @@ router.post('/batches', async (req: AuthRequest, res: Response, next: NextFuncti
|
||||
data: {
|
||||
orgId,
|
||||
month,
|
||||
payMonth: payMonth || null,
|
||||
batchNo,
|
||||
name: batchName,
|
||||
type,
|
||||
|
||||
@@ -100,12 +100,18 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => {
|
||||
// 状态过滤在 DB 层完成(contractStatus 需要后处理计算,仍需内存过滤)
|
||||
if (status === 'RESIGNED') {
|
||||
whereBase.status = 'RESIGNED'
|
||||
} else if (status === 'PRE_HIRE') {
|
||||
whereBase.status = 'ACTIVE'
|
||||
whereBase.hireDate = { gt: todayEnd }
|
||||
} else if (status === 'PRE_HIRE' || status === 'PRE_ONBOARD') {
|
||||
// 预入职:PRE_ONBOARD 状态,或 ACTIVE 状态但入职日期在未来
|
||||
whereBase.OR = [
|
||||
{ status: 'PRE_ONBOARD' },
|
||||
{ status: 'ACTIVE', hireDate: { gt: todayEnd } },
|
||||
]
|
||||
} else if (status === 'ACTIVE') {
|
||||
whereBase.status = 'ACTIVE'
|
||||
whereBase.hireDate = { lte: todayEnd }
|
||||
} else if (!status) {
|
||||
// 无状态过滤时,排除 PRE_ONBOARD(默认只看在职和离职)
|
||||
whereBase.NOT = { status: 'PRE_ONBOARD' }
|
||||
}
|
||||
|
||||
// unsigned 合同状态可以在 DB 层过滤
|
||||
@@ -1600,4 +1606,20 @@ router.get('/contract-types', authMiddleware, (_req: AuthRequest, res) => {
|
||||
res.json({ success: true, data: types })
|
||||
})
|
||||
|
||||
// 预入职转正式(PRE_ONBOARD → ACTIVE)
|
||||
router.post('/:id/activate', authMiddleware, async (req: AuthRequest, res, next) => {
|
||||
try {
|
||||
const emp = await prisma.employee.findFirst({ where: { id: req.params.id, orgId: req.user!.orgId! } })
|
||||
if (!emp) return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '员工不存在' } })
|
||||
if (emp.status !== 'PRE_ONBOARD') {
|
||||
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: '该员工不是预入职状态' } })
|
||||
}
|
||||
const updated = await prisma.employee.update({
|
||||
where: { id: emp.id },
|
||||
data: { status: 'ACTIVE' },
|
||||
})
|
||||
res.json({ success: true, data: { id: updated.id, status: updated.status } })
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user