import prisma from '../lib/prisma' import { encrypt, decrypt } from '../lib/crypto' import { createDraft as createTerminationDraft, executeTermination } from './termination.service' import { createEmployee, addContract, prevMonth } from './contract.service' import { runRiskDetection } from './risk.service' // 13类流程定义 export const PROCESS_TYPES: Record = { HIRE: { label: '员工录用', description: '录用新员工并起草劳动合同', icon: 'user-plus' }, ONBOARD: { label: '员工入职', description: '办理员工入职手续', icon: 'log-in' }, CUSTOM_CONTRACT: { label: '自定义合同签署', description: '自定义合同内容并签署', icon: 'file-signature' }, INFO_SUBMIT: { label: '员工信息提交', description: '提交员工信息变更', icon: 'edit' }, CONFIRM: { label: '员工转正', description: '试用期员工转正', icon: 'check-circle' }, CHANGE: { label: '合同变更', description: '变更合同内容', icon: 'refresh-cw' }, RENEW: { label: '合同续签', description: '到期合同续签', icon: 'repeat' }, SUSPEND: { label: '合同中止', description: '中止履行合同', icon: 'pause' }, INCOME_CERT: { label: '开具收入证明', description: '为员工开具收入证明', icon: 'file-text' }, TERMINATE: { label: '合同终止', description: '合同到期终止', icon: 'x-circle' }, RESCIND: { label: '合同解除', description: '协商或单方解除合同', icon: 'user-x' }, LEAVING_CERT: { label: '开具离职证明', description: '为离职员工开具证明', icon: 'file-minus' }, FLEXIBLE: { label: '灵活用工', description: '灵活用工协议签署', icon: 'briefcase' }, } export const PROCESS_STATUS: Record = { DRAFT: { label: '草稿', color: 'bg-gray-100 text-gray-600' }, PENDING_APPROVAL: { label: '待审批', color: 'bg-amber-50 text-amber-700' }, APPROVED: { label: '已审批', color: 'bg-blue-50 text-blue-700' }, REJECTED: { label: '已驳回', color: 'bg-red-50 text-red-700' }, EXECUTING: { label: '执行中', color: 'bg-purple-50 text-purple-700' }, COMPLETED: { label: '已完成', color: 'bg-green-50 text-safe' }, CANCELLED: { label: '已撤销', color: 'bg-gray-100 text-gray-400' }, } // 提交后业务联动 export async function executeWorkProcess(processId: string, type: string, formData: any, orgId: string, userId: string) { switch (type) { case 'HIRE': { const { name, department, hireDate, monthlySalary, phone, idCardNumber, gender, contractStartDate, contractEndDate, contractType = 'FIXED' } = formData const result = await createEmployee(orgId, userId, { name, department: department || '未分配', hireDate, monthlySalary: monthlySalary || 0, phone, idCardNumber, gender, contract: contractStartDate ? { contractType, startDate: contractStartDate, endDate: contractEndDate || undefined, contractYears: 3, probationMonths: 0, probationSalary: 0, } : undefined, }) return { employeeId: result.id } } case 'ONBOARD': { const { employeeId, hireDate } = formData if (employeeId) { await prisma.employee.update({ where: { id: employeeId }, data: { hireDate: new Date(hireDate), status: 'ACTIVE' }, }) } return { employeeId } } case 'CONFIRM': { const { employeeId, regularSalary } = formData if (employeeId) { if (regularSalary) { const employee = await prisma.employee.findFirst({ where: { id: employeeId, orgId } }) const oldSalary = employee ? Number(decrypt(employee.monthlySalary)) || 0 : 0 const newSalary = Number(regularSalary) || 0 await prisma.employee.update({ where: { id: employeeId }, data: { monthlySalary: encrypt(String(regularSalary)) }, }) // 记录薪资变更(试用期薪资 → 转正薪资) if (oldSalary !== newSalary) { const now = new Date() const nowMonth = now.toISOString().slice(0, 7) await prisma.salaryChangeRecord.updateMany({ where: { employeeId, endMonth: null }, data: { endMonth: prevMonth(nowMonth) }, }) await prisma.salaryChangeRecord.create({ data: { orgId, employeeId, oldSalary, newSalary, effectiveDate: now, effectiveMonth: nowMonth, endMonth: null, changeType: 'CONFIRM', reason: '试用期转正薪资调整', createdBy: userId, }, }) } // 校验转正薪资与最新合同试用期薪资是否一致(提示性校验) const latestContract = await prisma.laborContract.findFirst({ where: { employeeId, orgId }, orderBy: { createdAt: 'desc' }, }) if (latestContract?.probationSalary && latestContract.probationSalary !== newSalary) { console.warn(`[CONFIRM] 转正薪资 ¥${newSalary} 与合同试用期薪资 ¥${latestContract.probationSalary} 不一致,员工: ${employeeId}`) } await runRiskDetection(orgId) } } return { employeeId } } case 'RENEW': { const { employeeId, oldContractId, newStartDate, newEndDate, newSalary, contractType = 'FIXED', contractYears = 3 } = formData // 截止旧合同 if (oldContractId) { const oldEndDate = new Date(newStartDate) oldEndDate.setDate(oldEndDate.getDate() - 1) await prisma.laborContract.update({ where: { id: oldContractId }, data: { endDate: oldEndDate }, }) } // 创建新合同(复用 addContract 的验证逻辑) if (employeeId) { const result = await addContract(orgId, userId, { employeeId, startDate: newStartDate, endDate: newEndDate || undefined, contractType, contractYears: Number(contractYears) || 3, probationMonths: 0, probationSalary: 0, }) if (newSalary) { await prisma.employee.update({ where: { id: employeeId }, data: { monthlySalary: encrypt(String(newSalary)) }, }) } return { employeeId, newContractId: result.id } } return { employeeId } } case 'TERMINATE': { const { employeeId, contractId, terminateDate, reason = 'EXPIRED', compensation = 0 } = formData // 截止合同 if (contractId) { await prisma.laborContract.update({ where: { id: contractId }, data: { endDate: new Date(terminateDate) }, }) } // 通过 termination service 创建解聘记录(含风险评级、合规清单) if (employeeId) { const draft = await createTerminationDraft(orgId, userId, { employeeId, type: 'TERMINATION', reason, terminationDate: terminateDate, compensation, }) // 直接执行(WorkProcess 流程审批已通过) await executeTermination(orgId, draft.id, userId) return { employeeId, terminationRecordId: draft.id } } return { employeeId } } case 'RESCIND': { const { employeeId, contractId, rescindDate, reason = 'NEGOTIATED', compensation = 0 } = formData // 截止合同 if (contractId) { await prisma.laborContract.update({ where: { id: contractId }, data: { endDate: new Date(rescindDate) }, }) } // 通过 termination service 创建解聘记录(含风险评级、合规清单) if (employeeId) { const draft = await createTerminationDraft(orgId, userId, { employeeId, type: 'TERMINATION', reason, terminationDate: rescindDate, compensation, }) await executeTermination(orgId, draft.id, userId) return { employeeId, terminationRecordId: draft.id } } return { employeeId } } case 'CHANGE': { const { contractId, newEndDate } = formData if (contractId && newEndDate) { await prisma.laborContract.update({ where: { id: contractId }, data: { endDate: new Date(newEndDate) }, }) } return { contractId } } case 'SUSPEND': { const { contractId, suspendDate } = formData if (contractId && suspendDate) { await prisma.laborContract.update({ where: { id: contractId }, data: { endDate: new Date(suspendDate) }, }) } return { contractId } } case 'CUSTOM_CONTRACT': { const { employeeId, contractStartDate, contractEndDate, contractType = 'FIXED', signMethod = 'PAPER', contractYears = 3 } = formData if (employeeId) { const result = await addContract(orgId, userId, { employeeId, startDate: contractStartDate, endDate: contractEndDate || undefined, contractType, signMethod, contractYears: Number(contractYears) || 3, probationMonths: 0, probationSalary: 0, }) return { employeeId, newContractId: result.id } } return {} } case 'FLEXIBLE': { const { name, phone, idCardNumber, department, agreementStartDate, agreementEndDate } = formData const empResult = await createEmployee(orgId, userId, { name, department: department || '灵活用工', hireDate: agreementStartDate, monthlySalary: 0, phone, idCardNumber, }) const contractResult = await addContract(orgId, userId, { employeeId: empResult.id, startDate: agreementStartDate, endDate: agreementEndDate || undefined, contractType: 'LABOR', contractYears: 1, probationMonths: 0, probationSalary: 0, }) return { employeeId: empResult.id, newContractId: contractResult.id } } case 'INFO_SUBMIT': { const { employeeId, ...updateFields } = formData if (employeeId) { const allowedFields: Record = {} if (updateFields.department) allowedFields.department = updateFields.department if (updateFields.phone) allowedFields.phone = updateFields.phone if (updateFields.address) allowedFields.address = updateFields.address if (updateFields.emergencyContact) allowedFields.emergencyContact = updateFields.emergencyContact if (updateFields.emergencyPhone) allowedFields.emergencyPhone = updateFields.emergencyPhone if (updateFields.bankAccount) allowedFields.bankAccount = encrypt(updateFields.bankAccount) if (updateFields.bankName) allowedFields.bankName = updateFields.bankName if (Object.keys(allowedFields).length > 0) { await prisma.employee.update({ where: { id: employeeId }, data: allowedFields }) } } return { employeeId } } case 'INCOME_CERT': case 'LEAVING_CERT': { // 这两类只生成文书,不改变业务数据 return {} } default: return {} } } // 生成文书预览 export async function generateDocument(type: string, formData: any, orgName: string): Promise<{ name: string; content: string }> { // 如果指定了企业自定义模板,使用企业模板渲染 if (formData.enterpriseTemplateId) { const tpl = await (prisma as any).enterpriseTemplate.findFirst({ where: { id: formData.enterpriseTemplateId }, }) if (tpl) { let content = tpl.content // 替换变量 {{var}} const allVars: Record = { ...formData, companyName: orgName } for (const [key, value] of Object.entries(allVars)) { content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), String(value ?? '')) } return { name: `${tpl.name}.doc`, content } } } const wrapHtml = (title: string, body: string) => ` ${title}
${title}
${body} ` const templates: Record string> = { INCOME_CERT: (data, org) => wrapHtml('收入证明', `
兹证明 ${data.employeeName || '___'}(证件号码:${data.idCardNumber || '___'})系我单位员工,自 ${data.hireDate || '___'} 起在我单位工作,现任 ${data.position || '___'} 职务。
该员工近一年平均月收入为人民币 ${data.monthlyIncome || '___'} 元(税前)。
本证明仅用于 ${data.purpose || '___'},不作其他用途。
特此证明。
${org}
${new Date().toLocaleDateString('zh-CN')}
`), LEAVING_CERT: (data, org) => wrapHtml('离职证明', `
兹证明 ${data.employeeName || '___'}(证件号码:${data.idCardNumber || '___'})自 ${data.hireDate || '___'} 至 ${data.leaveDate || '___'} 在我单位工作,最后职务为 ${data.position || '___'}。
该员工已于 ${data.leaveDate || '___'} 与我单位解除劳动关系,双方已办妥交接手续。
特此证明。
${org}
${new Date().toLocaleDateString('zh-CN')}
`), } const generator = templates[type] if (!generator) return { name: '', content: '' } return { name: `${PROCESS_TYPES[type]?.label || '文书'}.doc`, content: generator(formData, orgName) } }