diff --git a/packages/hr/README.md b/packages/hr/README.md new file mode 100644 index 000000000..f4d056b40 --- /dev/null +++ b/packages/hr/README.md @@ -0,0 +1,74 @@ +# @hotcrm/hr - Human Capital Management Module + +## Overview + +The HR/HCM module provides comprehensive human capital management functionality for HotCRM, enabling customers to manage the entire employee lifecycle from recruitment to retirement. + +## Features + +### 📊 Organizational Structure +- Employee master data management +- Position and job role definitions +- Department hierarchy and org charts + +### 🎯 Talent Acquisition +- Recruitment pipeline management +- Candidate tracking +- Application processing +- Interview scheduling +- Offer management +- Employee onboarding + +### 📈 Performance Management +- Performance reviews +- Goal setting and OKRs +- Performance tracking + +### 🎓 Learning & Development +- Training programs +- Professional certifications +- Skill development tracking + +### 📅 Time & Attendance +- Leave/time-off management +- Attendance tracking +- Working hours monitoring + +### 💰 Compensation Management +- Payroll tracking +- Compensation history + +## Objects + +- **employee** - Employee master data +- **position** - Job positions and org chart +- **department** - Organizational units +- **recruitment** - Hiring pipeline +- **candidate** - Job applicants +- **application** - Job applications +- **interview** - Interview scheduling +- **offer** - Job offers +- **onboarding** - Employee onboarding +- **performance_review** - Performance management +- **goal** - OKRs and goals +- **training** - Learning & development +- **certification** - Professional certifications +- **time_off** - Leave management +- **attendance** - Time tracking +- **payroll** - Compensation tracking + +## AI Enhancements + +- Resume parsing and candidate matching +- Interview question generation +- Performance prediction +- Skill gap analysis +- Succession planning recommendations + +## Usage + +```typescript +import { HRPlugin } from '@hotcrm/hr'; + +// The plugin will be automatically registered with the ObjectStack runtime +``` diff --git a/packages/hr/package.json b/packages/hr/package.json new file mode 100644 index 000000000..51739a938 --- /dev/null +++ b/packages/hr/package.json @@ -0,0 +1,33 @@ +{ + "name": "@hotcrm/hr", + "version": "1.0.0", + "description": "Human Capital Management module - Employee lifecycle, recruitment, performance, and payroll", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "lint": "eslint src --ext .ts" + }, + "keywords": [ + "hr", + "hcm", + "human-capital-management", + "recruitment", + "performance", + "payroll" + ], + "author": "HotCRM Team", + "license": "MIT", + "dependencies": { + "@hotcrm/core": "workspace:*", + "@objectstack/spec": "^0.8.1", + "typescript": "^5.3.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint": "^8.55.0" + } +} diff --git a/packages/hr/src/application.object.ts b/packages/hr/src/application.object.ts new file mode 100644 index 000000000..737719533 --- /dev/null +++ b/packages/hr/src/application.object.ts @@ -0,0 +1,180 @@ + +const Application = { + name: 'application', + label: '求职申请', + labelPlural: '求职申请', + icon: 'file-alt', + description: '候选人职位申请记录管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + application_number: { + type: 'text', + label: '申请编号', + unique: true, + maxLength: 40 + }, + candidate_id: { + type: 'lookup', + label: '候选人', + reference: 'candidate', + required: true + }, + recruitment_id: { + type: 'lookup', + label: '招聘需求', + reference: 'recruitment', + required: true + }, + applied_date: { + type: 'date', + label: '申请日期', + required: true, + defaultValue: '$today' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Submitted', + options: [ + { label: '已提交', value: 'Submitted' }, + { label: '筛选中', value: 'Screening' }, + { label: '待面试', value: 'Interview Scheduled' }, + { label: '面试中', value: 'Interviewing' }, + { label: '通过初选', value: 'Shortlisted' }, + { label: '已录用', value: 'Offer Extended' }, + { label: '已拒绝', value: 'Rejected' }, + { label: '已撤回', value: 'Withdrawn' } + ] + }, + stage: { + type: 'select', + label: '当前阶段', + options: [ + { label: '简历筛选', value: 'Resume Review' }, + { label: '电话面试', value: 'Phone Screen' }, + { label: '初试', value: 'First Interview' }, + { label: '复试', value: 'Second Interview' }, + { label: '终试', value: 'Final Interview' }, + { label: 'Offer沟通', value: 'Offer Discussion' }, + { label: '背景调查', value: 'Background Check' } + ] + }, + source: { + type: 'select', + label: '来源渠道', + options: [ + { label: '招聘网站', value: 'Job Board' }, + { label: '内推', value: 'Employee Referral' }, + { label: '猎头', value: 'Headhunter' }, + { label: '社交媒体', value: 'Social Media' }, + { label: '校园招聘', value: 'Campus' }, + { label: '主动投递', value: 'Direct Application' }, + { label: '其他', value: 'Other' } + ] + }, + referrer_id: { + type: 'lookup', + label: '内推人', + reference: 'employee', + description: '如果是内推,记录推荐人' + }, + rating: { + type: 'rating', + label: '综合评分', + max: 5 + }, + resume_url: { + type: 'url', + label: '简历链接' + }, + cover_letter: { + type: 'textarea', + label: '求职信', + rows: 6 + }, + rejection_reason: { + type: 'textarea', + label: '拒绝原因', + rows: 3, + description: '如果被拒绝,记录原因' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'Interviews', + type: 'hasMany', + object: 'Interview', + foreignKey: 'application_id', + label: '面试记录' + } + ], + listViews: [ + { + name: 'All', + label: '所有申请', + filters: [], + columns: ['application_number', 'candidate_id', 'recruitment_id', 'applied_date', 'status', 'stage'] + }, + { + name: 'Screening', + label: '筛选中', + filters: [['status', '=', 'Screening']], + columns: ['candidate_id', 'recruitment_id', 'applied_date', 'source', 'rating'] + }, + { + name: 'Interviewing', + label: '面试中', + filters: [['status', 'in', ['Interview Scheduled', 'Interviewing']]], + columns: ['candidate_id', 'recruitment_id', 'stage', 'rating', 'applied_date'] + }, + { + name: 'Shortlisted', + label: '已通过初选', + filters: [['status', '=', 'Shortlisted']], + columns: ['candidate_id', 'recruitment_id', 'stage', 'rating', 'applied_date'], + sort: [['rating', 'desc']] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['application_number', 'candidate_id', 'recruitment_id', 'applied_date'] + }, + { + label: '申请状态', + columns: 2, + fields: ['status', 'stage', 'rating'] + }, + { + label: '来源信息', + columns: 2, + fields: ['source', 'referrer_id'] + }, + { + label: '申请材料', + columns: 1, + fields: ['resume_url', 'cover_letter'] + }, + { + label: '其他信息', + columns: 1, + fields: ['rejection_reason', 'notes'] + } + ] + } +}; + +export default Application; diff --git a/packages/hr/src/attendance.object.ts b/packages/hr/src/attendance.object.ts new file mode 100644 index 000000000..f7eed059f --- /dev/null +++ b/packages/hr/src/attendance.object.ts @@ -0,0 +1,188 @@ + +const Attendance = { + name: 'attendance', + label: '考勤', + labelPlural: '考勤记录', + icon: 'clock', + description: '员工考勤和工时跟踪', + enable: { + searchable: true, + trackHistory: false, + activities: false, + feeds: false, + files: false + }, + fields: { + employee_id: { + type: 'lookup', + label: '员工', + reference: 'employee', + required: true + }, + attendance_date: { + type: 'date', + label: '考勤日期', + required: true + }, + check_in_time: { + type: 'datetime', + label: '签到时间' + }, + check_out_time: { + type: 'datetime', + label: '签退时间' + }, + work_hours: { + type: 'number', + label: '工作时长(小时)', + precision: 2, + readonly: true, + description: '自动计算的工作时长' + }, + status: { + type: 'select', + label: '考勤状态', + defaultValue: 'Present', + options: [ + { label: '正常', value: 'Present' }, + { label: '迟到', value: 'Late' }, + { label: '早退', value: 'Early Leave' }, + { label: '缺勤', value: 'Absent' }, + { label: '请假', value: 'On Leave' }, + { label: '出差', value: 'Business Trip' }, + { label: '远程办公', value: 'Remote Work' }, + { label: '加班', value: 'Overtime' } + ] + }, + late_minutes: { + type: 'number', + label: '迟到分钟数', + precision: 0 + }, + early_leave_minutes: { + type: 'number', + label: '早退分钟数', + precision: 0 + }, + overtime_hours: { + type: 'number', + label: '加班时长(小时)', + precision: 2 + }, + location: { + type: 'select', + label: '工作地点', + options: [ + { label: '办公室', value: 'Office' }, + { label: '远程', value: 'Remote' }, + { label: '客户现场', value: 'Client Site' }, + { label: '出差', value: 'Business Trip' } + ] + }, + check_in_location: { + type: 'location', + label: '签到位置', + description: 'GPS位置(如适用)' + }, + check_out_location: { + type: 'location', + label: '签退位置', + description: 'GPS位置(如适用)' + }, + is_approved: { + type: 'checkbox', + label: '是否已审核', + defaultValue: false + }, + approver_id: { + type: 'lookup', + label: '审核人', + reference: 'employee' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 3, + description: '异常情况说明等' + } + }, + listViews: [ + { + name: 'All', + label: '所有考勤', + filters: [], + columns: ['employee_id', 'attendance_date', 'check_in_time', 'check_out_time', 'work_hours', 'status'] + }, + { + name: 'Today', + label: '今日考勤', + filters: [['attendance_date', '=', '$today']], + columns: ['employee_id', 'check_in_time', 'check_out_time', 'work_hours', 'status', 'location'] + }, + { + name: 'MyAttendance', + label: '我的考勤', + filters: [['employee_id', '=', '$currentUser']], + columns: ['attendance_date', 'check_in_time', 'check_out_time', 'work_hours', 'status', 'overtime_hours'], + sort: [['attendance_date', 'desc']] + }, + { + name: 'Late', + label: '迟到记录', + filters: [['status', '=', 'Late']], + columns: ['employee_id', 'attendance_date', 'check_in_time', 'late_minutes', 'notes'] + }, + { + name: 'Absent', + label: '缺勤记录', + filters: [['status', '=', 'Absent']], + columns: ['employee_id', 'attendance_date', 'notes'] + }, + { + name: 'Overtime', + label: '加班记录', + filters: [ + ['status', '=', 'Overtime'], + ['overtime_hours', '>', 0] + ], + columns: ['employee_id', 'attendance_date', 'work_hours', 'overtime_hours', 'is_approved'], + sort: [['attendance_date', 'desc']] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['employee_id', 'attendance_date', 'status'] + }, + { + label: '时间记录', + columns: 2, + fields: ['check_in_time', 'check_out_time', 'work_hours'] + }, + { + label: '异常情况', + columns: 2, + fields: ['late_minutes', 'early_leave_minutes', 'overtime_hours'] + }, + { + label: '地点信息', + columns: 2, + fields: ['location', 'check_in_location', 'check_out_location'] + }, + { + label: '审核', + columns: 2, + fields: ['is_approved', 'approver_id'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Attendance; diff --git a/packages/hr/src/candidate.object.ts b/packages/hr/src/candidate.object.ts new file mode 100644 index 000000000..f6744408e --- /dev/null +++ b/packages/hr/src/candidate.object.ts @@ -0,0 +1,260 @@ + +const Candidate = { + name: 'candidate', + label: '候选人', + labelPlural: '候选人', + icon: 'user-check', + description: '求职候选人信息管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + first_name: { + type: 'text', + label: '名', + required: true, + searchable: true, + maxLength: 40 + }, + last_name: { + type: 'text', + label: '姓', + required: true, + searchable: true, + maxLength: 80 + }, + email: { + type: 'email', + label: '邮箱', + required: true, + unique: true + }, + phone: { + type: 'phone', + label: '电话' + }, + mobile_phone: { + type: 'phone', + label: '手机', + required: true + }, + linkedin_url: { + type: 'url', + label: 'LinkedIn链接' + }, + current_company: { + type: 'text', + label: '当前公司', + maxLength: 255 + }, + current_title: { + type: 'text', + label: '当前职位', + maxLength: 255 + }, + years_of_experience: { + type: 'number', + label: '工作年限', + precision: 1, + description: '总工作年限' + }, + highest_education: { + type: 'select', + label: '最高学历', + options: [ + { label: '博士', value: 'PhD' }, + { label: '硕士', value: 'Master' }, + { label: '本科', value: 'Bachelor' }, + { label: '专科', value: 'Associate' }, + { label: '高中', value: 'High School' }, + { label: '其他', value: 'Other' } + ] + }, + university: { + type: 'text', + label: '毕业院校', + maxLength: 255 + }, + major: { + type: 'text', + label: '专业', + maxLength: 255 + }, + current_salary: { + type: 'currency', + label: '当前薪资', + precision: 2 + }, + expected_salary: { + type: 'currency', + label: '期望薪资', + precision: 2 + }, + notice_period: { + type: 'select', + label: '离职周期', + options: [ + { label: '立即到岗', value: 'Immediate' }, + { label: '1周', value: '1 Week' }, + { label: '2周', value: '2 Weeks' }, + { label: '1个月', value: '1 Month' }, + { label: '2个月', value: '2 Months' }, + { label: '3个月', value: '3 Months' } + ] + }, + source: { + type: 'select', + label: '来源渠道', + options: [ + { label: '招聘网站', value: 'Job Board' }, + { label: '内推', value: 'Employee Referral' }, + { label: '猎头', value: 'Headhunter' }, + { label: '社交媒体', value: 'Social Media' }, + { label: '校园招聘', value: 'Campus' }, + { label: '主动投递', value: 'Direct Application' }, + { label: '其他', value: 'Other' } + ] + }, + rating: { + type: 'rating', + label: '候选人评级', + max: 5, + description: '综合评分(1-5星)' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'New', + options: [ + { label: '新候选人', value: 'New' }, + { label: '审核中', value: 'Under Review' }, + { label: '面试中', value: 'Interviewing' }, + { label: '已录用', value: 'Hired' }, + { label: '已拒绝', value: 'Rejected' }, + { label: '已撤回', value: 'Withdrawn' } + ] + }, + skills: { + type: 'tags', + label: '技能标签', + description: '候选人的主要技能' + }, + resume_url: { + type: 'url', + label: '简历链接' + }, + city: { + type: 'text', + label: '所在城市', + maxLength: 40 + }, + country: { + type: 'text', + label: '国家', + maxLength: 40 + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'Applications', + type: 'hasMany', + object: 'Application', + foreignKey: 'candidate_id', + label: '申请记录' + }, + { + name: 'Interviews', + type: 'hasMany', + object: 'Interview', + foreignKey: 'candidate_id', + label: '面试记录' + }, + { + name: 'Offers', + type: 'hasMany', + object: 'Offer', + foreignKey: 'candidate_id', + label: 'Offer记录' + } + ], + listViews: [ + { + name: 'All', + label: '所有候选人', + filters: [], + columns: ['first_name', 'last_name', 'email', 'current_company', 'current_title', 'status', 'rating'] + }, + { + name: 'New', + label: '新候选人', + filters: [['status', '=', 'New']], + columns: ['first_name', 'last_name', 'email', 'mobile_phone', 'current_company', 'years_of_experience', 'source'] + }, + { + name: 'Interviewing', + label: '面试中', + filters: [['status', '=', 'Interviewing']], + columns: ['first_name', 'last_name', 'current_company', 'current_title', 'rating', 'expected_salary'] + }, + { + name: 'TopRated', + label: '高评分', + filters: [ + ['rating', '>=', 4], + ['status', 'in', ['New', 'Under Review', 'Interviewing']] + ], + columns: ['first_name', 'last_name', 'current_company', 'rating', 'years_of_experience', 'expected_salary'], + sort: [['rating', 'desc']] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['first_name', 'last_name', 'email', 'phone', 'mobile_phone', 'linkedin_url'] + }, + { + label: '当前状况', + columns: 2, + fields: ['current_company', 'current_title', 'years_of_experience', 'status', 'rating'] + }, + { + label: '教育背景', + columns: 2, + fields: ['highest_education', 'university', 'major'] + }, + { + label: '薪资期望', + columns: 2, + fields: ['current_salary', 'expected_salary', 'notice_period'] + }, + { + label: '来源与位置', + columns: 2, + fields: ['source', 'city', 'country'] + }, + { + label: '技能与简历', + columns: 2, + fields: ['skills', 'resume_url'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Candidate; diff --git a/packages/hr/src/certification.object.ts b/packages/hr/src/certification.object.ts new file mode 100644 index 000000000..f364b4f35 --- /dev/null +++ b/packages/hr/src/certification.object.ts @@ -0,0 +1,209 @@ + +const Certification = { + name: 'certification', + label: '认证', + labelPlural: '认证', + icon: 'certificate', + description: '员工专业认证和资质管理', + enable: { + searchable: true, + trackHistory: true, + activities: false, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '认证名称', + required: true, + searchable: true, + maxLength: 255 + }, + employee_id: { + type: 'lookup', + label: '员工', + reference: 'employee', + required: true + }, + certification_type: { + type: 'select', + label: '认证类型', + options: [ + { label: '专业认证', value: 'Professional' }, + { label: '技术认证', value: 'Technical' }, + { label: '语言认证', value: 'Language' }, + { label: '管理认证', value: 'Management' }, + { label: '安全认证', value: 'Safety' }, + { label: '合规认证', value: 'Compliance' }, + { label: '其他', value: 'Other' } + ] + }, + issuing_organization: { + type: 'text', + label: '发证机构', + required: true, + maxLength: 255 + }, + certification_number: { + type: 'text', + label: '证书编号', + maxLength: 100 + }, + issue_date: { + type: 'date', + label: '颁发日期', + required: true + }, + expiry_date: { + type: 'date', + label: '到期日期', + description: '如果证书有效期限制' + }, + is_active: { + type: 'checkbox', + label: '是否有效', + defaultValue: true, + description: '证书当前是否有效' + }, + renewal_required: { + type: 'checkbox', + label: '需要续期', + defaultValue: false + }, + next_renewal_date: { + type: 'date', + label: '下次续期日期' + }, + training_id: { + type: 'lookup', + label: '关联培训', + reference: 'training', + description: '如果通过培训获得' + }, + score: { + type: 'number', + label: '考试成绩', + precision: 2, + description: '如适用,记录考试分数' + }, + certificate_url: { + type: 'url', + label: '证书链接/扫描件' + }, + verification_url: { + type: 'url', + label: '验证链接', + description: '在线验证证书的链接' + }, + cost: { + type: 'currency', + label: '认证费用', + precision: 2 + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Active', + options: [ + { label: '有效', value: 'Active' }, + { label: '即将到期', value: 'Expiring Soon' }, + { label: '已过期', value: 'Expired' }, + { label: '已撤销', value: 'Revoked' } + ] + }, + description: { + type: 'textarea', + label: '认证描述', + rows: 4, + description: '认证的内容和价值' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 3 + } + }, + listViews: [ + { + name: 'All', + label: '所有认证', + filters: [], + columns: ['title', 'employee_id', 'certification_type', 'issuing_organization', 'issue_date', 'status'] + }, + { + name: 'Active', + label: '有效认证', + filters: [['is_active', '=', true]], + columns: ['title', 'employee_id', 'certification_type', 'issuing_organization', 'issue_date', 'expiry_date'] + }, + { + name: 'ExpiringSoon', + label: '即将到期', + filters: [ + ['is_active', '=', true], + ['expiry_date', 'next_90_days'] + ], + columns: ['title', 'employee_id', 'issuing_organization', 'expiry_date', 'renewal_required'], + sort: [['expiry_date', 'asc']] + }, + { + name: 'Expired', + label: '已过期', + filters: [ + ['is_active', '=', false], + ['status', '=', 'Expired'] + ], + columns: ['title', 'employee_id', 'issuing_organization', 'expiry_date', 'renewal_required'] + }, + { + name: 'MyCertifications', + label: '我的认证', + filters: [['employee_id', '=', '$currentUser']], + columns: ['title', 'certification_type', 'issuing_organization', 'issue_date', 'status', 'expiry_date'] + } + ], + validationRules: [ + { + name: 'ValidExpiryDate', + errorMessage: '到期日期必须在颁发日期之后', + formula: 'AND(NOT(ISBLANK(expiry_date)), expiry_date <= issue_date)' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'employee_id', 'certification_type', 'issuing_organization', 'certification_number'] + }, + { + label: '日期信息', + columns: 2, + fields: ['issue_date', 'expiry_date', 'next_renewal_date'] + }, + { + label: '状态', + columns: 2, + fields: ['is_active', 'status', 'renewal_required'] + }, + { + label: '关联信息', + columns: 2, + fields: ['training_id', 'score', 'cost'] + }, + { + label: '证书资料', + columns: 2, + fields: ['certificate_url', 'verification_url'] + }, + { + label: '详细说明', + columns: 1, + fields: ['description', 'notes'] + } + ] + } +}; + +export default Certification; diff --git a/packages/hr/src/department.object.ts b/packages/hr/src/department.object.ts new file mode 100644 index 000000000..e747847cc --- /dev/null +++ b/packages/hr/src/department.object.ts @@ -0,0 +1,168 @@ + +const Department = { + name: 'department', + label: '部门', + labelPlural: '部门', + icon: 'sitemap', + description: '组织部门和团队结构管理', + enable: { + searchable: true, + trackHistory: true, + activities: false, + feeds: true, + files: false + }, + fields: { + name: { + type: 'text', + label: '部门名称', + required: true, + searchable: true, + unique: true, + maxLength: 255 + }, + code: { + type: 'text', + label: '部门编码', + unique: true, + maxLength: 40, + description: '部门唯一标识代码' + }, + type: { + type: 'select', + label: '部门类型', + options: [ + { label: '总部', value: 'Headquarters' }, + { label: '分公司', value: 'Branch' }, + { label: '事业部', value: 'Division' }, + { label: '部门', value: 'Department' }, + { label: '团队', value: 'Team' } + ] + }, + parent_id: { + type: 'lookup', + label: '上级部门', + reference: 'department', + description: '组织层级结构中的上级部门' + }, + manager_id: { + type: 'lookup', + label: '部门负责人', + reference: 'employee', + description: '管理该部门的员工' + }, + cost_center: { + type: 'text', + label: '成本中心', + maxLength: 40, + description: '财务成本核算中心代码' + }, + location: { + type: 'text', + label: '办公地点', + maxLength: 255 + }, + phone: { + type: 'phone', + label: '部门电话' + }, + email: { + type: 'email', + label: '部门邮箱' + }, + employee_count: { + type: 'number', + label: '员工人数', + readonly: true, + description: '部门内员工总数(自动计算)' + }, + budget_amount: { + type: 'currency', + label: '年度预算', + precision: 2 + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Active', + options: [ + { label: '活跃', value: 'Active' }, + { label: '已关闭', value: 'Closed' }, + { label: '合并中', value: 'Merging' } + ] + }, + description: { + type: 'textarea', + label: '部门描述', + rows: 4 + } + }, + relationships: [ + { + name: 'Employees', + type: 'hasMany', + object: 'Employee', + foreignKey: 'department_id', + label: '员工' + }, + { + name: 'Positions', + type: 'hasMany', + object: 'Position', + foreignKey: 'department_id', + label: '职位' + }, + { + name: 'SubDepartments', + type: 'hasMany', + object: 'Department', + foreignKey: 'parent_id', + label: '下级部门' + } + ], + listViews: [ + { + name: 'All', + label: '所有部门', + filters: [], + columns: ['name', 'code', 'type', 'manager_id', 'employee_count', 'status'] + }, + { + name: 'Active', + label: '活跃部门', + filters: [['status', '=', 'Active']], + columns: ['name', 'type', 'manager_id', 'employee_count', 'location', 'budget_amount'] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['name', 'code', 'type', 'status', 'parent_id', 'manager_id'] + }, + { + label: '联系信息', + columns: 2, + fields: ['location', 'phone', 'email'] + }, + { + label: '财务信息', + columns: 2, + fields: ['cost_center', 'budget_amount'] + }, + { + label: '统计信息', + columns: 2, + fields: ['employee_count'] + }, + { + label: '其他信息', + columns: 1, + fields: ['description'] + } + ] + } +}; + +export default Department; diff --git a/packages/hr/src/employee.object.ts b/packages/hr/src/employee.object.ts new file mode 100644 index 000000000..694d31b4b --- /dev/null +++ b/packages/hr/src/employee.object.ts @@ -0,0 +1,328 @@ + +const Employee = { + name: 'employee', + label: '员工', + labelPlural: '员工', + icon: 'user-tie', + description: '员工主数据和信息管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + employee_number: { + type: 'text', + label: '工号', + required: true, + unique: true, + maxLength: 40 + }, + first_name: { + type: 'text', + label: '名', + required: true, + searchable: true, + maxLength: 40 + }, + last_name: { + type: 'text', + label: '姓', + required: true, + searchable: true, + maxLength: 80 + }, + full_name: { + type: 'formula', + label: '全名', + formula: 'CONCATENATE(last_name, first_name)', + readonly: true + }, + email: { + type: 'email', + label: '公司邮箱', + required: true, + unique: true + }, + personal_email: { + type: 'email', + label: '个人邮箱' + }, + phone: { + type: 'phone', + label: '电话' + }, + mobile_phone: { + type: 'phone', + label: '手机', + required: true + }, + date_of_birth: { + type: 'date', + label: '出生日期' + }, + gender: { + type: 'select', + label: '性别', + options: [ + { label: '男', value: 'Male' }, + { label: '女', value: 'Female' }, + { label: '其他', value: 'Other' } + ] + }, + national_id: { + type: 'text', + label: '身份证号', + unique: true, + maxLength: 40, + description: '身份证或护照号码' + }, + marital_status: { + type: 'select', + label: '婚姻状况', + options: [ + { label: '未婚', value: 'Single' }, + { label: '已婚', value: 'Married' }, + { label: '离异', value: 'Divorced' }, + { label: '其他', value: 'Other' } + ] + }, + department_id: { + type: 'lookup', + label: '所属部门', + reference: 'department', + required: true + }, + position_id: { + type: 'lookup', + label: '职位', + reference: 'position', + required: true + }, + manager_id: { + type: 'lookup', + label: '直属经理', + reference: 'employee', + description: '直接汇报的上级' + }, + hire_date: { + type: 'date', + label: '入职日期', + required: true + }, + termination_date: { + type: 'date', + label: '离职日期' + }, + employment_status: { + type: 'select', + label: '雇佣状态', + defaultValue: 'Active', + options: [ + { label: '在职', value: 'Active' }, + { label: '试用期', value: 'Probation' }, + { label: '休假', value: 'On Leave' }, + { label: '已离职', value: 'Terminated' } + ] + }, + employment_type: { + type: 'select', + label: '雇佣类型', + defaultValue: 'Full-time', + options: [ + { label: '全职', value: 'Full-time' }, + { label: '兼职', value: 'Part-time' }, + { label: '合同', value: 'Contract' }, + { label: '实习', value: 'Intern' } + ] + }, + work_location: { + type: 'text', + label: '工作地点', + maxLength: 255 + }, + base_salary: { + type: 'currency', + label: '基本工资', + precision: 2 + }, + street: { + type: 'textarea', + label: '地址(街道)', + rows: 2 + }, + city: { + type: 'text', + label: '城市', + maxLength: 40 + }, + state: { + type: 'text', + label: '省份', + maxLength: 40 + }, + postal_code: { + type: 'text', + label: '邮编', + maxLength: 20 + }, + country: { + type: 'text', + label: '国家', + maxLength: 40 + }, + emergency_contact_name: { + type: 'text', + label: '紧急联系人', + maxLength: 80 + }, + emergency_contact_phone: { + type: 'phone', + label: '紧急联系电话' + }, + emergency_contact_relationship: { + type: 'text', + label: '紧急联系人关系', + maxLength: 40 + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'DirectReports', + type: 'hasMany', + object: 'Employee', + foreignKey: 'manager_id', + label: '下属' + }, + { + name: 'PerformanceReviews', + type: 'hasMany', + object: 'PerformanceReview', + foreignKey: 'employee_id', + label: '绩效评估' + }, + { + name: 'Goals', + type: 'hasMany', + object: 'Goal', + foreignKey: 'employee_id', + label: '目标' + }, + { + name: 'Trainings', + type: 'hasMany', + object: 'Training', + foreignKey: 'employee_id', + label: '培训记录' + }, + { + name: 'Certifications', + type: 'hasMany', + object: 'Certification', + foreignKey: 'employee_id', + label: '认证' + }, + { + name: 'TimeOffs', + type: 'hasMany', + object: 'TimeOff', + foreignKey: 'employee_id', + label: '请假记录' + }, + { + name: 'Attendances', + type: 'hasMany', + object: 'Attendance', + foreignKey: 'employee_id', + label: '考勤记录' + }, + { + name: 'Payrolls', + type: 'hasMany', + object: 'Payroll', + foreignKey: 'employee_id', + label: '工资记录' + } + ], + listViews: [ + { + name: 'All', + label: '所有员工', + filters: [], + columns: ['employee_number', 'full_name', 'department_id', 'position_id', 'employment_status', 'hire_date'] + }, + { + name: 'Active', + label: '在职员工', + filters: [['employment_status', '=', 'Active']], + columns: ['employee_number', 'full_name', 'department_id', 'position_id', 'manager_id', 'hire_date'] + }, + { + name: 'Probation', + label: '试用期员工', + filters: [['employment_status', '=', 'Probation']], + columns: ['employee_number', 'full_name', 'department_id', 'position_id', 'hire_date', 'manager_id'] + }, + { + name: 'Terminated', + label: '已离职', + filters: [['employment_status', '=', 'Terminated']], + columns: ['employee_number', 'full_name', 'department_id', 'termination_date', 'hire_date'] + } + ], + validationRules: [ + { + name: 'ValidTerminationDate', + errorMessage: '离职日期必须在入职日期之后', + formula: 'AND(NOT(ISBLANK(termination_date)), termination_date < hire_date)' + }, + { + name: 'TerminatedMustHaveDate', + errorMessage: '离职状态必须填写离职日期', + formula: 'AND(employment_status = "Terminated", ISBLANK(termination_date))' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['employee_number', 'first_name', 'last_name', 'email', 'mobile_phone', 'date_of_birth', 'gender', 'marital_status'] + }, + { + label: '雇佣信息', + columns: 2, + fields: ['department_id', 'position_id', 'manager_id', 'hire_date', 'employment_status', 'employment_type', 'work_location'] + }, + { + label: '薪资信息', + columns: 2, + fields: ['base_salary'] + }, + { + label: '联系地址', + columns: 2, + fields: ['street', 'city', 'state', 'postal_code', 'country'] + }, + { + label: '紧急联系人', + columns: 2, + fields: ['emergency_contact_name', 'emergency_contact_phone', 'emergency_contact_relationship'] + }, + { + label: '其他信息', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Employee; diff --git a/packages/hr/src/goal.object.ts b/packages/hr/src/goal.object.ts new file mode 100644 index 000000000..323edc033 --- /dev/null +++ b/packages/hr/src/goal.object.ts @@ -0,0 +1,238 @@ + +const Goal = { + name: 'goal', + label: '目标', + labelPlural: '目标', + icon: 'bullseye', + description: 'OKR和个人目标管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: false + }, + fields: { + title: { + type: 'text', + label: '目标名称', + required: true, + searchable: true, + maxLength: 255 + }, + employee_id: { + type: 'lookup', + label: '负责员工', + reference: 'employee', + required: true + }, + manager_id: { + type: 'lookup', + label: '目标设定人', + reference: 'employee', + description: '通常是直属经理' + }, + goal_type: { + type: 'select', + label: '目标类型', + defaultValue: 'Individual', + options: [ + { label: '个人目标', value: 'Individual' }, + { label: '团队目标', value: 'Team' }, + { label: 'OKR', value: 'OKR' }, + { label: '发展目标', value: 'Development' }, + { label: '项目目标', value: 'Project' } + ] + }, + category: { + type: 'select', + label: '目标类别', + options: [ + { label: '业绩', value: 'Performance' }, + { label: '技能发展', value: 'Skill Development' }, + { label: '领导力', value: 'Leadership' }, + { label: '创新', value: 'Innovation' }, + { label: '团队协作', value: 'Teamwork' }, + { label: '客户满意度', value: 'Customer Satisfaction' }, + { label: '其他', value: 'Other' } + ] + }, + priority: { + type: 'select', + label: '优先级', + defaultValue: 'Medium', + options: [ + { label: '高', value: 'High' }, + { label: '中', value: 'Medium' }, + { label: '低', value: 'Low' } + ] + }, + start_date: { + type: 'date', + label: '开始日期', + required: true + }, + target_date: { + type: 'date', + label: '目标日期', + required: true + }, + completion_date: { + type: 'date', + label: '完成日期' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Not Started', + options: [ + { label: '未开始', value: 'Not Started' }, + { label: '进行中', value: 'In Progress' }, + { label: '有风险', value: 'At Risk' }, + { label: '已完成', value: 'Completed' }, + { label: '未完成', value: 'Not Achieved' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + progress: { + type: 'percent', + label: '完成进度', + defaultValue: 0, + description: '目标完成百分比' + }, + target_value: { + type: 'number', + label: '目标值', + precision: 2, + description: '量化的目标数值' + }, + current_value: { + type: 'number', + label: '当前值', + precision: 2, + description: '当前达成的数值' + }, + unit: { + type: 'text', + label: '单位', + maxLength: 40, + description: '目标值的单位,如:个、万元、%' + }, + description: { + type: 'textarea', + label: '目标描述', + rows: 5, + description: '详细的目标说明和期望结果' + }, + key_results: { + type: 'textarea', + label: '关键结果(KR)', + rows: 5, + description: 'OKR的关键结果,可列出多个' + }, + performance_review_id: { + type: 'lookup', + label: '关联绩效评估', + reference: 'performance_review' + }, + weight: { + type: 'percent', + label: '权重', + description: '在绩效考核中的权重' + }, + achievement_notes: { + type: 'textarea', + label: '达成情况说明', + rows: 4 + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + listViews: [ + { + name: 'All', + label: '所有目标', + filters: [], + columns: ['title', 'employee_id', 'goal_type', 'status', 'progress', 'target_date'] + }, + { + name: 'MyGoals', + label: '我的目标', + filters: [['employee_id', '=', '$currentUser']], + columns: ['title', 'goal_type', 'priority', 'status', 'progress', 'target_date'] + }, + { + name: 'InProgress', + label: '进行中', + filters: [['status', '=', 'In Progress']], + columns: ['title', 'employee_id', 'goal_type', 'priority', 'progress', 'target_date'] + }, + { + name: 'AtRisk', + label: '有风险', + filters: [['status', '=', 'At Risk']], + columns: ['title', 'employee_id', 'priority', 'progress', 'target_date', 'manager_id'] + }, + { + name: 'DueSoon', + label: '即将到期', + filters: [ + ['status', 'in', ['Not Started', 'In Progress']], + ['target_date', 'next_30_days'] + ], + columns: ['title', 'employee_id', 'priority', 'progress', 'target_date'], + sort: [['target_date', 'asc']] + } + ], + validationRules: [ + { + name: 'ValidTargetDate', + errorMessage: '目标日期不能早于开始日期', + formula: 'target_date < start_date' + }, + { + name: 'ValidCompletion', + errorMessage: '完成状态必须设置完成日期', + formula: 'AND(status = "Completed", ISBLANK(completion_date))' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'employee_id', 'manager_id', 'goal_type', 'category', 'priority'] + }, + { + label: '时间信息', + columns: 2, + fields: ['start_date', 'target_date', 'completion_date'] + }, + { + label: '进度跟踪', + columns: 2, + fields: ['status', 'progress', 'target_value', 'current_value', 'unit'] + }, + { + label: '绩效关联', + columns: 2, + fields: ['performance_review_id', 'weight'] + }, + { + label: '目标详情', + columns: 1, + fields: ['description', 'key_results'] + }, + { + label: '达成情况', + columns: 1, + fields: ['achievement_notes', 'notes'] + } + ] + } +}; + +export default Goal; diff --git a/packages/hr/src/index.ts b/packages/hr/src/index.ts new file mode 100644 index 000000000..8475172ad --- /dev/null +++ b/packages/hr/src/index.ts @@ -0,0 +1,64 @@ +/** + * @hotcrm/hr - Human Capital Management Module + * + * This package contains all HR/HCM-related business objects for managing the complete employee lifecycle: + * + * **Organizational Structure:** + * - Department: Organizational units and hierarchy + * - Position: Job positions and roles + * - Employee: Employee master data + * + * **Talent Acquisition:** + * - Recruitment: Hiring pipeline and job requisitions + * - Candidate: Job applicants + * - Application: Job applications + * - Interview: Interview scheduling and feedback + * - Offer: Job offers + * - Onboarding: New employee onboarding + * + * **Performance Management:** + * - PerformanceReview: Performance evaluations + * - Goal: OKRs and personal goals + * + * **Learning & Development:** + * - Training: Training programs + * - Certification: Professional certifications + * + * **Time & Attendance:** + * - TimeOff: Leave/time-off management + * - Attendance: Time tracking and attendance + * + * **Compensation:** + * - Payroll: Payroll and compensation tracking + */ + +// Export organizational objects +export { default as Department } from './department.object'; +export { default as Position } from './position.object'; +export { default as Employee } from './employee.object'; + +// Export recruitment objects +export { default as Recruitment } from './recruitment.object'; +export { default as Candidate } from './candidate.object'; +export { default as Application } from './application.object'; +export { default as Interview } from './interview.object'; +export { default as Offer } from './offer.object'; +export { default as Onboarding } from './onboarding.object'; + +// Export performance management objects +export { default as PerformanceReview } from './performance_review.object'; +export { default as Goal } from './goal.object'; + +// Export learning & development objects +export { default as Training } from './training.object'; +export { default as Certification } from './certification.object'; + +// Export time & attendance objects +export { default as TimeOff } from './time_off.object'; +export { default as Attendance } from './attendance.object'; + +// Export payroll object +export { default as Payroll } from './payroll.object'; + +// Export plugin definition +export { default as HRPlugin } from './plugin'; diff --git a/packages/hr/src/interview.object.ts b/packages/hr/src/interview.object.ts new file mode 100644 index 000000000..6b31b5e04 --- /dev/null +++ b/packages/hr/src/interview.object.ts @@ -0,0 +1,216 @@ + +const Interview = { + name: 'interview', + label: '面试', + labelPlural: '面试', + icon: 'comments', + description: '面试安排和记录管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '面试标题', + required: true, + searchable: true, + maxLength: 255 + }, + candidate_id: { + type: 'lookup', + label: '候选人', + reference: 'candidate', + required: true + }, + application_id: { + type: 'lookup', + label: '申请', + reference: 'application', + required: true + }, + recruitment_id: { + type: 'lookup', + label: '招聘需求', + reference: 'recruitment', + required: true + }, + interview_type: { + type: 'select', + label: '面试类型', + defaultValue: 'First Round', + options: [ + { label: '电话面试', value: 'Phone Screen' }, + { label: '视频面试', value: 'Video Interview' }, + { label: '初试', value: 'First Round' }, + { label: '复试', value: 'Second Round' }, + { label: '终试', value: 'Final Round' }, + { label: '技术面试', value: 'Technical' }, + { label: 'HR面试', value: 'HR' } + ] + }, + scheduled_date: { + type: 'datetime', + label: '面试时间', + required: true + }, + duration: { + type: 'number', + label: '时长(分钟)', + defaultValue: 60 + }, + location: { + type: 'text', + label: '面试地点', + maxLength: 255, + description: '线下地点或线上会议链接' + }, + interviewer_id: { + type: 'lookup', + label: '主面试官', + reference: 'employee', + required: true + }, + panel_members: { + type: 'text', + label: '面试官小组', + maxLength: 500, + description: '多位面试官用逗号分隔' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Scheduled', + options: [ + { label: '已安排', value: 'Scheduled' }, + { label: '已确认', value: 'Confirmed' }, + { label: '进行中', value: 'In Progress' }, + { label: '已完成', value: 'Completed' }, + { label: '已取消', value: 'Cancelled' }, + { label: '候选人未到', value: 'No Show' }, + { label: '需重新安排', value: 'Rescheduled' } + ] + }, + result: { + type: 'select', + label: '面试结果', + options: [ + { label: '强烈推荐', value: 'Strong Hire' }, + { label: '推荐', value: 'Hire' }, + { label: '待定', value: 'Maybe' }, + { label: '不推荐', value: 'No Hire' }, + { label: '强烈不推荐', value: 'Strong No Hire' } + ] + }, + overall_rating: { + type: 'rating', + label: '综合评分', + max: 5 + }, + technical_rating: { + type: 'rating', + label: '技术能力', + max: 5 + }, + communication_rating: { + type: 'rating', + label: '沟通能力', + max: 5 + }, + cultural_fit_rating: { + type: 'rating', + label: '文化契合度', + max: 5 + }, + feedback: { + type: 'textarea', + label: '面试反馈', + rows: 8, + description: '详细的面试评价和建议' + }, + strengths: { + type: 'textarea', + label: '优势', + rows: 3 + }, + weaknesses: { + type: 'textarea', + label: '不足', + rows: 3 + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + listViews: [ + { + name: 'All', + label: '所有面试', + filters: [], + columns: ['title', 'candidate_id', 'recruitment_id', 'interview_type', 'scheduled_date', 'status'] + }, + { + name: 'Upcoming', + label: '即将进行', + filters: [ + ['status', 'in', ['Scheduled', 'Confirmed']], + ['scheduled_date', '>=', '$today'] + ], + columns: ['candidate_id', 'recruitment_id', 'interview_type', 'scheduled_date', 'interviewer_id', 'location'], + sort: [['scheduled_date', 'asc']] + }, + { + name: 'Completed', + label: '已完成', + filters: [['status', '=', 'Completed']], + columns: ['candidate_id', 'recruitment_id', 'interview_type', 'scheduled_date', 'result', 'overall_rating'] + }, + { + name: 'MyInterviews', + label: '我的面试', + filters: [['interviewer_id', '=', '$currentUser']], + columns: ['candidate_id', 'recruitment_id', 'interview_type', 'scheduled_date', 'status', 'result'] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'candidate_id', 'application_id', 'recruitment_id', 'interview_type'] + }, + { + label: '安排信息', + columns: 2, + fields: ['scheduled_date', 'duration', 'location', 'interviewer_id', 'panel_members'] + }, + { + label: '状态与结果', + columns: 2, + fields: ['status', 'result', 'overall_rating'] + }, + { + label: '能力评分', + columns: 2, + fields: ['technical_rating', 'communication_rating', 'cultural_fit_rating'] + }, + { + label: '面试评价', + columns: 1, + fields: ['feedback', 'strengths', 'weaknesses'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Interview; diff --git a/packages/hr/src/offer.object.ts b/packages/hr/src/offer.object.ts new file mode 100644 index 000000000..85a964c46 --- /dev/null +++ b/packages/hr/src/offer.object.ts @@ -0,0 +1,245 @@ + +const Offer = { + name: 'offer', + label: 'Offer', + labelPlural: 'Offers', + icon: 'file-contract', + description: '录用通知和Offer管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + offer_number: { + type: 'text', + label: 'Offer编号', + unique: true, + maxLength: 40 + }, + candidate_id: { + type: 'lookup', + label: '候选人', + reference: 'candidate', + required: true + }, + application_id: { + type: 'lookup', + label: '申请', + reference: 'application', + required: true + }, + recruitment_id: { + type: 'lookup', + label: '招聘需求', + reference: 'recruitment', + required: true + }, + position_id: { + type: 'lookup', + label: '职位', + reference: 'position', + required: true + }, + department_id: { + type: 'lookup', + label: '部门', + reference: 'department', + required: true + }, + hiring_manager_id: { + type: 'lookup', + label: '招聘负责人', + reference: 'employee', + required: true + }, + offer_date: { + type: 'date', + label: 'Offer日期', + required: true, + defaultValue: '$today' + }, + expiry_date: { + type: 'date', + label: '截止日期', + description: 'Offer有效期截止日期' + }, + start_date: { + type: 'date', + label: '期望入职日期', + required: true + }, + base_salary: { + type: 'currency', + label: '基本工资', + precision: 2, + required: true + }, + bonus: { + type: 'currency', + label: '奖金', + precision: 2 + }, + equity: { + type: 'text', + label: '股权/期权', + maxLength: 255, + description: '股权或期权描述' + }, + benefits: { + type: 'textarea', + label: '福利待遇', + rows: 4, + description: '医疗保险、带薪休假等福利' + }, + employment_type: { + type: 'select', + label: '雇佣类型', + defaultValue: 'Full-time', + options: [ + { label: '全职', value: 'Full-time' }, + { label: '兼职', value: 'Part-time' }, + { label: '合同', value: 'Contract' }, + { label: '实习', value: 'Intern' } + ] + }, + probation_period: { + type: 'select', + label: '试用期', + options: [ + { label: '无试用期', value: 'None' }, + { label: '1个月', value: '1 Month' }, + { label: '2个月', value: '2 Months' }, + { label: '3个月', value: '3 Months' }, + { label: '6个月', value: '6 Months' } + ] + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Draft', + options: [ + { label: '草稿', value: 'Draft' }, + { label: '审批中', value: 'Pending Approval' }, + { label: '已发送', value: 'Extended' }, + { label: '已接受', value: 'Accepted' }, + { label: '已拒绝', value: 'Rejected' }, + { label: '已撤回', value: 'Withdrawn' }, + { label: '已过期', value: 'Expired' } + ] + }, + response_date: { + type: 'date', + label: '候选人回复日期' + }, + rejection_reason: { + type: 'textarea', + label: '拒绝原因', + rows: 3, + description: '如果候选人拒绝,记录原因' + }, + offer_letter_url: { + type: 'url', + label: 'Offer Letter链接' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'Onboarding', + type: 'hasOne', + object: 'Onboarding', + foreignKey: 'offer_id', + label: '入职流程' + } + ], + listViews: [ + { + name: 'All', + label: '所有Offer', + filters: [], + columns: ['offer_number', 'candidate_id', 'position_id', 'offer_date', 'status', 'base_salary'] + }, + { + name: 'Extended', + label: '已发送', + filters: [['status', '=', 'Extended']], + columns: ['candidate_id', 'position_id', 'department_id', 'offer_date', 'expiry_date', 'base_salary'] + }, + { + name: 'Accepted', + label: '已接受', + filters: [['status', '=', 'Accepted']], + columns: ['candidate_id', 'position_id', 'start_date', 'base_salary', 'response_date'] + }, + { + name: 'PendingResponse', + label: '待回复', + filters: [ + ['status', '=', 'Extended'], + ['expiry_date', '>=', '$today'] + ], + columns: ['candidate_id', 'position_id', 'offer_date', 'expiry_date', 'base_salary'], + sort: [['expiry_date', 'asc']] + } + ], + validationRules: [ + { + name: 'ValidExpiryDate', + errorMessage: '截止日期必须在Offer日期之后', + formula: 'AND(NOT(ISBLANK(expiry_date)), expiry_date < offer_date)' + }, + { + name: 'ValidStartDate', + errorMessage: '入职日期必须在Offer日期之后', + formula: 'start_date < offer_date' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['offer_number', 'candidate_id', 'application_id', 'recruitment_id'] + }, + { + label: '职位信息', + columns: 2, + fields: ['position_id', 'department_id', 'hiring_manager_id', 'employment_type'] + }, + { + label: '时间信息', + columns: 2, + fields: ['offer_date', 'expiry_date', 'start_date', 'response_date'] + }, + { + label: '薪资与福利', + columns: 2, + fields: ['base_salary', 'bonus', 'equity', 'probation_period'] + }, + { + label: '状态', + columns: 2, + fields: ['status', 'offer_letter_url'] + }, + { + label: '福利待遇', + columns: 1, + fields: ['benefits'] + }, + { + label: '其他信息', + columns: 1, + fields: ['rejection_reason', 'notes'] + } + ] + } +}; + +export default Offer; diff --git a/packages/hr/src/onboarding.object.ts b/packages/hr/src/onboarding.object.ts new file mode 100644 index 000000000..39a076bc3 --- /dev/null +++ b/packages/hr/src/onboarding.object.ts @@ -0,0 +1,193 @@ + +const Onboarding = { + name: 'onboarding', + label: '入职流程', + labelPlural: '入职流程', + icon: 'user-graduate', + description: '新员工入职和培训流程管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '入职流程名称', + required: true, + searchable: true, + maxLength: 255 + }, + employee_id: { + type: 'lookup', + label: '员工', + reference: 'employee', + required: true + }, + offer_id: { + type: 'lookup', + label: '关联Offer', + reference: 'offer' + }, + start_date: { + type: 'date', + label: '入职日期', + required: true + }, + buddy_id: { + type: 'lookup', + label: '入职导师', + reference: 'employee', + description: '帮助新员工熟悉环境的同事' + }, + manager_id: { + type: 'lookup', + label: '直属经理', + reference: 'employee', + required: true + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Not Started', + options: [ + { label: '未开始', value: 'Not Started' }, + { label: '进行中', value: 'In Progress' }, + { label: '已完成', value: 'Completed' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + completion_percentage: { + type: 'percent', + label: '完成进度', + readonly: true, + description: '入职任务完成百分比' + }, + paperwork_completed: { + type: 'checkbox', + label: '文件签署完成', + defaultValue: false, + description: '劳动合同、保密协议等' + }, + it_setup_completed: { + type: 'checkbox', + label: 'IT设备配置完成', + defaultValue: false, + description: '电脑、邮箱、系统权限等' + }, + workspace_setup_completed: { + type: 'checkbox', + label: '工位准备完成', + defaultValue: false + }, + training_completed: { + type: 'checkbox', + label: '基础培训完成', + defaultValue: false, + description: '公司文化、规章制度等培训' + }, + system_access_granted: { + type: 'checkbox', + label: '系统访问权限已开通', + defaultValue: false + }, + first_day_checklist: { + type: 'textarea', + label: '首日任务清单', + rows: 6, + description: '第一天需要完成的任务' + }, + first_week_goals: { + type: 'textarea', + label: '第一周目标', + rows: 4 + }, + first_month_goals: { + type: 'textarea', + label: '第一个月目标', + rows: 4 + }, + probation_end_date: { + type: 'date', + label: '试用期结束日期' + }, + feedback: { + type: 'textarea', + label: '入职反馈', + rows: 5, + description: '新员工的入职体验反馈' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + listViews: [ + { + name: 'All', + label: '所有入职流程', + filters: [], + columns: ['title', 'employee_id', 'start_date', 'status', 'completion_percentage'] + }, + { + name: 'InProgress', + label: '进行中', + filters: [['status', '=', 'In Progress']], + columns: ['employee_id', 'start_date', 'manager_id', 'buddy_id', 'completion_percentage'], + sort: [['start_date', 'desc']] + }, + { + name: 'Upcoming', + label: '即将入职', + filters: [ + ['status', '=', 'Not Started'], + ['start_date', '>=', '$today'] + ], + columns: ['employee_id', 'start_date', 'manager_id', 'buddy_id'], + sort: [['start_date', 'asc']] + }, + { + name: 'Recent', + label: '最近入职', + filters: [ + ['start_date', 'last_30_days'] + ], + columns: ['employee_id', 'start_date', 'status', 'completion_percentage', 'manager_id'], + sort: [['start_date', 'desc']] + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'employee_id', 'offer_id', 'start_date', 'manager_id', 'buddy_id'] + }, + { + label: '状态', + columns: 2, + fields: ['status', 'completion_percentage', 'probation_end_date'] + }, + { + label: '任务完成情况', + columns: 2, + fields: ['paperwork_completed', 'it_setup_completed', 'workspace_setup_completed', 'training_completed', 'system_access_granted'] + }, + { + label: '目标设定', + columns: 1, + fields: ['first_day_checklist', 'first_week_goals', 'first_month_goals'] + }, + { + label: '反馈与备注', + columns: 1, + fields: ['feedback', 'notes'] + } + ] + } +}; + +export default Onboarding; diff --git a/packages/hr/src/payroll.object.ts b/packages/hr/src/payroll.object.ts new file mode 100644 index 000000000..94349b31d --- /dev/null +++ b/packages/hr/src/payroll.object.ts @@ -0,0 +1,251 @@ + +const Payroll = { + name: 'payroll', + label: '工资', + labelPlural: '工资记录', + icon: 'money-bill-wave', + description: '员工薪资和工资单管理', + enable: { + searchable: true, + trackHistory: true, + activities: false, + feeds: false, + files: true + }, + fields: { + payroll_number: { + type: 'text', + label: '工资单号', + unique: true, + maxLength: 40 + }, + employee_id: { + type: 'lookup', + label: '员工', + reference: 'employee', + required: true + }, + pay_period_start: { + type: 'date', + label: '工资期开始', + required: true + }, + pay_period_end: { + type: 'date', + label: '工资期结束', + required: true + }, + pay_date: { + type: 'date', + label: '发薪日期', + required: true + }, + base_salary: { + type: 'currency', + label: '基本工资', + precision: 2, + required: true + }, + overtime_pay: { + type: 'currency', + label: '加班工资', + precision: 2 + }, + bonus: { + type: 'currency', + label: '奖金', + precision: 2 + }, + commission: { + type: 'currency', + label: '佣金', + precision: 2 + }, + allowances: { + type: 'currency', + label: '津贴', + precision: 2, + description: '交通、餐饮等补贴' + }, + gross_pay: { + type: 'currency', + label: '应发工资', + precision: 2, + readonly: true, + description: '税前总收入(自动计算)' + }, + tax: { + type: 'currency', + label: '个人所得税', + precision: 2 + }, + social_security: { + type: 'currency', + label: '社保', + precision: 2 + }, + housing_fund: { + type: 'currency', + label: '公积金', + precision: 2 + }, + other_deductions: { + type: 'currency', + label: '其他扣款', + precision: 2 + }, + total_deductions: { + type: 'currency', + label: '扣款合计', + precision: 2, + readonly: true, + description: '总扣款金额(自动计算)' + }, + net_pay: { + type: 'currency', + label: '实发工资', + precision: 2, + readonly: true, + description: '税后实际到手金额(自动计算)' + }, + worked_days: { + type: 'number', + label: '出勤天数', + precision: 1 + }, + overtime_hours: { + type: 'number', + label: '加班小时数', + precision: 1 + }, + leave_days: { + type: 'number', + label: '请假天数', + precision: 1 + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Draft', + options: [ + { label: '草稿', value: 'Draft' }, + { label: '待审核', value: 'Pending Approval' }, + { label: '已批准', value: 'Approved' }, + { label: '已支付', value: 'Paid' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + payment_method: { + type: 'select', + label: '支付方式', + defaultValue: 'Bank Transfer', + options: [ + { label: '银行转账', value: 'Bank Transfer' }, + { label: '支票', value: 'Check' }, + { label: '现金', value: 'Cash' }, + { label: '其他', value: 'Other' } + ] + }, + bank_account: { + type: 'text', + label: '银行账号', + maxLength: 100 + }, + payslip_url: { + type: 'url', + label: '工资条链接' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 3 + } + }, + listViews: [ + { + name: 'All', + label: '所有工资', + filters: [], + columns: ['payroll_number', 'employee_id', 'pay_period_end', 'gross_pay', 'net_pay', 'status'] + }, + { + name: 'CurrentMonth', + label: '本月工资', + filters: [['pay_period_end', 'this_month']], + columns: ['employee_id', 'pay_date', 'gross_pay', 'total_deductions', 'net_pay', 'status'] + }, + { + name: 'PendingApproval', + label: '待审核', + filters: [['status', '=', 'Pending Approval']], + columns: ['employee_id', 'pay_period_start', 'pay_period_end', 'gross_pay', 'net_pay'] + }, + { + name: 'Approved', + label: '已批准', + filters: [['status', '=', 'Approved']], + columns: ['employee_id', 'pay_date', 'gross_pay', 'net_pay', 'payment_method'] + }, + { + name: 'Paid', + label: '已支付', + filters: [['status', '=', 'Paid']], + columns: ['employee_id', 'pay_date', 'gross_pay', 'net_pay', 'payment_method'] + }, + { + name: 'MyPayroll', + label: '我的工资', + filters: [['employee_id', '=', '$currentUser']], + columns: ['pay_period_end', 'pay_date', 'gross_pay', 'total_deductions', 'net_pay', 'status'], + sort: [['pay_period_end', 'desc']] + } + ], + validationRules: [ + { + name: 'ValidPayPeriod', + errorMessage: '工资期结束日期不能早于开始日期', + formula: 'pay_period_end < pay_period_start' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['payroll_number', 'employee_id', 'pay_period_start', 'pay_period_end', 'pay_date'] + }, + { + label: '收入项', + columns: 2, + fields: ['base_salary', 'overtime_pay', 'bonus', 'commission', 'allowances', 'gross_pay'] + }, + { + label: '扣款项', + columns: 2, + fields: ['tax', 'social_security', 'housing_fund', 'other_deductions', 'total_deductions'] + }, + { + label: '实发工资', + columns: 2, + fields: ['net_pay'] + }, + { + label: '工时统计', + columns: 2, + fields: ['worked_days', 'overtime_hours', 'leave_days'] + }, + { + label: '支付信息', + columns: 2, + fields: ['status', 'payment_method', 'bank_account', 'payslip_url'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Payroll; diff --git a/packages/hr/src/performance_review.object.ts b/packages/hr/src/performance_review.object.ts new file mode 100644 index 000000000..57ee804bd --- /dev/null +++ b/packages/hr/src/performance_review.object.ts @@ -0,0 +1,264 @@ + +const PerformanceReview = { + name: 'performance_review', + label: '绩效评估', + labelPlural: '绩效评估', + icon: 'chart-line', + description: '员工绩效评估和考核管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + review_name: { + type: 'text', + label: '评估名称', + required: true, + searchable: true, + maxLength: 255 + }, + employee_id: { + type: 'lookup', + label: '被评估员工', + reference: 'employee', + required: true + }, + reviewer_id: { + type: 'lookup', + label: '评估人', + reference: 'employee', + required: true, + description: '通常是直属经理' + }, + review_period: { + type: 'select', + label: '评估周期', + defaultValue: 'Annual', + options: [ + { label: '季度', value: 'Quarterly' }, + { label: '半年度', value: 'Semi-Annual' }, + { label: '年度', value: 'Annual' }, + { label: '试用期', value: 'Probation' }, + { label: '临时', value: 'Ad-hoc' } + ] + }, + review_type: { + type: 'select', + label: '评估类型', + options: [ + { label: '自评', value: 'Self Review' }, + { label: '经理评估', value: 'Manager Review' }, + { label: '360度评估', value: '360 Review' }, + { label: '试用期评估', value: 'Probation Review' } + ] + }, + start_date: { + type: 'date', + label: '评估开始日期', + required: true + }, + end_date: { + type: 'date', + label: '评估结束日期', + required: true + }, + due_date: { + type: 'date', + label: '提交截止日期' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Not Started', + options: [ + { label: '未开始', value: 'Not Started' }, + { label: '进行中', value: 'In Progress' }, + { label: '待审核', value: 'Pending Review' }, + { label: '已完成', value: 'Completed' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + overall_rating: { + type: 'select', + label: '总体评级', + options: [ + { label: '优秀', value: 'Outstanding' }, + { label: '超出期望', value: 'Exceeds Expectations' }, + { label: '达到期望', value: 'Meets Expectations' }, + { label: '需要改进', value: 'Needs Improvement' }, + { label: '不满意', value: 'Unsatisfactory' } + ] + }, + overall_score: { + type: 'number', + label: '总体评分', + precision: 2, + min: 0, + max: 100, + description: '综合评分(0-100)' + }, + goals_rating: { + type: 'rating', + label: '目标完成度', + max: 5, + description: 'OKR/目标达成评分' + }, + competency_rating: { + type: 'rating', + label: '能力评估', + max: 5 + }, + values_rating: { + type: 'rating', + label: '价值观契合度', + max: 5 + }, + achievements: { + type: 'textarea', + label: '主要成就', + rows: 6, + description: '评估期内的关键成就和贡献' + }, + strengths: { + type: 'textarea', + label: '优势', + rows: 4 + }, + areas_for_improvement: { + type: 'textarea', + label: '改进方向', + rows: 4 + }, + development_plan: { + type: 'textarea', + label: '发展计划', + rows: 5, + description: '下一阶段的发展目标和行动计划' + }, + employee_comments: { + type: 'textarea', + label: '员工自评/反馈', + rows: 5 + }, + manager_comments: { + type: 'textarea', + label: '经理评语', + rows: 5 + }, + promotion_recommendation: { + type: 'checkbox', + label: '推荐晋升', + defaultValue: false + }, + salary_increase_recommendation: { + type: 'percent', + label: '建议加薪比例' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'Goals', + type: 'hasMany', + object: 'Goal', + foreignKey: 'performance_review_id', + label: '关联目标' + } + ], + listViews: [ + { + name: 'All', + label: '所有评估', + filters: [], + columns: ['review_name', 'employee_id', 'reviewer_id', 'review_period', 'status', 'overall_rating'] + }, + { + name: 'InProgress', + label: '进行中', + filters: [['status', '=', 'In Progress']], + columns: ['employee_id', 'reviewer_id', 'review_period', 'due_date', 'overall_score'] + }, + { + name: 'Completed', + label: '已完成', + filters: [['status', '=', 'Completed']], + columns: ['employee_id', 'reviewer_id', 'review_period', 'overall_rating', 'overall_score', 'end_date'] + }, + { + name: 'MyReviews', + label: '我负责的评估', + filters: [['reviewer_id', '=', '$currentUser']], + columns: ['employee_id', 'review_period', 'status', 'due_date', 'overall_rating'] + }, + { + name: 'Outstanding', + label: '优秀员工', + filters: [ + ['overall_rating', '=', 'Outstanding'], + ['status', '=', 'Completed'] + ], + columns: ['employee_id', 'reviewer_id', 'review_period', 'overall_score', 'end_date'], + sort: [['overall_score', 'desc']] + } + ], + validationRules: [ + { + name: 'ValidReviewPeriod', + errorMessage: '结束日期必须在开始日期之后', + formula: 'end_date < start_date' + }, + { + name: 'ScoreRange', + errorMessage: '总体评分必须在0-100之间', + formula: 'OR(overall_score < 0, overall_score > 100)' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['review_name', 'employee_id', 'reviewer_id', 'review_period', 'review_type'] + }, + { + label: '时间信息', + columns: 2, + fields: ['start_date', 'end_date', 'due_date', 'status'] + }, + { + label: '评估评级', + columns: 2, + fields: ['overall_rating', 'overall_score', 'goals_rating', 'competency_rating', 'values_rating'] + }, + { + label: '评估内容', + columns: 1, + fields: ['achievements', 'strengths', 'areas_for_improvement', 'development_plan'] + }, + { + label: '反馈意见', + columns: 1, + fields: ['employee_comments', 'manager_comments'] + }, + { + label: '建议', + columns: 2, + fields: ['promotion_recommendation', 'salary_increase_recommendation'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default PerformanceReview; diff --git a/packages/hr/src/plugin.ts b/packages/hr/src/plugin.ts new file mode 100644 index 000000000..816693ed2 --- /dev/null +++ b/packages/hr/src/plugin.ts @@ -0,0 +1,136 @@ +/** + * @hotcrm/hr - HR/HCM Plugin Definition + * + * This plugin provides comprehensive Human Capital Management functionality including: + * - Organizational Structure (Departments, Positions, Employees) + * - Talent Acquisition (Recruitment, Candidates, Applications, Interviews, Offers, Onboarding) + * - Performance Management (Performance Reviews, Goals/OKRs) + * - Learning & Development (Training, Certifications) + * - Time & Attendance (Time Off, Attendance) + * - Compensation (Payroll) + */ + +// Import organizational objects +import Department from './department.object'; +import Position from './position.object'; +import Employee from './employee.object'; + +// Import recruitment objects +import Recruitment from './recruitment.object'; +import Candidate from './candidate.object'; +import Application from './application.object'; +import Interview from './interview.object'; +import Offer from './offer.object'; +import Onboarding from './onboarding.object'; + +// Import performance management objects +import PerformanceReview from './performance_review.object'; +import Goal from './goal.object'; + +// Import learning & development objects +import Training from './training.object'; +import Certification from './certification.object'; + +// Import time & attendance objects +import TimeOff from './time_off.object'; +import Attendance from './attendance.object'; + +// Import payroll object +import Payroll from './payroll.object'; + +/** + * HR Plugin Definition + * + * Exports all HR/HCM-related business objects to be registered with the ObjectStack runtime + */ +export const HRPlugin = { + name: 'hr', + label: 'Human Capital Management', + version: '1.0.0', + description: 'Complete HR/HCM solution - Employee lifecycle management from recruitment to retirement', + + // Plugin dependencies + dependencies: [], + + // Plugin initialization + init: async () => { + // No initialization required for this plugin + }, + + // Business objects provided by this plugin + objects: { + // Organizational structure + department: Department, + position: Position, + employee: Employee, + + // Talent acquisition + recruitment: Recruitment, + candidate: Candidate, + application: Application, + interview: Interview, + offer: Offer, + onboarding: Onboarding, + + // Performance management + performance_review: PerformanceReview, + goal: Goal, + + // Learning & development + training: Training, + certification: Certification, + + // Time & attendance + time_off: TimeOff, + attendance: Attendance, + + // Compensation + payroll: Payroll, + }, + + // Navigation structure for this plugin + navigation: [ + { + type: 'group', + label: 'Organization', + children: [ + { type: 'object', object: 'employee' }, + { type: 'object', object: 'department' }, + { type: 'object', object: 'position' }, + ] + }, + { + type: 'group', + label: 'Talent Acquisition', + children: [ + { type: 'object', object: 'recruitment' }, + { type: 'object', object: 'candidate' }, + { type: 'object', object: 'application' }, + { type: 'object', object: 'interview' }, + { type: 'object', object: 'offer' }, + { type: 'object', object: 'onboarding' }, + ] + }, + { + type: 'group', + label: 'Performance & Development', + children: [ + { type: 'object', object: 'performance_review' }, + { type: 'object', object: 'goal' }, + { type: 'object', object: 'training' }, + { type: 'object', object: 'certification' }, + ] + }, + { + type: 'group', + label: 'Time & Payroll', + children: [ + { type: 'object', object: 'time_off' }, + { type: 'object', object: 'attendance' }, + { type: 'object', object: 'payroll' }, + ] + } + ] +}; + +export default HRPlugin; diff --git a/packages/hr/src/position.object.ts b/packages/hr/src/position.object.ts new file mode 100644 index 000000000..e98acc9ec --- /dev/null +++ b/packages/hr/src/position.object.ts @@ -0,0 +1,196 @@ + +const Position = { + name: 'position', + label: '职位', + labelPlural: '职位', + icon: 'briefcase', + description: '职位和岗位定义管理', + enable: { + searchable: true, + trackHistory: true, + activities: false, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '职位名称', + required: true, + searchable: true, + maxLength: 255 + }, + code: { + type: 'text', + label: '职位编码', + unique: true, + maxLength: 40 + }, + department_id: { + type: 'lookup', + label: '所属部门', + reference: 'department', + required: true + }, + level: { + type: 'select', + label: '职级', + options: [ + { label: 'C级高管', value: 'C-Level' }, + { label: 'VP级', value: 'VP' }, + { label: '总监', value: 'Director' }, + { label: '经理', value: 'Manager' }, + { label: '主管', value: 'Supervisor' }, + { label: '专员', value: 'Staff' }, + { label: '助理', value: 'Assistant' } + ] + }, + job_family: { + type: 'select', + label: '职位族', + options: [ + { label: '管理', value: 'Management' }, + { label: '技术', value: 'Technical' }, + { label: '销售', value: 'Sales' }, + { label: '营销', value: 'Marketing' }, + { label: '财务', value: 'Finance' }, + { label: '人力资源', value: 'HR' }, + { label: '运营', value: 'Operations' }, + { label: '客户服务', value: 'Customer Service' }, + { label: '其他', value: 'Other' } + ] + }, + reports_to_id: { + type: 'lookup', + label: '汇报给', + reference: 'position', + description: '直接汇报的上级职位' + }, + employment_type: { + type: 'select', + label: '雇佣类型', + defaultValue: 'Full-time', + options: [ + { label: '全职', value: 'Full-time' }, + { label: '兼职', value: 'Part-time' }, + { label: '合同', value: 'Contract' }, + { label: '实习', value: 'Intern' }, + { label: '临时', value: 'Temporary' } + ] + }, + headcount: { + type: 'number', + label: '计划编制', + defaultValue: 1, + description: '该职位的计划人数' + }, + current_headcount: { + type: 'number', + label: '当前人数', + readonly: true, + description: '当前在职人数(自动计算)' + }, + min_salary: { + type: 'currency', + label: '最低薪资', + precision: 2 + }, + max_salary: { + type: 'currency', + label: '最高薪资', + precision: 2 + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Active', + options: [ + { label: '活跃', value: 'Active' }, + { label: '招聘中', value: 'Hiring' }, + { label: '暂停', value: 'On Hold' }, + { label: '已关闭', value: 'Closed' } + ] + }, + description: { + type: 'textarea', + label: '职位描述', + rows: 5, + description: '职位职责和要求' + }, + requirements: { + type: 'textarea', + label: '任职要求', + rows: 5, + description: '学历、技能、经验等要求' + } + }, + relationships: [ + { + name: 'Employees', + type: 'hasMany', + object: 'Employee', + foreignKey: 'position_id', + label: '在职员工' + }, + { + name: 'Recruitments', + type: 'hasMany', + object: 'Recruitment', + foreignKey: 'position_id', + label: '招聘需求' + } + ], + listViews: [ + { + name: 'All', + label: '所有职位', + filters: [], + columns: ['title', 'code', 'department_id', 'level', 'current_headcount', 'headcount', 'status'] + }, + { + name: 'Active', + label: '活跃职位', + filters: [['status', '=', 'Active']], + columns: ['title', 'department_id', 'level', 'job_family', 'current_headcount', 'headcount'] + }, + { + name: 'Hiring', + label: '招聘中', + filters: [['status', '=', 'Hiring']], + columns: ['title', 'department_id', 'level', 'employment_type', 'min_salary', 'max_salary'] + } + ], + validationRules: [ + { + name: 'ValidSalaryRange', + errorMessage: '最高薪资必须大于最低薪资', + formula: 'AND(NOT(ISBLANK(min_salary)), NOT(ISBLANK(max_salary)), max_salary < min_salary)' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'code', 'department_id', 'level', 'job_family', 'reports_to_id'] + }, + { + label: '雇佣信息', + columns: 2, + fields: ['employment_type', 'status', 'headcount', 'current_headcount'] + }, + { + label: '薪资范围', + columns: 2, + fields: ['min_salary', 'max_salary'] + }, + { + label: '职位描述', + columns: 1, + fields: ['description', 'requirements'] + } + ] + } +}; + +export default Position; diff --git a/packages/hr/src/recruitment.object.ts b/packages/hr/src/recruitment.object.ts new file mode 100644 index 000000000..7741b5ea9 --- /dev/null +++ b/packages/hr/src/recruitment.object.ts @@ -0,0 +1,203 @@ + +const Recruitment = { + name: 'recruitment', + label: '招聘需求', + labelPlural: '招聘需求', + icon: 'user-plus', + description: '职位招聘需求和招聘计划管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '招聘标题', + required: true, + searchable: true, + maxLength: 255 + }, + requisition_number: { + type: 'text', + label: '需求编号', + unique: true, + maxLength: 40 + }, + position_id: { + type: 'lookup', + label: '招聘职位', + reference: 'position', + required: true + }, + department_id: { + type: 'lookup', + label: '所属部门', + reference: 'department', + required: true + }, + hiring_manager_id: { + type: 'lookup', + label: '招聘负责人', + reference: 'employee', + required: true, + description: '负责此次招聘的经理' + }, + headcount: { + type: 'number', + label: '招聘人数', + defaultValue: 1, + required: true + }, + priority: { + type: 'select', + label: '优先级', + defaultValue: 'Medium', + options: [ + { label: '紧急', value: 'Urgent' }, + { label: '高', value: 'High' }, + { label: '中', value: 'Medium' }, + { label: '低', value: 'Low' } + ] + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Open', + options: [ + { label: '草稿', value: 'Draft' }, + { label: '审批中', value: 'Pending Approval' }, + { label: '开放', value: 'Open' }, + { label: '进行中', value: 'In Progress' }, + { label: '暂停', value: 'On Hold' }, + { label: '已完成', value: 'Filled' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + target_start_date: { + type: 'date', + label: '期望到岗日期' + }, + posted_date: { + type: 'date', + label: '发布日期' + }, + close_date: { + type: 'date', + label: '关闭日期' + }, + job_description: { + type: 'textarea', + label: '职位描述', + rows: 8, + description: '详细的职位职责和要求' + }, + requirements: { + type: 'textarea', + label: '任职要求', + rows: 5, + description: '学历、经验、技能等要求' + }, + salary_range_min: { + type: 'currency', + label: '薪资范围(最低)', + precision: 2 + }, + salary_range_max: { + type: 'currency', + label: '薪资范围(最高)', + precision: 2 + }, + notes: { + type: 'textarea', + label: '备注', + rows: 4 + } + }, + relationships: [ + { + name: 'Applications', + type: 'hasMany', + object: 'Application', + foreignKey: 'recruitment_id', + label: '申请' + }, + { + name: 'Interviews', + type: 'hasMany', + object: 'Interview', + foreignKey: 'recruitment_id', + label: '面试' + } + ], + listViews: [ + { + name: 'All', + label: '所有招聘', + filters: [], + columns: ['requisition_number', 'title', 'position_id', 'department_id', 'headcount', 'status', 'priority'] + }, + { + name: 'Open', + label: '开放招聘', + filters: [['status', '=', 'Open']], + columns: ['title', 'position_id', 'department_id', 'hiring_manager_id', 'headcount', 'priority', 'posted_date'] + }, + { + name: 'InProgress', + label: '进行中', + filters: [['status', '=', 'In Progress']], + columns: ['title', 'position_id', 'department_id', 'hiring_manager_id', 'headcount', 'priority'] + }, + { + name: 'Urgent', + label: '紧急招聘', + filters: [ + ['priority', 'in', ['Urgent', 'High']], + ['status', 'in', ['Open', 'In Progress']] + ], + columns: ['title', 'position_id', 'department_id', 'hiring_manager_id', 'priority', 'target_start_date'], + sort: [['priority', 'asc']] + } + ], + validationRules: [ + { + name: 'ValidSalaryRange', + errorMessage: '最高薪资必须大于最低薪资', + formula: 'AND(NOT(ISBLANK(salary_range_min)), NOT(ISBLANK(salary_range_max)), salary_range_max <= salary_range_min)' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'requisition_number', 'position_id', 'department_id', 'hiring_manager_id', 'headcount'] + }, + { + label: '状态与优先级', + columns: 2, + fields: ['status', 'priority', 'target_start_date', 'posted_date', 'close_date'] + }, + { + label: '薪资范围', + columns: 2, + fields: ['salary_range_min', 'salary_range_max'] + }, + { + label: '职位信息', + columns: 1, + fields: ['job_description', 'requirements'] + }, + { + label: '备注', + columns: 1, + fields: ['notes'] + } + ] + } +}; + +export default Recruitment; diff --git a/packages/hr/src/time_off.object.ts b/packages/hr/src/time_off.object.ts new file mode 100644 index 000000000..efdae4815 --- /dev/null +++ b/packages/hr/src/time_off.object.ts @@ -0,0 +1,236 @@ + +const TimeOff = { + name: 'time_off', + label: '请假', + labelPlural: '请假记录', + icon: 'calendar-times', + description: '员工请假和休假管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + request_number: { + type: 'text', + label: '请假单号', + unique: true, + maxLength: 40 + }, + employee_id: { + type: 'lookup', + label: '员工', + reference: 'employee', + required: true + }, + leave_type: { + type: 'select', + label: '请假类型', + required: true, + options: [ + { label: '年假', value: 'Annual Leave' }, + { label: '病假', value: 'Sick Leave' }, + { label: '事假', value: 'Personal Leave' }, + { label: '婚假', value: 'Marriage Leave' }, + { label: '产假', value: 'Maternity Leave' }, + { label: '陪产假', value: 'Paternity Leave' }, + { label: '丧假', value: 'Bereavement Leave' }, + { label: '调休', value: 'Compensatory Leave' }, + { label: '无薪假', value: 'Unpaid Leave' }, + { label: '其他', value: 'Other' } + ] + }, + start_date: { + type: 'date', + label: '开始日期', + required: true + }, + end_date: { + type: 'date', + label: '结束日期', + required: true + }, + start_time: { + type: 'select', + label: '开始时段', + defaultValue: 'Full Day', + options: [ + { label: '全天', value: 'Full Day' }, + { label: '上午', value: 'Morning' }, + { label: '下午', value: 'Afternoon' } + ] + }, + end_time: { + type: 'select', + label: '结束时段', + defaultValue: 'Full Day', + options: [ + { label: '全天', value: 'Full Day' }, + { label: '上午', value: 'Morning' }, + { label: '下午', value: 'Afternoon' } + ] + }, + total_days: { + type: 'number', + label: '请假天数', + precision: 1, + readonly: true, + description: '自动计算的请假天数' + }, + request_date: { + type: 'date', + label: '申请日期', + defaultValue: '$today', + required: true + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Pending', + options: [ + { label: '待审批', value: 'Pending' }, + { label: '已批准', value: 'Approved' }, + { label: '已拒绝', value: 'Rejected' }, + { label: '已取消', value: 'Cancelled' } + ] + }, + approver_id: { + type: 'lookup', + label: '审批人', + reference: 'employee', + description: '负责审批的经理' + }, + approval_date: { + type: 'date', + label: '审批日期' + }, + reason: { + type: 'textarea', + label: '请假原因', + rows: 4, + required: true + }, + rejection_reason: { + type: 'textarea', + label: '拒绝原因', + rows: 3, + description: '如果被拒绝,记录原因' + }, + contact_info: { + type: 'text', + label: '请假期间联系方式', + maxLength: 255 + }, + backup_person_id: { + type: 'lookup', + label: '工作交接人', + reference: 'employee', + description: '临时负责工作的同事' + }, + is_paid: { + type: 'checkbox', + label: '是否带薪', + defaultValue: true + }, + attachment_url: { + type: 'url', + label: '附件链接', + description: '如病假证明等' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 3 + } + }, + listViews: [ + { + name: 'All', + label: '所有请假', + filters: [], + columns: ['request_number', 'employee_id', 'leave_type', 'start_date', 'total_days', 'status'] + }, + { + name: 'Pending', + label: '待审批', + filters: [['status', '=', 'Pending']], + columns: ['employee_id', 'leave_type', 'start_date', 'end_date', 'total_days', 'request_date', 'approver_id'] + }, + { + name: 'Approved', + label: '已批准', + filters: [['status', '=', 'Approved']], + columns: ['employee_id', 'leave_type', 'start_date', 'end_date', 'total_days', 'approval_date'] + }, + { + name: 'MyRequests', + label: '我的请假', + filters: [['employee_id', '=', '$currentUser']], + columns: ['leave_type', 'start_date', 'end_date', 'total_days', 'status', 'approval_date'] + }, + { + name: 'MyApprovals', + label: '待我审批', + filters: [ + ['approver_id', '=', '$currentUser'], + ['status', '=', 'Pending'] + ], + columns: ['employee_id', 'leave_type', 'start_date', 'total_days', 'request_date', 'reason'] + }, + { + name: 'Upcoming', + label: '即将开始', + filters: [ + ['status', '=', 'Approved'], + ['start_date', '>=', '$today'] + ], + columns: ['employee_id', 'leave_type', 'start_date', 'end_date', 'total_days', 'backup_person_id'], + sort: [['start_date', 'asc']] + } + ], + validationRules: [ + { + name: 'ValidDateRange', + errorMessage: '结束日期不能早于开始日期', + formula: 'end_date < start_date' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['request_number', 'employee_id', 'leave_type', 'is_paid'] + }, + { + label: '请假时间', + columns: 2, + fields: ['start_date', 'start_time', 'end_date', 'end_time', 'total_days'] + }, + { + label: '审批信息', + columns: 2, + fields: ['status', 'request_date', 'approver_id', 'approval_date'] + }, + { + label: '工作安排', + columns: 2, + fields: ['backup_person_id', 'contact_info'] + }, + { + label: '详细说明', + columns: 1, + fields: ['reason', 'rejection_reason'] + }, + { + label: '附件与备注', + columns: 1, + fields: ['attachment_url', 'notes'] + } + ] + } +}; + +export default TimeOff; diff --git a/packages/hr/src/training.object.ts b/packages/hr/src/training.object.ts new file mode 100644 index 000000000..e7823f9b4 --- /dev/null +++ b/packages/hr/src/training.object.ts @@ -0,0 +1,260 @@ + +const Training = { + name: 'training', + label: '培训', + labelPlural: '培训', + icon: 'graduation-cap', + description: '员工培训和学习发展管理', + enable: { + searchable: true, + trackHistory: true, + activities: true, + feeds: true, + files: true + }, + fields: { + title: { + type: 'text', + label: '培训名称', + required: true, + searchable: true, + maxLength: 255 + }, + training_code: { + type: 'text', + label: '培训编号', + unique: true, + maxLength: 40 + }, + employee_id: { + type: 'lookup', + label: '参训员工', + reference: 'employee', + required: true + }, + training_type: { + type: 'select', + label: '培训类型', + options: [ + { label: '入职培训', value: 'Onboarding' }, + { label: '技能培训', value: 'Skills Training' }, + { label: '领导力培训', value: 'Leadership' }, + { label: '合规培训', value: 'Compliance' }, + { label: '产品培训', value: 'Product' }, + { label: '销售培训', value: 'Sales' }, + { label: '安全培训', value: 'Safety' }, + { label: '其他', value: 'Other' } + ] + }, + category: { + type: 'select', + label: '培训分类', + options: [ + { label: '内部培训', value: 'Internal' }, + { label: '外部培训', value: 'External' }, + { label: '在线课程', value: 'Online' }, + { label: '研讨会', value: 'Workshop' }, + { label: '会议', value: 'Conference' }, + { label: '认证课程', value: 'Certification' } + ] + }, + provider: { + type: 'text', + label: '培训机构/讲师', + maxLength: 255 + }, + start_date: { + type: 'datetime', + label: '开始时间', + required: true + }, + end_date: { + type: 'datetime', + label: '结束时间', + required: true + }, + duration_hours: { + type: 'number', + label: '培训时长(小时)', + precision: 1 + }, + location: { + type: 'text', + label: '培训地点', + maxLength: 255, + description: '线下地点或线上会议链接' + }, + status: { + type: 'select', + label: '状态', + defaultValue: 'Scheduled', + options: [ + { label: '已安排', value: 'Scheduled' }, + { label: '进行中', value: 'In Progress' }, + { label: '已完成', value: 'Completed' }, + { label: '已取消', value: 'Cancelled' }, + { label: '未参加', value: 'No Show' } + ] + }, + attendance_status: { + type: 'select', + label: '出勤状态', + options: [ + { label: '已参加', value: 'Attended' }, + { label: '部分参加', value: 'Partial' }, + { label: '未参加', value: 'Absent' } + ] + }, + completion_percentage: { + type: 'percent', + label: '完成进度' + }, + is_mandatory: { + type: 'checkbox', + label: '是否必修', + defaultValue: false + }, + cost: { + type: 'currency', + label: '培训费用', + precision: 2 + }, + rating: { + type: 'rating', + label: '培训评分', + max: 5, + description: '员工对培训的评分' + }, + exam_score: { + type: 'number', + label: '考试成绩', + precision: 2, + min: 0, + max: 100, + description: '如有考试,记录成绩' + }, + passed: { + type: 'checkbox', + label: '是否通过', + defaultValue: false + }, + certificate_url: { + type: 'url', + label: '证书链接' + }, + description: { + type: 'textarea', + label: '培训描述', + rows: 4 + }, + learning_objectives: { + type: 'textarea', + label: '学习目标', + rows: 3 + }, + feedback: { + type: 'textarea', + label: '培训反馈', + rows: 5, + description: '员工的培训体验和收获' + }, + notes: { + type: 'textarea', + label: '备注', + rows: 3 + } + }, + relationships: [ + { + name: 'Certifications', + type: 'hasMany', + object: 'Certification', + foreignKey: 'training_id', + label: '相关认证' + } + ], + listViews: [ + { + name: 'All', + label: '所有培训', + filters: [], + columns: ['title', 'employee_id', 'training_type', 'start_date', 'status', 'completion_percentage'] + }, + { + name: 'Upcoming', + label: '即将开始', + filters: [ + ['status', '=', 'Scheduled'], + ['start_date', '>=', '$today'] + ], + columns: ['title', 'employee_id', 'training_type', 'start_date', 'location', 'provider'], + sort: [['start_date', 'asc']] + }, + { + name: 'Completed', + label: '已完成', + filters: [['status', '=', 'Completed']], + columns: ['title', 'employee_id', 'training_type', 'end_date', 'passed', 'rating'] + }, + { + name: 'MyTraining', + label: '我的培训', + filters: [['employee_id', '=', '$currentUser']], + columns: ['title', 'training_type', 'start_date', 'status', 'completion_percentage', 'rating'] + }, + { + name: 'Mandatory', + label: '必修培训', + filters: [['is_mandatory', '=', true]], + columns: ['title', 'employee_id', 'training_type', 'start_date', 'status', 'passed'] + } + ], + validationRules: [ + { + name: 'ValidDates', + errorMessage: '结束时间必须在开始时间之后', + formula: 'end_date < start_date' + } + ], + pageLayout: { + sections: [ + { + label: '基本信息', + columns: 2, + fields: ['title', 'training_code', 'employee_id', 'training_type', 'category', 'is_mandatory'] + }, + { + label: '时间与地点', + columns: 2, + fields: ['start_date', 'end_date', 'duration_hours', 'location', 'provider'] + }, + { + label: '状态与进度', + columns: 2, + fields: ['status', 'attendance_status', 'completion_percentage'] + }, + { + label: '成本', + columns: 2, + fields: ['cost'] + }, + { + label: '考核结果', + columns: 2, + fields: ['exam_score', 'passed', 'rating', 'certificate_url'] + }, + { + label: '培训详情', + columns: 1, + fields: ['description', 'learning_objectives'] + }, + { + label: '反馈与备注', + columns: 1, + fields: ['feedback', 'notes'] + } + ] + } +}; + +export default Training; diff --git a/packages/hr/tsconfig.json b/packages/hr/tsconfig.json new file mode 100644 index 000000000..252e79650 --- /dev/null +++ b/packages/hr/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../base.tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "__tests__"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc570a5e9..8bf774f5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,31 @@ importers: specifier: ^8.55.0 version: 8.57.1 + packages/hr: + dependencies: + '@hotcrm/core': + specifier: workspace:* + version: link:../core + '@objectstack/spec': + specifier: ^0.8.1 + version: 0.8.1 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + devDependencies: + '@types/node': + specifier: ^20.10.0 + version: 20.19.30 + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + eslint: + specifier: ^8.55.0 + version: 8.57.1 + packages/marketing: dependencies: '@objectstack/spec':