feat: 发薪日期多选+提前N天提醒+电子签设置区域修复

- Schema: 去掉 payrollFrequency,新增 payrollDays (JSON数组) + payrollReminderDays (Int)
- 设置页: 发薪日期改为1-28号多选按钮,新增提前提醒天数设置
- 设置页: 恢复电子签署设置区域(3个开关:规章制度/工资条/入职文件)
- 工作日历: 发薪日期作为 PAYROLL_DAY 事件显示
- 工作台: 提前N天提醒发薪日期,N可配置
- TaskCenter: 新增发薪提醒分类图标
- seed文件: 更新为 payrollDays 格式
This commit is contained in:
freedakgmail
2026-08-05 08:04:39 +08:00
parent 41b3030442
commit 6ec939dc7f
11 changed files with 180 additions and 25 deletions
+42
View File
@@ -285,6 +285,43 @@ router.get('/workspace/next-actions', authMiddleware, async (req: AuthRequest, r
take: 10,
})
// 5. 发薪日提前提醒
const org = await prisma.organization.findUnique({
where: { id: orgId },
select: { payrollDays: true, payrollReminderDays: true },
})
const payrollDays = Array.isArray(org?.payrollDays) ? org.payrollDays as number[] : []
const reminderDays = org?.payrollReminderDays ?? 3
const payrollReminderItems: any[] = []
const currentYear = now.getFullYear()
const currentMonth = now.getMonth() // 0-indexed
for (const day of payrollDays) {
// 本月发薪日
const thisMonthPayday = new Date(currentYear, currentMonth, day)
const diffDays = Math.floor((thisMonthPayday.getTime() - now.getTime()) / 86400000)
if (diffDays >= 0 && diffDays <= reminderDays) {
payrollReminderItems.push({
id: `payroll-${currentYear}-${currentMonth + 1}-${day}`,
title: `发薪日(每月${day}号)${diffDays === 0 ? '今天' : `${diffDays}天后`}`,
subtitle: diffDays === 0 ? '今天发薪' : `还有${diffDays}`,
link: '/money',
})
}
// 下月发薪日(如果当月已过,看下月)
if (diffDays < 0) {
const nextMonthPayday = new Date(currentYear, currentMonth + 1, day)
const nextDiffDays = Math.floor((nextMonthPayday.getTime() - now.getTime()) / 86400000)
if (nextDiffDays >= 0 && nextDiffDays <= reminderDays) {
payrollReminderItems.push({
id: `payroll-${currentYear}-${currentMonth + 2}-${day}`,
title: `发薪日(下月${day}号)${nextDiffDays === 0 ? '今天' : `${nextDiffDays}天后`}`,
subtitle: `还有${nextDiffDays}`,
link: '/money',
})
}
}
}
// 按优先级分组
const actions: Array<{ category: string; priority: 'high' | 'medium' | 'low'; items: any[] }> = [
{
@@ -333,6 +370,11 @@ router.get('/workspace/next-actions', authMiddleware, async (req: AuthRequest, r
link: '/special-status',
})),
},
{
category: '发薪提醒',
priority: 'medium',
items: payrollReminderItems,
},
]
// 过滤空分类
+1 -1
View File
@@ -115,7 +115,7 @@ router.get('/orgs', async (req: AuthRequest, res, next) => {
select: {
id: true, name: true, plan: true, maxEmployees: true,
city: true, contactName: true, contactPhone: true,
payrollFrequency: true, retirementReminderEnabled: true,
payrollDays: true, retirementReminderEnabled: true,
createdAt: true, updatedAt: true,
_count: {
select: { employees: true, users: true, contracts: true, payslips: true },
+5 -4
View File
@@ -28,7 +28,7 @@ router.get('/org', async (req: AuthRequest, res, next) => {
try {
const org = await prisma.organization.findUnique({
where: { id: req.user!.orgId },
select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollFrequency: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: true, createdAt: true },
select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollDays: true, payrollReminderDays: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: true, createdAt: true },
})
res.json({ success: true, data: org })
} catch (err) {
@@ -39,10 +39,11 @@ router.get('/org', async (req: AuthRequest, res, next) => {
// 更新企业信息
router.put('/org', requireAdmin, async (req: AuthRequest, res, next) => {
try {
const { name, payrollFrequency, city, contactName, contactPhone, retirementReminderEnabled, esignPolicyEnabled, esignPayslipEnabled, esignOnboardingEnabled } = req.body as { name?: string; payrollFrequency?: number; city?: string; contactName?: string; contactPhone?: string; retirementReminderEnabled?: boolean; esignPolicyEnabled?: boolean; esignPayslipEnabled?: boolean; esignOnboardingEnabled?: boolean }
const { name, payrollDays, payrollReminderDays, city, contactName, contactPhone, retirementReminderEnabled, esignPolicyEnabled, esignPayslipEnabled, esignOnboardingEnabled } = req.body as { name?: string; payrollDays?: number[]; payrollReminderDays?: number; city?: string; contactName?: string; contactPhone?: string; retirementReminderEnabled?: boolean; esignPolicyEnabled?: boolean; esignPayslipEnabled?: boolean; esignOnboardingEnabled?: boolean }
const updateData: any = {}
if (name) updateData.name = name
if (payrollFrequency !== undefined) updateData.payrollFrequency = payrollFrequency
if (payrollDays !== undefined) updateData.payrollDays = payrollDays
if (payrollReminderDays !== undefined) updateData.payrollReminderDays = payrollReminderDays
if (city !== undefined) updateData.city = city
if (contactName !== undefined) updateData.contactName = contactName
if (contactPhone !== undefined) updateData.contactPhone = contactPhone
@@ -53,7 +54,7 @@ router.put('/org', requireAdmin, async (req: AuthRequest, res, next) => {
const org = await prisma.organization.update({
where: { id: req.user!.orgId },
data: updateData,
select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollFrequency: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: true },
select: { id: true, name: true, plan: true, maxEmployees: true, city: true, contactName: true, contactPhone: true, payrollDays: true, payrollReminderDays: true, retirementReminderEnabled: true, esignPolicyEnabled: true, esignPayslipEnabled: true, esignOnboardingEnabled: true },
})
res.json({ success: true, data: org })
} catch (err) {
+21 -1
View File
@@ -1184,7 +1184,27 @@ export async function getMonthlyCalendar(orgId: string, month: string) {
}
}
// 7. 自定义日历事件
// 7. 发薪日期
const org = await prisma.organization.findUnique({
where: { id: orgId },
select: { payrollDays: true },
})
const payrollDays = Array.isArray(org?.payrollDays) ? org.payrollDays as number[] : []
for (const day of payrollDays) {
const dateStr = `${month}-${String(day).padStart(2, '0')}`
const payrollDate = new Date(parseInt(year), monthNum - 1, day)
if (payrollDate >= monthStart && payrollDate <= monthEnd) {
events.push({
date: dateStr,
type: 'PAYROLL_DAY',
title: `发薪日(每月${day}号)`,
actionUrl: '/money',
priority: 'medium',
})
}
}
// 8. 自定义日历事件
const customEvents = await prisma.calendarEvent.findMany({
where: {
orgId,