【薪资】月度工资、工资条
月度工资与工资条模块,由 yudao-module-hrm 后端模块的 salary.monthrecord、salary.slip、portal.salary 包实现,前端实现在 @/views/hrm/salary/month-record、@/views/hrm/salary/slip 和 @/views/hrm/portal/salary 目录。
本文是薪资链路的运行过程:管理端按计薪周期核算月度工资,核对无误后发放工资条并归档;员工端只查看发给本人的工资条。计薪周期、薪资组、工资项和员工薪资档案等前置配置,详见 《【薪资】计薪设置、薪资档案》。
- 月度工资:按计薪周期核算,可同步社保、同步或导入考勤、导入累计个税。
- 工资条:按「模板 → 发放批次 → 员工工资条」三级保存,发放时固化快照。
本文涉及表如下图所示:
# 1. 完整流转
计薪设置只需配置一次,之后每个月都是同一条固定跑法:维护档案与调薪 → 核算 → 校对 → 发工资条 → 创建下月并归档。这一节先把整条链路串起来,后面几章再按表逐段展开。
# 1.1 每月发薪的节奏
(上月末)创建下月工资表
│
▼
未核算(5) ──① 维护薪资档案、等待调薪生效、准备考勤与社保数据
│
│ ②【核算工资】
▼
已核算(11) ──③ 校对、在线编辑、重新核算(可反复)
│
│ ④【发送工资条】,可分多批
▼
员工端可见工资条快照
│
│ ⑤【创建下月工资表】
▼
已归档(10) + 下月「未核算」工资表
④ 和 ⑤ 没有「必须全部发完」的后端校验,但管理后台应先核对无误、发完工资条,再创建下月。创建下月会归档本月,历史工资表仅可查看,页面不再提供补发入口。
# 1.2 分步对照表
| 步骤 | 操作人 | 入口 | 前置条件 | 结果 |
|---|---|---|---|---|
| ① 定薪、调薪 | HR | 薪资档案 → 【定薪】【调薪】【批量调薪】 | 员工已入职 | 生成调薪记录,到达生效日期后写入薪资档案 |
| ② 核算工资 | HR | 月度工资表 → 【核算工资】 | 工资表未归档,员工已加薪资组 | 生成每人工资项快照与应发、应税、个税、实发,状态转为已核算 |
| ③ 校对与修正 | HR | 月度工资表 → 【在线编辑】【核算工资】 | 工资表未归档 | 修改叶子工资项和计薪天数,或整表重新核算 |
| ④ 发送工资条 | HR | 月度工资表 → 【发送工资条】 | 工资表已核算,员工已绑定账号 | 生成发放批次和员工工资条快照,员工端可见 |
| ⑤ 查看工资条 | 员工 | HRM 员工端 → 我的工资条 | 已收到工资条 | 打开后自动标记为已读,管理端同步看到阅读状态 |
| ⑥ 创建下月 | HR | 月度工资表 → 【创建下月工资表】 | 工资表已核算 | 本月归档只读,自动创建下月「未核算」工资表 |
其中步骤 ① 属于薪资档案的日常维护,详见 《【薪资】计薪设置、薪资档案》;本文从步骤 ② 开始展开。
# 1.3 核算时的取数与覆盖顺序
核算(computeMonthRecord)会对每名计薪员工逐个取数,后取到的数据覆盖同 code 的先取值:
薪资档案生效值(无档案 → 继承上月可调整工资项 → 仍无则按 0)
│ 覆盖
▼
考勤数据(同步考勤统计 或 上传考勤 Excel,二选一)
│ 覆盖
▼
社保数据(勾选同步时,按计薪配置定位到上月 / 当月 / 次月的社保月度表)
│ 覆盖
▼
专项附加扣除 Excel、累计个税 Excel
│
▼
按薪资组绑定的计税规则,算出应发、应税、个税、实发
| 数据 | 来源 | 说明 |
|---|---|---|
| 固定工资项 | hrm_salary_employee_info | 取计薪周期内生效的薪资档案;缺失时继承上月的可调整类目工资项 |
| 考勤扣款、出勤天数 | 考勤模块 或 Excel | 同步时读取当月考勤统计;未取到考勤数据时,计薪天数按薪资组的薪资标准计算 |
| 社保、公积金 | 社保模块 | 按 social_security_month_type 定位社保月份,只取正常参保记录 |
| 专项附加扣除、累计个税 | Excel 导入 | 模板可在核算弹窗中下载 |
| 绩效系数 | 绩效模块 | 按计薪月份读取已归档绩效,仅作为参考数据返回,不会自动乘入工资项 |
三个容易踩的点
- 三类 Excel 都按工号匹配员工,员工没有维护工号就取不到导入数据。
- 勾选「同步考勤」后,上传的考勤文件会被忽略,两者只会生效一个。
- 勾选「同步社保」时,如果对应月份的社保月度表还没创建,核算会直接报错,需要先去社保模块创建当月社保表。
# 1.4 谁会进本月工资表
计薪范围由 getPayrollEmployeeList 计算:入职时间早于或等于计薪周期结束日,且在职,或者已离职但离职日期不早于计薪周期开始日(即当月还需发一次工资)。在此基础上,只有已加入薪资组的员工才会被核算。
因此工资表页面用 SalaryPayrollReadinessAlert.vue 给出两类提醒:
- 未加入任何薪资组:这些员工不会出现在工资表里,需要先去薪资组补配置。
- 没有生效薪资档案:仍会参与核算,但工资项优先继承上月工资,无上月工资时按 0 处理。

