diff --git a/backend/prisma/seed-multi-org.ts b/backend/prisma/seed-multi-org.ts index ecf1a14..a6d6b24 100644 --- a/backend/prisma/seed-multi-org.ts +++ b/backend/prisma/seed-multi-org.ts @@ -399,6 +399,112 @@ async function createOrg(orgConfig: OrgConfig) { } } console.log(` ✅ 历史工资条已生成`) + + // 10. 生成培训记录 + console.log(` 📝 生成培训记录...`) + const trainingTopics = [ + { topic: '新员工入职培训', content: '公司文化、规章制度、安全规范', trainer: '张经理', duration: 4 }, + { topic: '岗位技能培训', content: '岗位操作规范与流程', trainer: '李主管', duration: 6 }, + { topic: '安全生产培训', content: '安全生产法规与操作规程', trainer: '王安全', duration: 3 }, + { topic: '团队协作培训', content: '沟通技巧与团队建设', trainer: '刘讲师', duration: 2 }, + ] + for (let i = 0; i < allEmployees.length; i++) { + const emp = allEmployees[i] + const t = trainingTopics[i % trainingTopics.length] + const trainDate = new Date(2026, (i % 6), 15) + const ackStatus = i % 3 === 0 ? 'PENDING' : i % 3 === 1 ? 'SIGNED' : 'REFUSED' + await prisma.trainingRecord.create({ + data: { + orgId: org.id, + employeeId: emp.id, + trainingDate: trainDate, + topic: t.topic, + content: t.content, + trainer: t.trainer, + duration: t.duration, + ackStatus: ackStatus as any, + ackDate: ackStatus === 'SIGNED' ? new Date(trainDate.getTime() + 86400000) : null, + createdBy: admin.id, + }, + }) + } + console.log(` ✅ 培训记录已生成 (${allEmployees.length}条)`) + + // 11. 生成绩效记录 + console.log(` 📊 生成绩效记录...`) + const perfResults = ['EXCELLENT', 'QUALIFIED', 'QUALIFIED', 'NEED_IMPROVE', 'UNQUALIFIED'] as const + const perfGrades = ['A', 'B', 'B', 'C', 'D'] + for (let i = 0; i < allEmployees.length; i++) { + const emp = allEmployees[i] + const idx = i % perfResults.length + const score = 95 - idx * 12 + await prisma.performanceRecord.create({ + data: { + orgId: org.id, + employeeId: emp.id, + period: '2026-Q1', + score, + grade: perfGrades[idx], + result: perfResults[idx] as any, + summary: idx < 2 ? '工作表现优秀,完成任务质量高' : idx < 4 ? '基本完成工作目标,有待提升' : '未达到岗位要求,需制定改进计划', + improvementPlan: idx >= 3 ? '加强技能培训,设定阶段性目标' : null, + reviewer: admin.name, + employeeAck: i % 2 === 0, + createdBy: admin.id, + }, + }) + // 部分员工有Q2绩效 + if (i % 2 === 0) { + await prisma.performanceRecord.create({ + data: { + orgId: org.id, + employeeId: emp.id, + period: '2026-Q2', + score: score - 5, + grade: perfGrades[Math.min(idx + 1, 4)], + result: perfResults[Math.min(idx + 1, 4)] as any, + summary: '二季度绩效评估', + reviewer: admin.name, + employeeAck: false, + createdBy: admin.id, + }, + }) + } + } + console.log(` ✅ 绩效记录已生成 (${allEmployees.length}条)`) + + // 12. 生成违纪记录(部分员工) + console.log(` ⚠️ 生成违纪记录...`) + const discTypes = ['LATE', 'ABSENT', 'INSUBORDINATION', 'MISCONDUCT'] as const + const discDescriptions = [ + '月内累计迟到3次,超过公司允许范围', + '未经请假擅自缺勤1天', + '不服从主管工作安排,拒绝执行合理指令', + '违反公司安全操作规程,未佩戴防护设备', + ] + const discActions = ['ORAL_WARNING', 'WRITTEN_WARNING', 'DEDUCTION', 'WRITTEN_WARNING'] as const + for (let i = 0; i < Math.min(allEmployees.length, 4); i++) { + const emp = allEmployees[i] + const violationDate = new Date(2026, i % 6, 10) + await prisma.disciplinaryRecord.create({ + data: { + orgId: org.id, + employeeId: emp.id, + violationDate, + violationType: discTypes[i], + description: discDescriptions[i], + severity: i < 2 ? 'WARNING' : 'SERIOUS', + action: discActions[i], + actionDetail: i === 2 ? '扣除当日工资' : '', + employeeAck: i % 2 === 0, + ackDate: i % 2 === 0 ? new Date(violationDate.getTime() + 86400000) : null, + ackMethod: i % 2 === 0 ? 'SIGN' : null, + witness: i >= 2 ? '部门主管' : null, + createdBy: admin.id, + }, + }) + } + console.log(` ✅ 违纪记录已生成 (${Math.min(allEmployees.length, 4)}条)`) } // ========== 主函数 ========== diff --git a/frontend/src/pages/roster/DisciplinaryRecords.tsx b/frontend/src/pages/roster/DisciplinaryRecords.tsx index 776be33..bc5322e 100644 --- a/frontend/src/pages/roster/DisciplinaryRecords.tsx +++ b/frontend/src/pages/roster/DisciplinaryRecords.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom' import { Search, Plus, Edit2, Trash2, X } from 'lucide-react' import { toast } from 'sonner' import { rosterApi, employeeApi } from '../../lib/api-services' +import api from '../../lib/api' import { usePageSize } from '../../hooks/usePageSize' import { Input, Label, Select } from '../../components/ui/Input' import Button from '../../components/ui/Button' @@ -43,14 +44,10 @@ export default function DisciplinaryRecords() { const isEdit = !!data.recordId const recordId = data.recordId delete data.recordId - const url = isEdit - ? `/api/v1/roster/${empId}/disciplinary/${recordId}` - : `/api/v1/roster/${empId}/disciplinary` - return fetch(url, { - method: isEdit ? 'PUT' : 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - body: JSON.stringify(data), - }).then(r => r.json()) + if (isEdit) { + return api.put(`/roster/${empId}/disciplinary/${recordId}`, data) + } + return api.post(`/roster/${empId}/disciplinary`, data) }, onSuccess: () => { toast.success('违纪记录已保存') @@ -63,10 +60,7 @@ export default function DisciplinaryRecords() { const deleteMut = useMutation({ mutationFn: ({ employeeId, recordId }: { employeeId: string; recordId: string }) => - fetch(`/api/v1/roster/${employeeId}/disciplinary/${recordId}`, { - method: 'DELETE', - headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - }).then(r => r.json()), + api.delete(`/roster/${employeeId}/disciplinary/${recordId}`), onSuccess: () => { toast.success('记录已删除') queryClient.invalidateQueries({ queryKey: ['disciplinary-list'] }) diff --git a/frontend/src/pages/roster/PerformanceRecords.tsx b/frontend/src/pages/roster/PerformanceRecords.tsx index 0fb0b95..ea9f947 100644 --- a/frontend/src/pages/roster/PerformanceRecords.tsx +++ b/frontend/src/pages/roster/PerformanceRecords.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom' import { Search, Plus, Edit2, Trash2, X } from 'lucide-react' import { toast } from 'sonner' import { rosterApi, employeeApi } from '../../lib/api-services' +import api from '../../lib/api' import { usePageSize } from '../../hooks/usePageSize' import { Input, Label, Select } from '../../components/ui/Input' import Button from '../../components/ui/Button' @@ -36,14 +37,10 @@ export default function PerformanceRecords() { const isEdit = !!data.recordId const recordId = data.recordId delete data.recordId - const url = isEdit - ? `/api/v1/roster/${empId}/performance/${recordId}` - : `/api/v1/roster/${empId}/performance` - return fetch(url, { - method: isEdit ? 'PUT' : 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - body: JSON.stringify(data), - }).then(r => r.json()) + if (isEdit) { + return api.put(`/roster/${empId}/performance/${recordId}`, data) + } + return api.post(`/roster/${empId}/performance`, data) }, onSuccess: () => { toast.success('绩效记录已保存') @@ -56,10 +53,7 @@ export default function PerformanceRecords() { const deleteMut = useMutation({ mutationFn: ({ employeeId, recordId }: { employeeId: string; recordId: string }) => - fetch(`/api/v1/roster/${employeeId}/performance/${recordId}`, { - method: 'DELETE', - headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - }).then(r => r.json()), + api.delete(`/roster/${employeeId}/performance/${recordId}`), onSuccess: () => { toast.success('记录已删除') queryClient.invalidateQueries({ queryKey: ['performance-list'] }) diff --git a/frontend/src/pages/roster/TrainingRecords.tsx b/frontend/src/pages/roster/TrainingRecords.tsx index 4c85d9d..e9523da 100644 --- a/frontend/src/pages/roster/TrainingRecords.tsx +++ b/frontend/src/pages/roster/TrainingRecords.tsx @@ -4,6 +4,7 @@ import { Link } from 'react-router-dom' import { Search, Plus, Edit2, Trash2, X } from 'lucide-react' import { toast } from 'sonner' import { rosterApi, employeeApi } from '../../lib/api-services' +import api from '../../lib/api' import { usePageSize } from '../../hooks/usePageSize' import { Input, Label, Select } from '../../components/ui/Input' import Button from '../../components/ui/Button' @@ -38,11 +39,7 @@ export default function TrainingRecords() { mutationFn: (data: any) => { const empId = data.employeeId delete data.employeeId - return fetch(`/api/v1/roster/${empId}/training`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - body: JSON.stringify(data), - }).then(r => r.json()) + return api.post(`/roster/${empId}/training`, data) }, onSuccess: () => { toast.success('培训记录已添加') @@ -58,11 +55,7 @@ export default function TrainingRecords() { const recordId = data.recordId delete data.employeeId delete data.recordId - return fetch(`/api/v1/roster/${empId}/training/${recordId}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - body: JSON.stringify(data), - }).then(r => r.json()) + return api.put(`/roster/${empId}/training/${recordId}`, data) }, onSuccess: () => { toast.success('培训记录已更新') @@ -74,10 +67,7 @@ export default function TrainingRecords() { const deleteMut = useMutation({ mutationFn: ({ employeeId, recordId }: { employeeId: string; recordId: string }) => - fetch(`/api/v1/roster/${employeeId}/training/${recordId}`, { - method: 'DELETE', - headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, - }).then(r => r.json()), + api.delete(`/roster/${employeeId}/training/${recordId}`), onSuccess: () => { toast.success('记录已删除') queryClient.invalidateQueries({ queryKey: ['training-list'] })