feat: 城市变更功能完善及跨页面缓存刷新修复
- 城市变更使用CITY_CHANGE类型替代ADJUST,月度办理显示减员/新增(城市变更) - 在保人员查询排除本月已关闭记录(gte→gt)和本月新增记录(lte→lt) - 社保/公积金减员查询包含CITY_CHANGE类型 - 缴纳记录表格添加城市列显示 - 后端profile API从快照提取city字段 - 修复旧快照缺失city字段的数据 - 修复旧记录changeType为CITY_CHANGE,endMonth与新记录startMonth一致 - 城市变更必填原因,写入备注和审计日志 - 员工详情页添加变更历史Tab - 移除薪酬社保Tab下重复的参保城市变更子Tab - 全局修复跨页面mutation缓存刷新:调薪/调部门/离职/重新入职/批量续签/批量解聘/社保公积金调基/月度办理/撤销解聘均刷新roster-profile
This commit is contained in:
@@ -158,6 +158,7 @@ model Organization {
|
||||
trainingRecords TrainingRecord[]
|
||||
performanceRecords PerformanceRecord[]
|
||||
retirementPolicies RetirementPolicy[]
|
||||
socialMonthlyProcesses SocialMonthlyProcess[]
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -739,6 +740,24 @@ model EmployeeHousingFundRecord {
|
||||
@@index([employeeId, startMonth, endMonth])
|
||||
}
|
||||
|
||||
/// 月度社保/公积金办理记录(标记某月已办理完成,保存快照)
|
||||
model SocialMonthlyProcess {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
month String // 办理月份 YYYY-MM
|
||||
type String // SOCIAL=社保, HOUSING=公积金
|
||||
status String @default("COMPLETED") // COMPLETED=已办理
|
||||
snapshot Json // 办理时的数据快照(增减员+在保人员+缴费明细)
|
||||
processedBy String
|
||||
processedAt DateTime @default(now())
|
||||
createdBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([orgId, month, type])
|
||||
@@index([orgId, month])
|
||||
}
|
||||
|
||||
model EmployeeDepartmentRecord {
|
||||
id String @id @default(cuid())
|
||||
orgId String
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import prisma from '../src/lib/prisma'
|
||||
|
||||
const EID = 'cmrx61v6d001oqqcwb2pu2tih'
|
||||
const ORGID = 'cmrx61v3l0000qqcwo3dr3h95'
|
||||
const UID = 'cmrx61v5u0002qqcwqf4vlyth'
|
||||
const EID = 'cmry97xbv001qtrrpuo12ozy7'
|
||||
const ORGID = 'cmry97x7l0000trrp5f9jgng5'
|
||||
const UID = 'cmry97xa10002trrp7peorkvh'
|
||||
|
||||
async function main() {
|
||||
// 加班记录
|
||||
// 加班记录(入职后按季度分布)
|
||||
const otMonths = [
|
||||
{ month: '2025-03', wh: 8, weh: 4, hh: 0, wp: 600, wep: 600, hp: 0, pay: 1200 },
|
||||
{ month: '2025-06', wh: 12, weh: 8, hh: 0, wp: 1200, wep: 1200, hp: 0, pay: 2400 },
|
||||
{ month: '2025-09', wh: 6, weh: 0, hh: 8, wp: 600, wep: 0, hp: 1200, pay: 1800 },
|
||||
{ month: '2025-12', wh: 10, weh: 4, hh: 0, wp: 900, wep: 600, hp: 0, pay: 1500 },
|
||||
{ month: '2026-03', wh: 8, weh: 8, hh: 0, wp: 600, wep: 1200, hp: 0, pay: 1800 },
|
||||
{ month: '2026-06', wh: 6, weh: 0, hh: 0, wp: 450, wep: 0, hp: 0, pay: 450 },
|
||||
]
|
||||
for (const o of otMonths) {
|
||||
const existing = await prisma.overtimeRecord.findUnique({ where: { employeeId_month: { employeeId: EID, month: o.month } } })
|
||||
@@ -19,10 +22,11 @@ async function main() {
|
||||
}
|
||||
console.log('加班记录: 完成')
|
||||
|
||||
// 违纪记录
|
||||
// 违纪记录(补充历史记录,保留已有的 2026-07-24 记录)
|
||||
const discRecords = [
|
||||
{ violationDate: new Date('2025-05-12'), violationType: 'LATE', description: '月度迟到超过5次,影响团队考勤', severity: 'WARNING', action: 'ORAL_WARNING', actionDetail: '口头警告并谈话', employeeAck: true, ackDate: new Date('2025-05-13'), ackMethod: 'SIGN', witness: '王强' },
|
||||
{ violationDate: new Date('2025-09-20'), violationType: 'ABSENT', description: '未经请假擅自旷工1天', severity: 'SERIOUS', action: 'DEDUCTION', actionDetail: '扣款200元', employeeAck: true, ackDate: new Date('2025-09-21'), ackMethod: 'SIGN', witness: '王强' },
|
||||
{ violationDate: new Date('2026-03-10'), violationType: 'INSUBORDINATION', description: '不服从主管工作安排,拒绝参加客户会议', severity: 'SERIOUS', action: 'WRITTEN_WARNING', actionDetail: '书面警告并记入档案', employeeAck: true, ackDate: new Date('2026-03-11'), ackMethod: 'SIGN', witness: '赵敏' },
|
||||
]
|
||||
for (const d of discRecords) {
|
||||
const existing = await prisma.disciplinaryRecord.findFirst({ where: { employeeId: EID, violationDate: d.violationDate } })
|
||||
@@ -32,23 +36,52 @@ async function main() {
|
||||
}
|
||||
console.log('违纪记录: 完成')
|
||||
|
||||
// 考勤记录 - 最近10个工作日
|
||||
// 考勤记录 - 2026年6-7月最近20个工作日
|
||||
const attendance = [
|
||||
{ date: '2026-07-10', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-11', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-14', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-15', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-16', status: 'LATE', late: 25, early: 0 },
|
||||
{ date: '2026-07-17', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-18', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-21', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-07-22', status: 'EARLY_LEAVE', late: 0, early: 30 },
|
||||
{ date: '2026-07-23', status: 'NORMAL', late: 0, early: 0 },
|
||||
{ date: '2026-06-01', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-02', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-03', status: 'LATE', late: 15, early: 0, ot: 0 },
|
||||
{ date: '2026-06-04', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-05', status: 'NORMAL', late: 0, early: 0, ot: 2 },
|
||||
{ date: '2026-06-08', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-09', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-10', status: 'LEAVE', late: 0, early: 0, ot: 0, remark: '事假' },
|
||||
{ date: '2026-06-11', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-12', status: 'NORMAL', late: 0, early: 0, ot: 3 },
|
||||
{ date: '2026-06-15', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-16', status: 'LATE', late: 30, early: 0, ot: 0 },
|
||||
{ date: '2026-06-17', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-18', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-19', status: 'EARLY_LEAVE', late: 0, early: 45, ot: 0 },
|
||||
{ date: '2026-06-22', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-23', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-24', status: 'BUSINESS_TRIP', late: 0, early: 0, ot: 0, remark: '上海客户拜访' },
|
||||
{ date: '2026-06-25', status: 'BUSINESS_TRIP', late: 0, early: 0, ot: 0, remark: '上海客户拜访' },
|
||||
{ date: '2026-06-26', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-29', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-06-30', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-01', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-02', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-03', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-06', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-07', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-08', status: 'LATE', late: 20, early: 0, ot: 0 },
|
||||
{ date: '2026-07-09', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-10', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-13', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-14', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-15', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-16', status: 'LATE', late: 25, early: 0, ot: 0 },
|
||||
{ date: '2026-07-17', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-20', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-21', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
{ date: '2026-07-22', status: 'EARLY_LEAVE', late: 0, early: 30, ot: 0 },
|
||||
{ date: '2026-07-23', status: 'NORMAL', late: 0, early: 0, ot: 0 },
|
||||
]
|
||||
for (const a of attendance) {
|
||||
const existing = await prisma.attendanceRecord.findUnique({ where: { employeeId_date: { employeeId: EID, date: new Date(a.date) } } })
|
||||
if (!existing) {
|
||||
await prisma.attendanceRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, date: new Date(a.date), checkInTime: '09:00', checkOutTime: '18:00', status: a.status, lateMinutes: a.late, earlyMinutes: a.early, workHours: 8, overtimeHours: 0 } })
|
||||
await prisma.attendanceRecord.create({ data: { orgId: ORGID, employeeId: EID, createdBy: UID, date: new Date(a.date), checkInTime: a.status === 'LEAVE' ? null : '09:00', checkOutTime: a.status === 'LEAVE' ? null : '18:00', status: a.status, lateMinutes: a.late, earlyMinutes: a.early, workHours: a.status === 'LEAVE' ? 0 : 8, overtimeHours: a.ot || 0, remark: a.remark || null } })
|
||||
}
|
||||
}
|
||||
console.log('考勤记录: 完成')
|
||||
@@ -57,7 +90,9 @@ async function main() {
|
||||
const trainings = [
|
||||
{ trainingDate: new Date('2025-03-15'), topic: '《员工手册》培训', content: '公司规章制度、考勤制度、奖惩条例', trainer: '赵敏', duration: 2, ackStatus: 'SIGNED', ackDate: new Date('2025-03-15'), remark: '新员工入职培训' },
|
||||
{ trainingDate: new Date('2025-06-20'), topic: '销售技巧与合规培训', content: '销售话术规范、客户信息保护、合同签订注意事项', trainer: '王强', duration: 4, ackStatus: 'SIGNED', ackDate: new Date('2025-06-20') },
|
||||
{ trainingDate: new Date('2026-01-10'), topic: '2026年度规章制度更新培训', content: '新版考勤制度、绩效考核办法、安全生产规范', trainer: '赵敏', duration: 3, ackStatus: 'PENDING', remark: '待员工签收确认' },
|
||||
{ trainingDate: new Date('2025-09-10'), topic: '《数据安全管理制度》培训', content: '客户数据保护规范、信息安全操作规程、违规处罚条例', trainer: '赵敏', duration: 2, ackStatus: 'SIGNED', ackDate: new Date('2025-09-10') },
|
||||
{ trainingDate: new Date('2026-01-10'), topic: '2026年度规章制度更新培训', content: '新版考勤制度、绩效考核办法、安全生产规范', trainer: '赵敏', duration: 3, ackStatus: 'SIGNED', ackDate: new Date('2026-01-10') },
|
||||
{ trainingDate: new Date('2026-04-15'), topic: '销售合规与反商业贿赂培训', content: '反商业贿赂法规、客户招待标准、合规销售流程', trainer: '王强', duration: 3, ackStatus: 'PENDING', remark: '待员工签收确认' },
|
||||
]
|
||||
for (const t of trainings) {
|
||||
const existing = await prisma.trainingRecord.findFirst({ where: { employeeId: EID, trainingDate: t.trainingDate } })
|
||||
@@ -67,12 +102,14 @@ async function main() {
|
||||
}
|
||||
console.log('培训记录: 完成')
|
||||
|
||||
// 绩效记录
|
||||
// 绩效记录(从入职后按季度考核)
|
||||
const performances = [
|
||||
{ period: '2025-Q1', score: 82, grade: 'B', result: 'QUALIFIED', summary: '销售业绩达标,客户维护良好,需提升新客户开发能力', improvementPlan: '', employeeAck: true, ackDate: new Date('2025-04-10'), reviewer: '王强' },
|
||||
{ period: '2025-Q1', score: 82, grade: 'B', result: 'QUALIFIED', summary: '入职适应良好,销售业绩达标,客户维护良好,需提升新客户开发能力', improvementPlan: '', employeeAck: true, ackDate: new Date('2025-04-10'), reviewer: '王强' },
|
||||
{ period: '2025-Q2', score: 75, grade: 'B', result: 'QUALIFIED', summary: '业绩略有下滑,新客户开发不足,团队协作有待加强', improvementPlan: '', employeeAck: true, ackDate: new Date('2025-07-08'), reviewer: '王强' },
|
||||
{ period: '2025-Q3', score: 68, grade: 'C', result: 'NEED_IMPROVE', summary: '连续3个月未完成销售目标,客户投诉1次', improvementPlan: '调岗至客户维护岗,加强销售技巧培训1个月', employeeAck: true, ackDate: new Date('2025-10-15'), reviewer: '王强' },
|
||||
{ period: '2025-Q4', score: 78, grade: 'B', result: 'QUALIFIED', summary: '改进后业绩回升,客户满意度提升', improvementPlan: '', employeeAck: false, reviewer: '王强' },
|
||||
{ period: '2025-Q3', score: 68, grade: 'C', result: 'NEED_IMPROVE', summary: '连续3个月未完成销售目标,客户投诉1次,工作态度需改善', improvementPlan: '调岗至客户维护岗,加强销售技巧培训1个月', employeeAck: true, ackDate: new Date('2025-10-15'), reviewer: '王强' },
|
||||
{ period: '2025-Q4', score: 78, grade: 'B', result: 'QUALIFIED', summary: '改进后业绩回升,客户满意度提升,团队配合度改善', improvementPlan: '', employeeAck: true, ackDate: new Date('2026-01-12'), reviewer: '王强' },
|
||||
{ period: '2026-Q1', score: 72, grade: 'B', result: 'QUALIFIED', summary: '一季度业绩基本达标,大客户维护稳定,新签客户2家', improvementPlan: '', employeeAck: true, ackDate: new Date('2026-04-08'), reviewer: '王强' },
|
||||
{ period: '2026-Q2', score: 65, grade: 'C', result: 'NEED_IMPROVE', summary: '二季度业绩下滑明显,客户流失1家,不服从管理记录1次', improvementPlan: '加强客户维护培训,调整销售目标考核方式', employeeAck: false, reviewer: '王强' },
|
||||
]
|
||||
for (const p of performances) {
|
||||
const existing = await prisma.performanceRecord.findUnique({ where: { employeeId_period: { employeeId: EID, period: p.period } } })
|
||||
@@ -88,6 +125,8 @@ async function main() {
|
||||
{ fileName: '吴芳银行卡复印件.jpg', fileType: 'BANK_CARD', fileUrl: 'data:image/jpeg;base64,placeholder', fileSize: 51200 },
|
||||
{ fileName: '吴芳劳动合同扫描件.pdf', fileType: 'CONTRACT_SCAN', fileUrl: 'data:application/pdf;base64,placeholder', fileSize: 204800 },
|
||||
{ fileName: '吴芳学历证书.jpg', fileType: 'EDUCATION', fileUrl: 'data:image/jpeg;base64,placeholder', fileSize: 81920 },
|
||||
{ fileName: '吴芳学位证书.jpg', fileType: 'EDUCATION', fileUrl: 'data:image/jpeg;base64,placeholder', fileSize: 76800 },
|
||||
{ fileName: '吴芳离职证明(前单位).pdf', fileType: 'OTHER', fileUrl: 'data:application/pdf;base64,placeholder', fileSize: 153600 },
|
||||
]
|
||||
for (const a of attachments) {
|
||||
const existing = await prisma.employeeAttachment.findFirst({ where: { employeeId: EID, fileName: a.fileName } })
|
||||
@@ -97,10 +136,32 @@ async function main() {
|
||||
}
|
||||
console.log('附件: 完成')
|
||||
|
||||
// 社保缴费记录(入职)
|
||||
const hireMonth = '2025-02'
|
||||
const existingSocial = await prisma.employeeSocialInsRecord.findFirst({ where: { employeeId: EID, startMonth: hireMonth } })
|
||||
if (!existingSocial) {
|
||||
await prisma.employeeSocialInsRecord.create({
|
||||
data: { orgId: ORGID, employeeId: EID, startMonth: hireMonth, endMonth: null, base: 9000, changeType: 'ONBOARDING', createdBy: UID, city: '北京' },
|
||||
})
|
||||
}
|
||||
// 公积金缴费记录(入职)
|
||||
const existingHousing = await prisma.employeeHousingFundRecord.findFirst({ where: { employeeId: EID, startMonth: hireMonth } })
|
||||
if (!existingHousing) {
|
||||
await prisma.employeeHousingFundRecord.create({
|
||||
data: { orgId: ORGID, employeeId: EID, startMonth: hireMonth, endMonth: null, base: 9000, changeType: 'ONBOARDING', createdBy: UID, city: '北京' },
|
||||
})
|
||||
}
|
||||
// 更新 Employee 便捷字段
|
||||
await prisma.employee.update({
|
||||
where: { id: EID },
|
||||
data: { city: '北京', socialInsStartMonth: hireMonth, housingFundStartMonth: hireMonth },
|
||||
})
|
||||
console.log('社保/公积金: 完成')
|
||||
|
||||
// 验证
|
||||
const emp = await prisma.employee.findFirst({
|
||||
where: { id: EID },
|
||||
include: { contracts: true, payslips: true, overtimeRecords: true, disciplinaryRecords: true, attendanceRecords: true, trainingRecords: true, performanceRecords: true, terminations: true, attachments: true }
|
||||
include: { contracts: true, payslips: true, overtimeRecords: true, disciplinaryRecords: true, attendanceRecords: true, trainingRecords: true, performanceRecords: true, terminations: true, attachments: true, socialInsRecords: true, housingFundRecords: true }
|
||||
})
|
||||
if (emp) {
|
||||
console.log('--- 吴芳完整档案数据统计 ---')
|
||||
@@ -111,8 +172,10 @@ async function main() {
|
||||
console.log('attendanceRecords:', emp.attendanceRecords.length)
|
||||
console.log('trainingRecords:', emp.trainingRecords.length)
|
||||
console.log('performanceRecords:', emp.performanceRecords.length)
|
||||
console.log('terminations:', emp.terminations.length)
|
||||
console.log('terminations:', emp.terminations.length, JSON.stringify(emp.terminations.map(t => ({ status: t.status, type: t.type }))))
|
||||
console.log('attachments:', emp.attachments.length)
|
||||
console.log('socialInsRecords:', (emp as any).socialInsRecords?.length)
|
||||
console.log('housingFundRecords:', (emp as any).housingFundRecords?.length)
|
||||
}
|
||||
await prisma.$disconnect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user