# 1.5 四道快照边界
薪资链路上有多处「固化」动作,理解它们才能判断改动会不会影响已有数据:
| 快照 | 固化时点 | 后续变更的影响 |
|---|---|---|
| 调薪记录的调整前后值 | 提交定薪、调薪时 | 记录不可改,只能取消未生效的调薪 |
| 工资表表头 | 每次核算时 | 重新核算会按最新工资项刷新表头 |
| 员工当月工资项金额 | 每次核算、在线编辑时 | 重新核算会覆盖,归档后不再变化 |
| 工资条内容 | 发放工资条时 | 之后重新核算月度工资,不会改写已经发出的工资条 |
# 2. 月度工资
月表,由 HrmSalaryMonthRecordController 提供接口(/hrm/salary/month-record);员工明细,由 HrmSalaryMonthEmployeeRecordController 提供接口(/hrm/salary/month-employee-record)。
核算时读取计薪周期内有效的员工薪资,并可同步社保、同步或导入考勤,以及导入累计个税和专项附加扣除。
# 2.1 主表表结构
CREATE TABLE `hrm_salary_month_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`title` varchar(128) DEFAULT NULL COMMENT '工资表标题',
`year` int NOT NULL COMMENT '年份',
`month` int NOT NULL COMMENT '月份',
`start_time` datetime DEFAULT NULL COMMENT '计薪开始时间',
`end_time` datetime DEFAULT NULL COMMENT '计薪结束时间',
`status` tinyint DEFAULT NULL COMMENT '状态',
`employee_count` int DEFAULT NULL COMMENT '员工人数',
`expected_pay_salary` decimal(12,2) DEFAULT NULL COMMENT '应发工资',
`personal_tax` decimal(12,2) DEFAULT NULL COMMENT '个人所得税',
`real_pay_salary` decimal(12,2) DEFAULT NULL COMMENT '实发工资',
`personal_insurance_amount` decimal(12,2) DEFAULT NULL COMMENT '个人社保',
`personal_provident_fund_amount` decimal(12,2) DEFAULT NULL COMMENT '个人公积金',
`corporate_insurance_amount` decimal(12,2) DEFAULT NULL COMMENT '企业社保',
`corporate_provident_fund_amount` decimal(12,2) DEFAULT NULL COMMENT '企业公积金',
`option_headers` varchar(20000) DEFAULT NULL COMMENT '工资项表头快照 JSON',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='HRM 月度工资表';
① 枚举 status 工资表状态(HrmSalaryMonthRecordStatusEnum,字典 hrm_salary_month_status:5 = 未核算,11 = 已核算,10 = 已归档)。详见 §2.3 状态流转。
② option_headers 保存本月的工资项树快照。之后修改计薪设置不会立即改写已创建工资表的表头;未归档工资表重新核算时,会按当前工资项刷新表头,归档后不再重新核算,因此历史结构保持稳定。
③ start_time / end_time 按计薪周期配置生成,跨月计薪时与自然月不一致。
该表包含一个子表:
hrm_salary_month_employee_record(员工月度工资记录):保存每名员工的计薪天数、各工资项金额和汇总结果。
# 2.2 子表结构
CREATE TABLE `hrm_salary_month_employee_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`month_record_id` bigint NOT NULL COMMENT '月度工资表编号',
`employee_id` bigint NOT NULL COMMENT '员工编号',
`year` int NOT NULL COMMENT '年份',
`month` int NOT NULL COMMENT '月份',
`actual_work_day` decimal(10,2) DEFAULT NULL COMMENT '实际工作天数',
`need_work_day` decimal(10,2) DEFAULT NULL COMMENT '应工作天数',
`expected_pay_salary` decimal(12,2) DEFAULT NULL COMMENT '应发工资',
`taxable_salary` decimal(12,2) DEFAULT NULL COMMENT '应税工资',
`personal_tax` decimal(12,2) DEFAULT NULL COMMENT '个人所得税',
`real_pay_salary` decimal(12,2) DEFAULT NULL COMMENT '实发工资',
`option_values` varchar(20000) DEFAULT NULL COMMENT '员工工资项值快照 JSON',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='HRM 员工月度工资记录';
① month_record_id 关联 hrm_salary_month_record 表的 id 字段,employee_id 关联员工;冗余的 year / month 用于员工历史查询和工资条生成。
② option_values 与月表的 option_headers 按工资项 code 对齐,保存当月每个项目的金额快照。
# 2.3 状态流转
月度工资表生命周期由 HrmSalaryMonthRecordServiceImpl 控制。状态枚举 HrmSalaryMonthRecordStatusEnum:
| 状态值 | 枚举 | 说明 | 可执行操作 |
|---|---|---|---|
| 5 | UNCOMPUTED | 未核算 | 在线编辑、核算、删除 |
| 11 | COMPUTED | 已核算 | 在线编辑、重新核算、发送工资条、创建下月、删除 |
| 10 | HISTORY | 已归档 | — |
状态流转说明
创建 ──→ 未核算(5) ──核算──→ 已核算(11) ──创建下月──→ 已归档(10)(终态)
↑ │
└──重新核算─────┘
- 创建首张(
createMonthRecord):由初始化计薪周期时自动触发,按起始年月创建。 - 核算 / 重新核算(
computeMonthRecord):读取计薪周期内有效的员工薪资,重新生成员工工资项、应发、应税、个税和实发汇总,状态变为已核算。已核算月份仍可反复重新核算。 - 在线编辑(
updateMonthEmployeeRecordList):未归档月份可直接修改叶子工资项和计薪天数,随后重新汇总主表金额。 - 创建下月(
createNextMonthRecord):当前月归档并变为只读,按计薪周期创建下一月。 - 删除(
deleteMonthRecord):仅最新且未归档的工资表可删除,且至少保留一张;删除后上一月恢复为当前月。
# 2.4 管理后台
对应 [HRM 人力资源 -> 薪资管理 -> 月度工资表] 菜单,对应 yudao-ui-admin-vue3 项目的 @/views/hrm/salary/month-record 目录。
# 当前工资表
页面读取当前工资表及员工明细,可按员工、工号和部门筛选,并以「计薪人数、新入职、离职、转正、调岗」页签查看本月人员变化(HrmSalaryEmployeeChangeTypeEnum)。
SalaryPayrollReadinessAlert.vue 会做核算准备检查:未加入薪资组的员工不参与核算,没有生效薪资档案的员工优先沿用上月工资。详见 §1.4 谁会进本月工资表。

