|
|
@@ -1,4 +1,22 @@
|
|
|
import { getApiBaseUrl, getServerConfig, request } from '@/utils/auth'
|
|
|
+import { resignings, resignStages } from '@/data/mock'
|
|
|
+import {
|
|
|
+ addCreatedResignation,
|
|
|
+ formatNow,
|
|
|
+ getCreatedResignations,
|
|
|
+ getCurrentUser,
|
|
|
+ getHandoverById,
|
|
|
+ getHandoverByResignationId,
|
|
|
+ getHandovers,
|
|
|
+ getResignations,
|
|
|
+ getResignIds,
|
|
|
+ rememberResignId,
|
|
|
+ removeHandoverById,
|
|
|
+ saveHandover,
|
|
|
+ setResignLocalState,
|
|
|
+ updateCreatedResignation,
|
|
|
+ updateHandoverItemState,
|
|
|
+} from '@/utils/storage'
|
|
|
|
|
|
// ============ 模式判断 ============
|
|
|
function isServerMode() {
|
|
|
@@ -13,8 +31,7 @@ function serverPath() {
|
|
|
|
|
|
// ============ 枚举 ============
|
|
|
|
|
|
-// 前端离职类型(form.vue actionSheet 选项)→ 后端 resignationType 枚举
|
|
|
-// 后端枚举值未定,先用与前端一致的中文 key,由后端后续给出映射表时再调整
|
|
|
+// 员工端申请表单(form.vue actionSheet)→ 后端 resignationType 枚举
|
|
|
export const RESIGNATION_TYPE_MAP = {
|
|
|
'主动离职': 'VOLUNTARY',
|
|
|
'协商离职': 'NEGOTIATED',
|
|
|
@@ -32,18 +49,884 @@ export const RESIGN_REASON_MAP = {
|
|
|
'健康原因': 'HEALTH',
|
|
|
}
|
|
|
|
|
|
+// M02 后端枚举 → 中文(与 PC 端 src/api/hr/resignation.js 对齐)
|
|
|
+export const RESIGNATION_TYPE_LABELS = {
|
|
|
+ VOLUNTARY: '主动离职',
|
|
|
+ NEGOTIATED: '协商解除',
|
|
|
+ PROBATION_FAILED: '试用不合格',
|
|
|
+ PERFORMANCE_FAILED: '绩效不合格',
|
|
|
+ TERMINATED: '辞退',
|
|
|
+ DISMISSED: '辞退',
|
|
|
+ CONTRACT_EXPIRED: '合同到期',
|
|
|
+ RETIRE: '退休',
|
|
|
+ RETIREMENT: '退休',
|
|
|
+ OTHER: '其他',
|
|
|
+}
|
|
|
+
|
|
|
+export const APPROVAL_STATUS_LABELS = {
|
|
|
+ DRAFT: '草稿',
|
|
|
+ PENDING: '审批中',
|
|
|
+ APPROVING: '审批中',
|
|
|
+ APPROVED: '已通过',
|
|
|
+ REJECTED: '已驳回',
|
|
|
+ WITHDRAWN: '已撤回',
|
|
|
+ CANCELLED: '已取消',
|
|
|
+}
|
|
|
+
|
|
|
+export const HANDOVER_STATUS_LABELS = {
|
|
|
+ NOT_STARTED: '未开始',
|
|
|
+ PENDING: '待交接',
|
|
|
+ IN_PROGRESS: '交接中',
|
|
|
+ PROCESSING: '交接中',
|
|
|
+ COMPLETED: '已完成',
|
|
|
+ FORCE_COMPLETED: '强制办结',
|
|
|
+}
|
|
|
+
|
|
|
+export const EFFECTIVE_STATUS_LABELS = {
|
|
|
+ PENDING: '待生效',
|
|
|
+ SCHEDULED: '已排期',
|
|
|
+ EFFECTIVE: '已生效',
|
|
|
+ CANCELLED: '已取消',
|
|
|
+}
|
|
|
+
|
|
|
+export const ARCHIVE_STATUS_LABELS = {
|
|
|
+ NOT_ARCHIVED: '未归档',
|
|
|
+ PENDING: '待归档',
|
|
|
+ ARCHIVED: '已归档',
|
|
|
+}
|
|
|
+
|
|
|
+export const HANDOVER_CATEGORY_LABELS = {
|
|
|
+ WORK: '工作交接',
|
|
|
+ ASSET: '资产交接',
|
|
|
+ FINANCE: '财务交接',
|
|
|
+ HR: 'HR 交接',
|
|
|
+}// 流程展示状态 → 标签 / 样式(对应 H5 现有 .status-* 原子类)
|
|
|
+export const RESIGN_DISPLAY_STATUS = {
|
|
|
+ draft: { label: '草稿', class: 'status-pending' },
|
|
|
+ pending: { label: '审批中', class: 'status-pending' },
|
|
|
+ rejected: { label: '已驳回', class: 'status-rejected' },
|
|
|
+ withdrawn: { label: '已撤回', class: 'status-rejected' },
|
|
|
+ cancelled: { label: '已取消', class: 'status-rejected' },
|
|
|
+ overdue: { label: '已逾期', class: 'status-rejected' },
|
|
|
+ approved: { label: '已通过', class: 'status-approved' },
|
|
|
+ handover: { label: '交接中', class: 'status-processing' },
|
|
|
+ completed: { label: '已完成', class: 'status-approved' },
|
|
|
+}
|
|
|
+
|
|
|
+// 服务器模式流程节点:与 M02 状态机(提交 → 审批 → 交接 → 归档)一一对应
|
|
|
+export const RESIGN_FLOW_STAGES = [
|
|
|
+ { key: 'submit', label: '申请提交', desc: '员工提交离职申请并填写预计离职日' },
|
|
|
+ { key: 'approval', label: '离职审批', desc: '直属主管、部门负责人与 HRBP 审批' },
|
|
|
+ { key: 'handover', label: '工作交接', desc: '交接人确认工作、资产与文档移交' },
|
|
|
+ { key: 'archive', label: '归档生效', desc: '交接完成后归档,劳动关系解除' },
|
|
|
+]
|
|
|
+
|
|
|
+// 列表页状态筛选:value 对应 displayStatus
|
|
|
+export const RESIGN_STATUS_FILTERS = [
|
|
|
+ { value: 'all', label: '全部状态' },
|
|
|
+ { value: 'pending', label: '审批中' },
|
|
|
+ { value: 'handover', label: '交接中' },
|
|
|
+ { value: 'completed', label: '已完成' },
|
|
|
+ { value: 'rejected', label: '已驳回' },
|
|
|
+ { value: 'withdrawn', label: '已撤回' },
|
|
|
+]
|
|
|
+
|
|
|
+// 生成交接单时的默认事项(工作 / 资产 / 财务 / HR 四类,与 PC 端服务端默认项一致)
|
|
|
+export const DEFAULT_HANDOVER_ITEMS = [
|
|
|
+ { category: 'WORK', name: '在办工作事项与项目进度移交', detail: '未完成事项清单、排期与对接人说明' },
|
|
|
+ { category: 'WORK', name: '文档、资料与系统权限交接', detail: '共享盘文档归档、系统账号与权限移交' },
|
|
|
+ { category: 'ASSET', name: '办公设备与门禁工牌归还', detail: '电脑、仪器、工牌、门禁卡归还登记' },
|
|
|
+ { category: 'FINANCE', name: '借款、报销与备用金结清', detail: '未清借款、报销单据与备用金核对' },
|
|
|
+ { category: 'HR', name: '考勤、假期与档案信息确认', detail: '剩余年假折算、社保公积金停缴确认' },
|
|
|
+]
|
|
|
+
|
|
|
+// 演示数据状态 → 流程展示状态
|
|
|
+const DEMO_STATUS_TO_DISPLAY = {
|
|
|
+ draft: 'draft',
|
|
|
+ pending: 'pending',
|
|
|
+ approved: 'approved',
|
|
|
+ processing: 'handover',
|
|
|
+ overdue: 'overdue',
|
|
|
+ done: 'completed',
|
|
|
+ cancelled: 'withdrawn',
|
|
|
+ rejected: 'rejected',
|
|
|
+}
|
|
|
+
|
|
|
+// ============ 工具函数 ============
|
|
|
+
|
|
|
+function labelOf(map, raw, fallback = '') {
|
|
|
+ const text = String(raw || '').trim()
|
|
|
+ if (!text) return fallback
|
|
|
+ return map[text] || map[text.toUpperCase()] || text
|
|
|
+}
|
|
|
+
|
|
|
+function dateOnly(value) {
|
|
|
+ if (!value) return ''
|
|
|
+ return String(value).slice(0, 10)
|
|
|
+}
|
|
|
+
|
|
|
+function compact(object = {}) {
|
|
|
+ return Object.keys(object).reduce((result, key) => {
|
|
|
+ const value = object[key]
|
|
|
+ if (value !== '' && value !== null && value !== undefined) result[key] = value
|
|
|
+ return result
|
|
|
+ }, {})
|
|
|
+}
|
|
|
+
|
|
|
+function asList(data) {
|
|
|
+ if (Array.isArray(data)) return data
|
|
|
+ if (Array.isArray(data?.list)) return data.list
|
|
|
+ if (Array.isArray(data?.records)) return data.records
|
|
|
+ if (data && typeof data === 'object' && data.id != null) return [data]
|
|
|
+ return []
|
|
|
+}
|
|
|
+
|
|
|
+function isDoneItemStatus(status) {
|
|
|
+ return ['COMPLETED', 'DONE', 'FINISHED'].includes(String(status || '').toUpperCase())
|
|
|
+}
|
|
|
+
|
|
|
+function approvalCode(raw = {}) {
|
|
|
+ return String(raw.approvalStatus || raw.statusCode || '').toUpperCase()
|
|
|
+}
|
|
|
+
|
|
|
+// ============ 状态推导与操作可用性(与 PC 端保持一致) ============
|
|
|
+
|
|
|
+export function deriveResignationDisplayStatus(raw = {}) {
|
|
|
+ const archive = String(raw.archiveStatus || '').toUpperCase()
|
|
|
+ const effective = String(raw.effectiveStatus || '').toUpperCase()
|
|
|
+ const handover = String(raw.handoverStatus || '').toUpperCase()
|
|
|
+ const approval = String(raw.approvalStatus || '').toUpperCase()
|
|
|
+
|
|
|
+ if (approval === 'CANCELLED') return 'cancelled'
|
|
|
+ if (archive === 'ARCHIVED' || effective === 'EFFECTIVE') return 'completed'
|
|
|
+ if (approval === 'APPROVED') {
|
|
|
+ if (!handover || ['IN_PROGRESS', 'PROCESSING', 'PENDING', 'NOT_STARTED'].includes(handover)) {
|
|
|
+ return 'handover'
|
|
|
+ }
|
|
|
+ return 'approved'
|
|
|
+ }
|
|
|
+ if (approval === 'REJECTED') return 'rejected'
|
|
|
+ if (approval === 'PENDING' || approval === 'APPROVING') return 'pending'
|
|
|
+ if (approval === 'WITHDRAWN') return 'withdrawn'
|
|
|
+ return 'draft'
|
|
|
+}
|
|
|
+
|
|
|
+export function canSubmitResignation(row = {}) {
|
|
|
+ if (!row.id) return false
|
|
|
+ const code = approvalCode(row)
|
|
|
+ return code === 'DRAFT' || code === 'REJECTED' || code === ''
|
|
|
+}
|
|
|
+
|
|
|
+export function canWithdrawResignation(row = {}) {
|
|
|
+ const code = approvalCode(row)
|
|
|
+ return code === 'PENDING' || code === 'APPROVING' || row.displayStatus === 'pending'
|
|
|
+}
|
|
|
+
|
|
|
+export function canCancelResignation(row = {}) {
|
|
|
+ if (!row.id) return false
|
|
|
+ if (approvalCode(row) === 'CANCELLED') return false
|
|
|
+ return row.displayStatus !== 'completed'
|
|
|
+}
|
|
|
+
|
|
|
+export function canGenerateHandover(row = {}) {
|
|
|
+ if (!row.id || row.handover) return false
|
|
|
+ if (approvalCode(row) === 'APPROVED') return true
|
|
|
+ // 演示模式没有真实审批流,审批中即可先建交接单,便于走完整流程
|
|
|
+ return row.source === 'demo' && ['pending', 'approved', 'handover'].includes(row.displayStatus)
|
|
|
+}
|
|
|
+
|
|
|
+export function canArchiveResignation(row = {}) {
|
|
|
+ if (!row.id) return false
|
|
|
+ if (!row.handover || row.handover.status !== 'completed') return false
|
|
|
+ return String(row.archiveStatus || '').toUpperCase() !== 'ARCHIVED'
|
|
|
+}
|
|
|
+
|
|
|
+export function canForceCompleteResignation(row = {}) {
|
|
|
+ if (!row.id) return false
|
|
|
+ return (
|
|
|
+ String(row.archiveStatus || '').toUpperCase() !== 'ARCHIVED' &&
|
|
|
+ row.displayStatus !== 'completed'
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export function matchDisplayStatus(row = {}, filter = '') {
|
|
|
+ if (!filter || filter === 'all') return true
|
|
|
+ if (filter === 'rejected') return ['rejected', 'cancelled'].includes(row.displayStatus)
|
|
|
+ if (filter === 'withdrawn') return ['withdrawn', 'cancelled'].includes(row.displayStatus)
|
|
|
+ return row.displayStatus === filter
|
|
|
+}
|
|
|
+// ============ VO → 页面形状映射 ============
|
|
|
+
|
|
|
+export function adaptHandoverItem(raw = {}) {
|
|
|
+ const done = isDoneItemStatus(raw.itemStatus || raw.status)
|
|
|
+ const category = raw.itemCategory || raw.category || 'WORK'
|
|
|
+ return {
|
|
|
+ id: raw.id,
|
|
|
+ name: raw.itemName || raw.name || '',
|
|
|
+ detail: raw.itemContent || raw.detail || raw.completionRemark || '',
|
|
|
+ category,
|
|
|
+ categoryLabel: labelOf(HANDOVER_CATEGORY_LABELS, category),
|
|
|
+ receiverUserId: raw.receiverUserId,
|
|
|
+ receiverName: raw.receiverName || '',
|
|
|
+ status: raw.itemStatus || raw.status || '',
|
|
|
+ done,
|
|
|
+ doneAt: dateOnly(raw.completeTime || raw.doneAt),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function adaptHandover(raw = {}) {
|
|
|
+ if (!raw || typeof raw !== 'object') return null
|
|
|
+ const items = (raw.items || []).map(adaptHandoverItem)
|
|
|
+ const doneCount = items.filter((item) => item.done).length
|
|
|
+ const itemsProgress = items.length ? Math.round((doneCount / items.length) * 100) : 0
|
|
|
+ const code = String(raw.handoverStatus || '').toUpperCase()
|
|
|
+ const completed =
|
|
|
+ ['COMPLETED', 'FORCE_COMPLETED'].includes(code) || (items.length > 0 && doneCount === items.length)
|
|
|
+ const progress = completed ? 100 : itemsProgress
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: raw.id,
|
|
|
+ resignationId: raw.resignationId,
|
|
|
+ resignationNo: raw.resignationNo || '',
|
|
|
+ userId: raw.userId,
|
|
|
+ person: raw.handoverPerson || items.find((item) => item.receiverName)?.receiverName || '',
|
|
|
+ handoverDate: dateOnly(raw.handoverDate),
|
|
|
+ deadline: dateOnly(raw.deadline || raw.handoverDate),
|
|
|
+ remark: raw.remark || '',
|
|
|
+ status: completed ? 'completed' : progress > 0 ? 'processing' : 'not_started',
|
|
|
+ statusLabel: completed ? '已完成' : progress > 0 ? '交接中' : '待交接',
|
|
|
+ statusCode: raw.handoverStatus || '',
|
|
|
+ statusCodeLabel: labelOf(HANDOVER_STATUS_LABELS, raw.handoverStatus),
|
|
|
+ progress,
|
|
|
+ items,
|
|
|
+ updated: dateOnly(raw.handoverDate || raw.createTime),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function stageProgressOf(stageKey, stages, handover) {
|
|
|
+ const index = stages.findIndex((stage) => stage.key === stageKey)
|
|
|
+ if (index < 0) return 0
|
|
|
+ if (stageKey === 'handover' && handover) {
|
|
|
+ // 交接阶段按事项完成度在节点内平滑推进(50% ~ 99%)
|
|
|
+ return Math.min(99, 50 + Math.round((handover.progress || 0) / 2))
|
|
|
+ }
|
|
|
+ return Math.round(((index + 1) / stages.length) * 100)
|
|
|
+}
|
|
|
+
|
|
|
+function progressTipOf(displayStatus, handover) {
|
|
|
+ switch (displayStatus) {
|
|
|
+ case 'draft':
|
|
|
+ return '草稿未提交,请提交审批后进入离职流程。'
|
|
|
+ case 'pending':
|
|
|
+ return '离职审批中,请等待审批结果。'
|
|
|
+ case 'rejected':
|
|
|
+ return '审批已驳回,可修改后重新提交。'
|
|
|
+ case 'withdrawn':
|
|
|
+ return '本次离职申请已撤回。'
|
|
|
+ case 'cancelled':
|
|
|
+ return '本次离职申请已取消。'
|
|
|
+ case 'approved':
|
|
|
+ return '审批已通过,待生成离职交接单。'
|
|
|
+ case 'handover':
|
|
|
+ return handover
|
|
|
+ ? `交接进行中,事项已完成 ${handover.progress}%。`
|
|
|
+ : '审批已通过,待生成离职交接单。'
|
|
|
+ case 'completed':
|
|
|
+ return '离职流程已全部完成。'
|
|
|
+ case 'overdue':
|
|
|
+ return '已超过最后工作日,请尽快推进交接与结算。'
|
|
|
+ default:
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 服务器 VO → 页面形状 */
|
|
|
+export function adaptResignation(vo = {}, extras = {}) {
|
|
|
+ if (!vo || typeof vo !== 'object') return null
|
|
|
+ const displayStatus = deriveResignationDisplayStatus(vo)
|
|
|
+ const handover = vo.handover ? adaptHandover(vo.handover) : null
|
|
|
+ const statusMeta = RESIGN_DISPLAY_STATUS[displayStatus] || RESIGN_DISPLAY_STATUS.pending
|
|
|
+ const stages = RESIGN_FLOW_STAGES
|
|
|
+ const stageKey =
|
|
|
+ displayStatus === 'completed'
|
|
|
+ ? 'archive'
|
|
|
+ : displayStatus === 'handover' || displayStatus === 'approved'
|
|
|
+ ? 'handover'
|
|
|
+ : displayStatus === 'pending'
|
|
|
+ ? 'approval'
|
|
|
+ : 'submit'
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: vo.id,
|
|
|
+ source: 'server',
|
|
|
+ resignationNo: vo.resignationNo || '',
|
|
|
+ userId: vo.userId,
|
|
|
+ name: extras.name || vo.employeeName || vo.userName || '',
|
|
|
+ avatar: String(extras.name || vo.employeeName || '').charAt(0),
|
|
|
+ employeeNo: extras.employeeNo || vo.employeeNo || '',
|
|
|
+ department: extras.department || vo.departmentName || vo.deptName || '',
|
|
|
+ position: extras.position || vo.positionName || '',
|
|
|
+ entryDate: dateOnly(extras.hireDate || vo.hireDate),
|
|
|
+ expectedLeaveDate: dateOnly(vo.plannedLastWorkDate || vo.effectiveDate),
|
|
|
+ lastWorkDate: dateOnly(vo.effectiveDate),
|
|
|
+ applyDate: dateOnly(vo.applyDate || vo.createTime),
|
|
|
+ effectiveDate: dateOnly(vo.effectiveDate),
|
|
|
+ type: labelOf(RESIGNATION_TYPE_LABELS, vo.resignationType),
|
|
|
+ typeCode: vo.resignationType || '',
|
|
|
+ reason: vo.reason || '',
|
|
|
+ userComment: vo.userComment || '',
|
|
|
+ hrComment: vo.hrComment || '',
|
|
|
+ approvalStatus: vo.approvalStatus || '',
|
|
|
+ approvalStatusLabel: labelOf(APPROVAL_STATUS_LABELS, vo.approvalStatus),
|
|
|
+ handoverStatus: vo.handoverStatus || '',
|
|
|
+ handoverStatusLabel: labelOf(HANDOVER_STATUS_LABELS, vo.handoverStatus),
|
|
|
+ effectiveStatus: vo.effectiveStatus || '',
|
|
|
+ effectiveStatusLabel: labelOf(EFFECTIVE_STATUS_LABELS, vo.effectiveStatus),
|
|
|
+ archiveStatus: vo.archiveStatus || '',
|
|
|
+ archiveStatusLabel: labelOf(ARCHIVE_STATUS_LABELS, vo.archiveStatus),
|
|
|
+ displayStatus,
|
|
|
+ statusLabel: statusMeta.label,
|
|
|
+ statusClass: statusMeta.class,
|
|
|
+ stageKey,
|
|
|
+ stages,
|
|
|
+ progress: displayStatus === 'completed' ? 100 : stageProgressOf(stageKey, stages, handover),
|
|
|
+ progressTip: progressTipOf(displayStatus, handover),
|
|
|
+ handover,
|
|
|
+ onlineSign: Boolean(vo.approvalDocumentFileId || vo.onlineDocumentId),
|
|
|
+ createTime: vo.createTime || '',
|
|
|
+ raw: vo,
|
|
|
+ }
|
|
|
+}
|
|
|
+// ============ 演示数据 → 页面形状 ============
|
|
|
+
|
|
|
+function buildDemoHandoverItems(record = {}) {
|
|
|
+ const raw = record.handover || {}
|
|
|
+ return (raw.items || []).map((item, index) => {
|
|
|
+ const name = typeof item === 'string' ? item : item.name || ''
|
|
|
+ const detail = typeof item === 'string' ? '' : item.detail || item.content || ''
|
|
|
+ return {
|
|
|
+ id: `demo-item-${record.id}-${index}`,
|
|
|
+ name,
|
|
|
+ detail,
|
|
|
+ category: 'WORK',
|
|
|
+ categoryLabel: HANDOVER_CATEGORY_LABELS.WORK,
|
|
|
+ receiverName: raw.person || '',
|
|
|
+ done: false,
|
|
|
+ status: 'PENDING',
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 演示模式交接单:优先取本地已生成的,否则把 mock 交接信息归一成只读交接单
|
|
|
+export function getDemoHandover(record = {}) {
|
|
|
+ const stored = getHandoverByResignationId(record.id)
|
|
|
+ if (stored) return adaptHandover(stored)
|
|
|
+
|
|
|
+ const raw = record.handover || {}
|
|
|
+ const items = buildDemoHandoverItems(record)
|
|
|
+ if (!items.length && !raw.person) return null
|
|
|
+
|
|
|
+ // mock 只给了交接百分比,这里按比例把前 N 项标记为已交接,保证进度条与事项数一致
|
|
|
+ const rawProgress = Number(String(raw.progress || '').replace('%', '')) || 0
|
|
|
+ const readyCount = record.status === 'done' ? items.length : Math.round((rawProgress / 100) * items.length)
|
|
|
+ items.forEach((item, index) => {
|
|
|
+ item.done = index < readyCount
|
|
|
+ item.status = item.done ? 'COMPLETED' : 'PENDING'
|
|
|
+ })
|
|
|
+
|
|
|
+ const doneCount = items.filter((item) => item.done).length
|
|
|
+ const progress = items.length ? Math.round((doneCount / items.length) * 100) : rawProgress
|
|
|
+ const completed = record.status === 'done' || (items.length > 0 && doneCount === items.length)
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: `demo-handover-${record.id}`,
|
|
|
+ resignationId: record.id,
|
|
|
+ resignationNo: record.id,
|
|
|
+ userId: record.id,
|
|
|
+ person: raw.person || '',
|
|
|
+ handoverDate: '',
|
|
|
+ deadline: dateOnly(raw.deadline || record.expectedLeaveDate),
|
|
|
+ remark: raw.note || '',
|
|
|
+ status: completed ? 'completed' : progress > 0 ? 'processing' : 'not_started',
|
|
|
+ statusLabel: completed ? '已完成' : progress > 0 ? '交接中' : '待交接',
|
|
|
+ statusCode: completed ? 'COMPLETED' : progress > 0 ? 'IN_PROGRESS' : 'NOT_STARTED',
|
|
|
+ statusCodeLabel: completed ? '已完成' : progress > 0 ? '交接中' : '未开始',
|
|
|
+ progress: completed ? 100 : progress,
|
|
|
+ items,
|
|
|
+ updated: '',
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** mock / 本地新建的离职单 → 页面形状 */
|
|
|
+export function mapDemoResignation(record = {}, override = {}) {
|
|
|
+ const merged = { ...record, ...override }
|
|
|
+ const handover = getDemoHandover(merged)
|
|
|
+ const displayStatus = DEMO_STATUS_TO_DISPLAY[merged.status] || 'pending'
|
|
|
+ const statusMeta = RESIGN_DISPLAY_STATUS[displayStatus] || RESIGN_DISPLAY_STATUS.pending
|
|
|
+ const stageKey = merged.stage || 'submit'
|
|
|
+ const index = resignStages.findIndex((stage) => stage.key === stageKey)
|
|
|
+ const progress =
|
|
|
+ displayStatus === 'completed'
|
|
|
+ ? 100
|
|
|
+ : Math.round((Math.max(index, 0) / resignStages.length) * 100)
|
|
|
+ const currentStage = resignStages.find((stage) => stage.key === stageKey)
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...merged,
|
|
|
+ id: merged.id,
|
|
|
+ source: 'demo',
|
|
|
+ avatar: merged.avatar || String(merged.name || '').charAt(0),
|
|
|
+ employeeNo: merged.id,
|
|
|
+ entryDate: dateOnly(merged.entryDate),
|
|
|
+ expectedLeaveDate: dateOnly(merged.expectedLeaveDate),
|
|
|
+ lastWorkDate: dateOnly(merged.lastWorkDate),
|
|
|
+ applyDate: dateOnly(merged.submitDate),
|
|
|
+ typeCode: merged.typeCode || '',
|
|
|
+ approvalStatus: merged.approvalStatus || '',
|
|
|
+ archiveStatus: merged.archiveStatus || (displayStatus === 'completed' ? 'ARCHIVED' : ''),
|
|
|
+ displayStatus,
|
|
|
+ statusLabel: statusMeta.label,
|
|
|
+ statusClass: statusMeta.class,
|
|
|
+ stageKey,
|
|
|
+ stages: resignStages,
|
|
|
+ progress,
|
|
|
+ progressTip:
|
|
|
+ displayStatus === 'overdue'
|
|
|
+ ? '已超过最后工作日,请尽快推进交接与结算。'
|
|
|
+ : displayStatus === 'completed'
|
|
|
+ ? '离职流程已全部完成。'
|
|
|
+ : displayStatus === 'withdrawn'
|
|
|
+ ? '本次离职申请已撤回。'
|
|
|
+ : `当前进行:${currentStage?.label || '—'}`,
|
|
|
+ handover,
|
|
|
+ createTime: merged.submitDate || '',
|
|
|
+ raw: merged,
|
|
|
+ }
|
|
|
+}
|
|
|
+// ============ 员工信息补全(服务器模式) ============
|
|
|
+
|
|
|
+const employeeCache = new Map()
|
|
|
+
|
|
|
+async function fetchEmployeeBrief(userId) {
|
|
|
+ if (userId === undefined || userId === null || userId === '') return null
|
|
|
+ const key = String(userId)
|
|
|
+ if (employeeCache.has(key)) return employeeCache.get(key)
|
|
|
+ try {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/user/getById/${key}`, method: 'GET' })
|
|
|
+ const vo = res.data || {}
|
|
|
+ const brief = {
|
|
|
+ name: vo.name || vo.userName || '',
|
|
|
+ employeeNo: vo.jobNumber || '',
|
|
|
+ department: vo.deptName || '',
|
|
|
+ position: vo.postName || vo.position || '',
|
|
|
+ hireDate: dateOnly(vo.joinDate || vo.entryDate),
|
|
|
+ }
|
|
|
+ employeeCache.set(key, brief)
|
|
|
+ return brief
|
|
|
+ } catch (error) {
|
|
|
+ employeeCache.set(key, null)
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function extrasOf(brief) {
|
|
|
+ if (!brief) return {}
|
|
|
+ return {
|
|
|
+ name: brief.name,
|
|
|
+ employeeNo: brief.employeeNo,
|
|
|
+ department: brief.department,
|
|
|
+ position: brief.position,
|
|
|
+ hireDate: brief.hireDate,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ============ 服务器接口(M02-员工离职) ============
|
|
|
+
|
|
|
+/** 查询离职单详情 GET /main/resignations/{id} */
|
|
|
+export async function getResignationById(id) {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/resignations/${id}`, method: 'GET' })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 提交离职审批 POST /main/resignations/{id}/submit */
|
|
|
+export async function submitResignation(id) {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/resignations/${id}/submit`, method: 'POST' })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 撤回离职审批 POST /main/resignations/{id}/withdraw */
|
|
|
+export async function withdrawResignation(id, reason = '') {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/${id}/withdraw`,
|
|
|
+ method: 'POST',
|
|
|
+ data: compact({ reason }),
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 取消离职单 POST /main/resignations/cancel/{id}(注意:cancel 在路径中间) */
|
|
|
+export async function cancelResignation(id, reason = '') {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/cancel/${id}`,
|
|
|
+ method: 'POST',
|
|
|
+ data: compact({ reason }),
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 归档离职单 POST /main/resignations/{id}/archive */
|
|
|
+export async function archiveResignation(id) {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/resignations/${id}/archive`, method: 'POST' })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 强制办结离职单 POST /main/resignations/{id}/force-complete(原因必填) */
|
|
|
+export async function forceCompleteResignation(id, reason) {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/${id}/force-complete`,
|
|
|
+ method: 'POST',
|
|
|
+ data: { reason },
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 生成离职交接单 POST /main/resignations/{id}/generate-handover */
|
|
|
+export async function generateResignationHandover(id) {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/${id}/generate-handover`,
|
|
|
+ method: 'POST',
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 完成离职交接事项 PUT /main/resignations/{id}/handover/items/{itemId}/complete */
|
|
|
+export async function completeResignationHandoverItem(resignationId, itemId, remark = '') {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/${resignationId}/handover/items/${itemId}/complete`,
|
|
|
+ method: 'PUT',
|
|
|
+ data: compact({ reason: remark }),
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 分页查询离职交接单 POST /main/handovers/page */
|
|
|
+export async function getHandoverPage(params = {}) {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/handovers/page`,
|
|
|
+ method: 'POST',
|
|
|
+ data: compact({
|
|
|
+ pageNum: params.pageNum,
|
|
|
+ size: params.size,
|
|
|
+ handoverStatus: params.handoverStatus || undefined,
|
|
|
+ resignationNo: params.keyword || undefined,
|
|
|
+ }),
|
|
|
+ })
|
|
|
+ const data = res.data || {}
|
|
|
+ return { list: asList(data), count: Number(data?.count ?? data?.total ?? 0) }
|
|
|
+}
|
|
|
+
|
|
|
+/** 查询离职交接单 GET /main/handovers/{id} */
|
|
|
+export async function getHandoverDetail(id) {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/handovers/${id}`, method: 'GET' })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 删除离职交接单 DELETE /main/handovers/{id} */
|
|
|
+export async function deleteHandover(id) {
|
|
|
+ const res = await request({ url: `${serverPath()}/main/handovers/${id}`, method: 'DELETE' })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 回退离职交接事项 PUT /main/handovers/{id}/items/{itemId}/rollback */
|
|
|
+export async function rollbackHandoverItem(handoverId, itemId, reason = '') {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/handovers/${handoverId}/items/${itemId}/rollback`,
|
|
|
+ method: 'PUT',
|
|
|
+ data: compact({ rollbackReason: reason }),
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+
|
|
|
+/** 套用离职交接模板 POST /main/handovers/{id}/templates/{templateId}/apply */
|
|
|
+export async function applyHandoverTemplate(handoverId, templateId) {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/handovers/${handoverId}/templates/${templateId}/apply`,
|
|
|
+ method: 'POST',
|
|
|
+ })
|
|
|
+ return res.data
|
|
|
+}
|
|
|
+// ============ 演示模式本地实现 ============
|
|
|
+
|
|
|
+function mergeDemoRecords() {
|
|
|
+ const overrides = getResignations()
|
|
|
+ const created = getCreatedResignations()
|
|
|
+ const seen = new Set(created.map((item) => String(item.id)))
|
|
|
+ const base = [...created, ...resignings.filter((item) => !seen.has(String(item.id)))]
|
|
|
+ return base.map((record) => mapDemoResignation(record, overrides[String(record.id)] || {}))
|
|
|
+}
|
|
|
+
|
|
|
+function updateDemoResignation(id, patch) {
|
|
|
+ setResignLocalState(id, patch)
|
|
|
+ updateCreatedResignation(id, patch)
|
|
|
+ return mergeDemoRecords().find((item) => String(item.id) === String(id)) || null
|
|
|
+}
|
|
|
+
|
|
|
+function buildDefaultDemoItems(resignationId) {
|
|
|
+ return DEFAULT_HANDOVER_ITEMS.map((item, index) => ({
|
|
|
+ id: `demo-item-${resignationId}-new-${index}`,
|
|
|
+ itemName: item.name,
|
|
|
+ itemContent: item.detail,
|
|
|
+ itemCategory: item.category,
|
|
|
+ itemStatus: 'PENDING',
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+// 演示模式的「生成交接单」:把当前事项落成本地交接单,供勾选 / 回退
|
|
|
+function materializeDemoHandover(resignationId) {
|
|
|
+ const existing = getHandoverByResignationId(resignationId)
|
|
|
+ if (existing) return existing
|
|
|
+ const record = mergeDemoRecords().find((item) => String(item.id) === String(resignationId))
|
|
|
+ if (!record) return null
|
|
|
+ const items = (record.handover?.items || []).map((item, index) => ({
|
|
|
+ id: `demo-item-${resignationId}-${index}`,
|
|
|
+ itemName: item.name,
|
|
|
+ itemContent: item.detail,
|
|
|
+ itemCategory: item.category || 'WORK',
|
|
|
+ itemStatus: item.done ? 'COMPLETED' : 'PENDING',
|
|
|
+ receiverName: record.handover?.person || '',
|
|
|
+ }))
|
|
|
+ return saveHandover({
|
|
|
+ id: record.handover?.id || `demo-handover-${resignationId}`,
|
|
|
+ resignationId,
|
|
|
+ resignationNo: record.resignationNo || resignationId,
|
|
|
+ handoverDate: record.expectedLeaveDate || '',
|
|
|
+ remark: record.handover?.remark || '',
|
|
|
+ items: items.length ? items : buildDefaultDemoItems(resignationId),
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function syncDemoHandoverStatus(resignationId, handover) {
|
|
|
+ if (!handover) return
|
|
|
+ const items = handover.items || []
|
|
|
+ const done = items.filter((item) => isDoneItemStatus(item.itemStatus)).length
|
|
|
+ const finished = items.length > 0 && done === items.length
|
|
|
+ updateDemoResignation(resignationId, {
|
|
|
+ handoverStatus: finished ? 'COMPLETED' : done > 0 ? 'IN_PROGRESS' : 'NOT_STARTED',
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// ============ 对外统一入口(页面只调用这些) ============
|
|
|
+
|
|
|
+/**
|
|
|
+ * 离职单列表。
|
|
|
+ * 服务器模式:POST /main/resignations/page 分页查询(分页体 {list, count});
|
|
|
+ * 集合查询不可用时,按本地记住的主键逐个拉详情兜底(与 PC 端策略一致)。
|
|
|
+ * 演示模式:mock 数据 + 本地新建 / 覆盖。
|
|
|
+ */
|
|
|
+export async function loadResignationList(params = {}) {
|
|
|
+ const { keyword = '', status = 'all', pageNum = 1, size = 50 } = params
|
|
|
+
|
|
|
+ if (!isServerMode()) {
|
|
|
+ const list = mergeDemoRecords()
|
|
|
+ .filter((row) => {
|
|
|
+ if (!matchDisplayStatus(row, status)) return false
|
|
|
+ if (!keyword) return true
|
|
|
+ return `${row.name}${row.employeeNo}${row.department}${row.position}`.includes(keyword)
|
|
|
+ })
|
|
|
+ .sort((a, b) =>
|
|
|
+ String(b.applyDate || b.createTime || '').localeCompare(
|
|
|
+ String(a.applyDate || a.createTime || ''),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ return { list, count: list.length, source: 'demo' }
|
|
|
+ }
|
|
|
+
|
|
|
+ let rows = []
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: `${serverPath()}/main/resignations/page`,
|
|
|
+ method: 'POST',
|
|
|
+ data: compact({ pageNum, size, resignationNo: keyword || undefined }),
|
|
|
+ })
|
|
|
+ rows = asList(res.data)
|
|
|
+ } catch (error) {
|
|
|
+ rows = []
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!rows.length) {
|
|
|
+ const details = await Promise.all(
|
|
|
+ getResignIds().map(async (id) => {
|
|
|
+ try {
|
|
|
+ return await getResignationById(id)
|
|
|
+ } catch (error) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ rows = details.filter(Boolean)
|
|
|
+ }
|
|
|
+
|
|
|
+ const list = []
|
|
|
+ for (const vo of rows) {
|
|
|
+ const brief = await fetchEmployeeBrief(vo.userId)
|
|
|
+ const row = adaptResignation(vo, extrasOf(brief))
|
|
|
+ if (!row) continue
|
|
|
+ if (row.id != null) rememberResignId(row.id)
|
|
|
+ if (!matchDisplayStatus(row, status)) continue
|
|
|
+ if (keyword && !`${row.name}${row.employeeNo}${row.resignationNo}${row.department}`.includes(keyword)) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ list.push(row)
|
|
|
+ }
|
|
|
+
|
|
|
+ return { list, count: list.length, source: 'api' }
|
|
|
+}
|
|
|
+
|
|
|
+/** 离职单详情 */
|
|
|
+export async function loadResignationDetail(id) {
|
|
|
+ if (!id) return null
|
|
|
+
|
|
|
+ if (!isServerMode()) {
|
|
|
+ return mergeDemoRecords().find((item) => String(item.id) === String(id)) || null
|
|
|
+ }
|
|
|
+
|
|
|
+ const vo = await getResignationById(id)
|
|
|
+ if (!vo) return null
|
|
|
+ rememberResignId(id)
|
|
|
+ const brief = await fetchEmployeeBrief(vo.userId)
|
|
|
+ return adaptResignation(vo, extrasOf(brief))
|
|
|
+}
|
|
|
+
|
|
|
+/** 离职交接单列表 */
|
|
|
+export async function loadHandoverList(params = {}) {
|
|
|
+ const { keyword = '', status = 'all' } = params
|
|
|
+
|
|
|
+ if (!isServerMode()) {
|
|
|
+ const local = Object.values(getHandovers()).map(adaptHandover).filter(Boolean)
|
|
|
+ const localIds = new Set(local.map((item) => String(item.resignationId)))
|
|
|
+ const fromResignations = mergeDemoRecords()
|
|
|
+ .filter((row) => row.handover && !localIds.has(String(row.id)))
|
|
|
+ .map((row) => row.handover)
|
|
|
+ const list = [...local, ...fromResignations]
|
|
|
+ .filter((item) => (status === 'all' ? true : item.status === status))
|
|
|
+ .filter((item) => (keyword ? String(item.resignationNo || '').includes(keyword) : true))
|
|
|
+ return { list, count: list.length, source: 'demo' }
|
|
|
+ }
|
|
|
+
|
|
|
+ const page = await getHandoverPage({ keyword })
|
|
|
+ const list = page.list.map(adaptHandover).filter(Boolean)
|
|
|
+ return { list, count: page.count || list.length, source: 'api' }
|
|
|
+}
|
|
|
+
|
|
|
+/** 交接单详情(含事项) */
|
|
|
+export async function loadHandoverDetail(id) {
|
|
|
+ if (!id) return null
|
|
|
+ if (!isServerMode()) {
|
|
|
+ const stored = getHandoverById(id)
|
|
|
+ return stored ? adaptHandover(stored) : null
|
|
|
+ }
|
|
|
+ const vo = await getHandoverDetail(id)
|
|
|
+ return adaptHandover(vo)
|
|
|
+}
|
|
|
+
|
|
|
+// —— 流程动作:服务器模式走接口,演示模式改本地态 ——
|
|
|
+
|
|
|
+export async function actSubmitResignation(row) {
|
|
|
+ if (isServerMode()) return submitResignation(row.id)
|
|
|
+ return updateDemoResignation(row.id, { status: 'pending', stage: 'manager', approvalStatus: 'PENDING' })
|
|
|
+}
|
|
|
+
|
|
|
+export async function actWithdrawResignation(row, reason = '') {
|
|
|
+ if (isServerMode()) return withdrawResignation(row.id, reason)
|
|
|
+ return updateDemoResignation(row.id, {
|
|
|
+ status: 'cancelled',
|
|
|
+ stage: 'submit',
|
|
|
+ approvalStatus: 'WITHDRAWN',
|
|
|
+ hrComment: reason,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export async function actCancelResignation(row, reason = '') {
|
|
|
+ if (isServerMode()) return cancelResignation(row.id, reason)
|
|
|
+ return updateDemoResignation(row.id, {
|
|
|
+ status: 'cancelled',
|
|
|
+ stage: 'submit',
|
|
|
+ approvalStatus: 'CANCELLED',
|
|
|
+ hrComment: reason,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export async function actGenerateHandover(row) {
|
|
|
+ if (isServerMode()) return generateResignationHandover(row.id)
|
|
|
+ const handover = materializeDemoHandover(row.id)
|
|
|
+ updateDemoResignation(row.id, { status: 'processing', stage: 'handover', handoverStatus: 'PENDING' })
|
|
|
+ return handover
|
|
|
+}
|
|
|
+
|
|
|
+export async function actCompleteHandoverItem(resignationId, itemId, remark = '') {
|
|
|
+ if (isServerMode()) return completeResignationHandoverItem(resignationId, itemId, remark)
|
|
|
+ const handover = materializeDemoHandover(resignationId)
|
|
|
+ if (!handover) return null
|
|
|
+ const updated = updateHandoverItemState(handover.id, itemId, {
|
|
|
+ itemStatus: 'COMPLETED',
|
|
|
+ completionRemark: remark,
|
|
|
+ completeTime: formatNow(),
|
|
|
+ })
|
|
|
+ syncDemoHandoverStatus(resignationId, updated)
|
|
|
+ return updated
|
|
|
+}
|
|
|
+
|
|
|
+export async function actRollbackHandoverItem(resignationId, itemId, reason = '') {
|
|
|
+ if (isServerMode()) {
|
|
|
+ const stored = getHandoverByResignationId(resignationId)
|
|
|
+ if (!stored?.id) throw new Error('缺少交接单主键,无法回退')
|
|
|
+ return rollbackHandoverItem(stored.id, itemId, reason)
|
|
|
+ }
|
|
|
+ const handover = materializeDemoHandover(resignationId)
|
|
|
+ if (!handover) return null
|
|
|
+ const updated = updateHandoverItemState(handover.id, itemId, {
|
|
|
+ itemStatus: 'PENDING',
|
|
|
+ completionRemark: reason,
|
|
|
+ })
|
|
|
+ syncDemoHandoverStatus(resignationId, updated)
|
|
|
+ return updated
|
|
|
+}
|
|
|
+
|
|
|
+export async function actArchiveResignation(row) {
|
|
|
+ if (isServerMode()) return archiveResignation(row.id)
|
|
|
+ return updateDemoResignation(row.id, {
|
|
|
+ status: 'done',
|
|
|
+ stage: 'certificate',
|
|
|
+ archiveStatus: 'ARCHIVED',
|
|
|
+ effectiveStatus: 'EFFECTIVE',
|
|
|
+ lastWorkDate: row.lastWorkDate || row.expectedLeaveDate,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export async function actForceCompleteResignation(row, reason) {
|
|
|
+ if (isServerMode()) return forceCompleteResignation(row.id, reason)
|
|
|
+ return updateDemoResignation(row.id, {
|
|
|
+ status: 'done',
|
|
|
+ stage: 'certificate',
|
|
|
+ archiveStatus: 'ARCHIVED',
|
|
|
+ effectiveStatus: 'EFFECTIVE',
|
|
|
+ hrComment: reason,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export async function actDeleteHandover(handover) {
|
|
|
+ if (!handover?.id) return null
|
|
|
+ if (isServerMode()) return deleteHandover(handover.id)
|
|
|
+ removeHandoverById(handover.id)
|
|
|
+ updateDemoResignation(handover.resignationId, { handoverStatus: '' })
|
|
|
+ return true
|
|
|
+}
|
|
|
+// ============ 员工端申请表单 ============
|
|
|
+
|
|
|
/**
|
|
|
* 把表单字段映射成 /hr/ess/mobile/applications/resignation 请求参数。
|
|
|
- * 后端 M02 创建单据后进入既有 Flowable 审批 + 离职交接流程,部门和申请人由服务端按当前任职确定,
|
|
|
- * 前端不需要传 userId / deptId。
|
|
|
- *
|
|
|
- * @param {object} form form reactive(resignType / resignReason / expectedLeaveDate / reason / handover / handoverNote / compensation / needCertificate)
|
|
|
- * @returns {{
|
|
|
- * plannedLastWorkDate: string,
|
|
|
- * reason: string,
|
|
|
- * resignationType: string,
|
|
|
- * userComment?: string,
|
|
|
- * }}
|
|
|
+ * 后端创建 M02 离职单据后进入既有 Flowable 审批 + 离职交接流程,
|
|
|
+ * 部门和申请人由服务端按当前任职确定,前端不需要传 userId / deptId。
|
|
|
*/
|
|
|
export function buildResignationPayload(form) {
|
|
|
if (!form) return null
|
|
|
@@ -78,11 +961,7 @@ export function buildResignationPayload(form) {
|
|
|
/**
|
|
|
* 提交我的离职申请
|
|
|
* POST /hr/ess/mobile/applications/resignation
|
|
|
- *
|
|
|
* 响应 { code: 0, data: long, message },data 是新建单据的主键。
|
|
|
- *
|
|
|
- * @param {object} payload 见 buildResignationPayload 返回结构
|
|
|
- * @returns {Promise<number>} 服务端返回的单据主键(演示模式返回时间戳假 id)
|
|
|
*/
|
|
|
export async function submitResignationApplication(payload) {
|
|
|
if (!payload) throw new Error('申请参数为空')
|
|
|
@@ -94,7 +973,66 @@ export async function submitResignationApplication(payload) {
|
|
|
|
|
|
const path = `${serverPath()}/hr/ess/mobile/applications/resignation`
|
|
|
const res = await request({ url: path, method: 'POST', data: payload })
|
|
|
- // 接口约定的 data 是 long;防止后端返回 string(PHP/Java 都可能出现),统一转 number
|
|
|
const id = Number(res.data)
|
|
|
return Number.isFinite(id) ? id : 0
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 提交成功后登记离职单,让「离职管理」列表与详情能串起来:
|
|
|
+ * - 服务器模式:记住服务端返回的主键,集合接口不可用时按主键拉详情
|
|
|
+ * - 演示模式:本地新建一条离职单,走演示态流程
|
|
|
+ */
|
|
|
+export function registerSubmittedResignation({ serverId, form } = {}) {
|
|
|
+ if (serverId) {
|
|
|
+ rememberResignId(serverId)
|
|
|
+ return { id: serverId, source: 'server' }
|
|
|
+ }
|
|
|
+ const user = getCurrentUser() || {}
|
|
|
+ const record = {
|
|
|
+ id: `RS${Date.now()}`,
|
|
|
+ name: user.name || '',
|
|
|
+ avatar: user.initials || String(user.name || '').charAt(0),
|
|
|
+ department: user.department || '',
|
|
|
+ position: user.position || '',
|
|
|
+ entryDate: dateOnly(user.entryDate),
|
|
|
+ status: 'pending',
|
|
|
+ expectedLeaveDate: dateOnly(form?.expectedLeaveDate),
|
|
|
+ lastWorkDate: null,
|
|
|
+ submitDate: dateOnly(formatNow()),
|
|
|
+ type: form?.resignType || '主动离职',
|
|
|
+ reason: form?.resignReason || '',
|
|
|
+ handover: {
|
|
|
+ person: form?.handover || '待指派',
|
|
|
+ dept: user.department || '',
|
|
|
+ progress: '0%',
|
|
|
+ deadline: dateOnly(form?.expectedLeaveDate),
|
|
|
+ items: DEFAULT_HANDOVER_ITEMS.map((item) => item.name),
|
|
|
+ note: form?.handoverNote || '',
|
|
|
+ },
|
|
|
+ compensation: { scheme: form?.compensation || '待核算', amount: '—', status: '待核算', note: '' },
|
|
|
+ assetReturn: { status: '未开始', items: [] },
|
|
|
+ contractTermination: {
|
|
|
+ method: '待办理',
|
|
|
+ deadline: dateOnly(form?.expectedLeaveDate),
|
|
|
+ status: '待解除',
|
|
|
+ },
|
|
|
+ socialSecurity: { stopMonth: '', city: '', status: '待办理' },
|
|
|
+ financeSettle: {
|
|
|
+ lastSalary: '—',
|
|
|
+ annualLeave: '—',
|
|
|
+ compensation: '—',
|
|
|
+ payoutDate: '',
|
|
|
+ status: '待核算',
|
|
|
+ },
|
|
|
+ accountOff: { status: '待注销', items: [] },
|
|
|
+ certificate: {
|
|
|
+ status: form?.needCertificate === false ? '无需开具' : '待开具',
|
|
|
+ needCopy: false,
|
|
|
+ },
|
|
|
+ stage: 'manager',
|
|
|
+ approvalStatus: 'PENDING',
|
|
|
+ createdByApp: true,
|
|
|
+ }
|
|
|
+ addCreatedResignation(record)
|
|
|
+ return { id: record.id, source: 'demo' }
|
|
|
+}
|