# 在线编辑
未归档月份可点击【在线编辑】打开 SalaryBatchEmployeeRecordForm.vue。表单只允许修改本月可计算的叶子工资项和计薪天数,系统计算项(应发、应税、个税、实发)不可直接编辑。

# 核算与重新核算
点击【核算工资】打开 SalaryMonthComputeForm.vue:可选择是否同步社保,选择直接同步考勤或上传考勤文件,也可上传累计个税与专项附加扣除。三类导入模板均可在弹窗中下载。各项数据的取数规则和覆盖顺序,详见 §1.3 核算时的取数与覆盖顺序。

# 创建下月与删除
点击【创建下月工资表】并确认后,当前月归档并变为只读。最新未归档工资表可以删除,删除后上一月恢复为当前月。
# 历史工资表
month-record/history/index.vue 按年份查询已归档月份,点击月份进入 month-record/detail/index.vue 查看只读工资表。

# 3. 工资条
工资条,由 HrmSalarySlipTemplateController、HrmSalarySlipSendRecordController 和 HrmSalarySlipController 提供接口,按「模板 → 发放批次 → 员工工资条」三级保存:模板决定展示分类和工资项,每次发放创建一个批次,并为选中员工生成独立快照。
# 3.1 表结构
hrm_salary_slip_template 表结构
CREATE TABLE `hrm_salary_slip_template` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(64) NOT NULL COMMENT '工资条模板名称',
`default_status` bit NOT NULL DEFAULT 0 COMMENT '是否默认模板',
`hide_empty` bit NOT NULL DEFAULT 0 COMMENT '是否隐藏空项',
`options` varchar(20000) NOT NULL COMMENT '工资条模板项 JSON',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='HRM 工资条模板';
① options 以树形 JSON 保存分类与工资项:节点类型见 HrmSalarySlipTemplateOptionTypeEnum(1 分类、2 薪资项),内置分类见 HrmSalarySlipTemplateCategoryEnum(-1 基本项、-2 明细项)。
② 默认模板作为兜底模板,管理端不能修改或删除;默认模板包含的工资项见 HrmSalaryOptionCodeEnum.SALARY_SLIP_DEFAULT_CODES。
hrm_salary_slip_send_record 表结构
CREATE TABLE `hrm_salary_slip_send_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`month_record_id` bigint NOT NULL COMMENT '月度工资表编号',
`year` int NOT NULL COMMENT '年份',
`month` int NOT NULL COMMENT '月份',
`employee_count` int DEFAULT NULL COMMENT '工资表总人数',
`send_employee_count` int DEFAULT NULL COMMENT '本批次发放人数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='HRM 工资条发放记录';
month_record_id 关联 hrm_salary_month_record 表的 id 字段。同一月度工资表可以分多批次发放,因此发放记录保持独立,并冗余年月和人数供列表展示。
hrm_salary_slip 表结构
CREATE TABLE `hrm_salary_slip` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`send_record_id` bigint NOT NULL COMMENT '发放批次编号',
`month_employee_record_id` bigint NOT NULL COMMENT '员工月工资编号',
`employee_id` bigint NOT NULL COMMENT '员工编号',
`year` int NOT NULL COMMENT '年份',
`month` int NOT NULL COMMENT '月份',
`read_status` tinyint DEFAULT NULL COMMENT '阅读状态',
`real_pay_salary` decimal(12,2) DEFAULT NULL COMMENT '实发工资',
`options` varchar(20000) NOT NULL COMMENT '工资条内容快照 JSON',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='HRM 员工工资条';
① send_record_id 关联发放批次,month_employee_record_id 关联员工月工资,employee_id 关联员工。
② 枚举 read_status 阅读状态(HrmSalarySlipReadStatusEnum,字典 hrm_salary_slip_read_status):0 未读、1 已读。员工端打开工资条后由后端标记为已读。
③ options 是发放时的快照,月工资后续重新核算不会改写已经发出的工资条。
④ 同一员工、同一年月只保留一张有效工资条。指定员工发放时,未绑定后台账号会导致请求失败;按筛选结果全部发放时,系统会自动过滤未绑定账号的员工。
# 3.2 管理后台
对应 [HRM 人力资源 -> 薪资管理 -> 发放记录] 菜单,对应 yudao-ui-admin-vue3 项目的 @/views/hrm/salary/slip 目录。
# 发放工资条
已核算工资表点击【发送工资条】打开 SalarySlipSendForm.vue,分两步完成:
- 第一步,选择模板:可调整隐藏空项、项目顺序和展示内容,并可另存为新模板。
- 第二步,选择员工:按员工姓名、部门和已发 / 未发状态筛选,可勾选员工发放,也可按当前筛选条件全部发放。
提交后一次生成发放批次及员工工资条快照。未核算的工资表不允许发放;管理后台只在当前已核算工资表展示【发送工资条】,归档后的历史详情页只读。

# 维护工资条模板
发放弹窗中的【新增模板】【编辑模板】打开 SalarySlipTemplateForm.vue,内部使用 SalarySlipTemplateOptionEditor.vue 编排分类和工资项。非默认模板才可删除。

# 发放记录
slip/send-record/index.vue 按年月查询发放批次,点击批次进入详情查看批次汇总。

# 工资条详情与备注
批次详情中的 SalarySlipList.vue 可按员工、部门、阅读状态和备注筛选。点击员工打开 SalarySlipDetail.vue 查看工资条快照,并支持单条或批量设置、清空备注。

# 删除发放记录
删除批次时会同时逻辑删除该批次的员工工资条,因此这些员工会重新出现在对应月份的「未发送」筛选中,可以再次发放。
# 3.3 员工端
员工端「我的工资条」,由 HrmPortalSalarySlipController 提供接口(/hrm/portal/salary/slip),根据当前 system_users.id 反查 hrm_employee.user_id,只返回本人工资条。
对应 [HRM 人力资源 -> HRM 员工端 -> 我的工资条] 菜单,对应 yudao-ui-admin-vue3 项目的 @/views/hrm/portal/salary/slip 目录。
# 查询工资条
按月份范围和排序方式查询本人历史工资条,展开后查看发放时保存的工资项和实发金额快照。

# 标记已读
页面进入时获取未读汇总,加载工资条后把本次返回的未读记录标记为已读,管理端的阅读状态随之更新。个人工作台也会展示未读工资条数量。
# 4. 与其它模块的衔接
- 员工:月度工资和工资条都以
hrm_employee.id为业务关联;只有员工端访问和工资条通知需要绑定后台账号。 - 薪资档案:核算读取计薪周期内生效的员工薪资档案,缺失时继承上月工资,详见 《【薪资】计薪设置、薪资档案》。
- 考勤:月度核算可读取实时考勤统计,也可通过 Excel 导入考勤结果,详见 《【考勤】考勤管理》。
- 社保:按计薪配置选择上月、当月或次月的社保月度记录,并同步个人 / 企业社保与公积金,详见 《【社保】社保管理》。
- 绩效:已归档绩效可按计薪月份提供绩效系数。该系数作为衔接数据返回,不会自动乘入工资项,详见 《【绩效】绩效考核、绩效档案》。