2026-05-06 dcc8bb8f47544cbad6e6440640dcdaa946086013
feat(staff): 员工模块新增民族出生日期入职日期字段

- 在StaffOnJob实体类中添加nation、birthDate、entryDate三个字段
- 更新StaffOnJobImportDto导入DTO的字段映射和排序
- 修改数据库查询条件使用IFNULL函数支持入职日期查询
- 重构StaffOnJobServiceImpl实现类的数据处理逻辑
- 添加员工数据同步和字段回填功能确保兼容性
- 优化导入验证逻辑增强错误提示信息准确性
- 完善员工台账分页查询和详情展示功能
- 补充数据库变更脚本和前端联调文档说明
已添加2个文件
已修改7个文件
485 ■■■■ 文件已修改
doc/20260506_staff_on_job_add_nation_birth_entry_date.sql 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/员工模块-前端联调文档-20260506.md 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffOnJobImportDto.java 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockOutRecordMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260506_staff_on_job_add_nation_birth_entry_date.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
ALTER TABLE staff_on_job
    ADD COLUMN nation VARCHAR(32) NULL COMMENT '民族' AFTER sex;
ALTER TABLE staff_on_job
    ADD COLUMN birth_date DATE NULL COMMENT '出生日期' AFTER nation;
ALTER TABLE staff_on_job
    ADD COLUMN entry_date DATE NULL COMMENT '入职日期' AFTER emergency_contact_phone;
UPDATE staff_on_job
SET nation = native_place
WHERE (nation IS NULL OR nation = '')
  AND native_place IS NOT NULL
  AND native_place <> '';
UPDATE staff_on_job
SET entry_date = DATE(create_time)
WHERE entry_date IS NULL
  AND create_time IS NOT NULL;
doc/Ô±¹¤Ä£¿é-ǰ¶ËÁªµ÷Îĵµ-20260506.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
# å‘˜å·¥æ¨¡å—前端联调文档(2026-05-06)
## 1. æœ¬æ¬¡å˜æ›´èŒƒå›´
模块:`员工台账 / å…¥èŒä¿¡æ¯`
接口前缀:`/staff/staffOnJob`
新增/调整后的入职表单字段:
1. å‘˜å·¥ç¼–号 `staffNo`
2. å§“名 `staffName`
3. æ€§åˆ« `sex`
4. æ°‘族 `nation`
5. å‡ºç”Ÿæ—¥æœŸ `birthDate`
6. èº«ä»½è¯å· `identityCard`
7. çŽ°ä½å€ `adress`
8. è”系电话 `phone`
9. ç´§æ€¥è”系人电话 `emergencyContactPhone`
10. å…¥èŒæ—¥æœŸ `entryDate`
11. å²—位 `sysPostId`
12. éƒ¨é—¨ `sysDeptId`
13. åˆåŒå¹´é™ `contractTerm`(原逻辑不变)
14. åˆåŒå¼€å§‹æ—¥æœŸ `contractStartTime`(原逻辑不变)
15. åˆåŒç»“束日期 `contractEndTime`(原逻辑不变)
## 2. æ•°æ®åº“变更
先执行 SQL:
`doc/20260506_staff_on_job_add_nation_birth_entry_date.sql`
新增列:
1. `nation`(民族)
2. `birth_date`(出生日期)
3. `entry_date`(入职日期)
兼容处理:
1. `nation` ä¼šå›žå¡«åŽ†å² `native_place`
2. `entry_date` ä¼šå›žå¡«åŽ†å² `create_time` çš„æ—¥æœŸéƒ¨åˆ†
## 3. æŽ¥å£æ¸…单(核心联调)
### 3.1 æ–°å¢žå…¥èŒ
- `POST /staff/staffOnJob`
- `Content-Type: application/json`
请求示例:
```json
{
  "staffNo": "YG20260506001",
  "staffName": "张三",
  "sex": "男",
  "nation": "汉族",
  "birthDate": "1995-06-08",
  "identityCard": "320XXXXXXXXXXXXX",
  "adress": "南通市崇川区XXXè·¯",
  "phone": "13800000000",
  "emergencyContactPhone": "13900000000",
  "entryDate": "2026-05-06",
  "sysPostId": 12,
  "sysDeptId": 6,
  "contractTerm": "3",
  "contractStartTime": "2026-05-06",
  "contractEndTime": "2029-05-05"
}
```
说明:
1. `staffNo` å”¯ä¸€ï¼Œé‡å¤ä¼šæŠ¥é”™
2. å¦‚æžœ `entryDate` ä¸ä¼ ï¼ŒåŽç«¯ä¼šå›žé€€ä½¿ç”¨ `contractStartTime`
3. æ–°å¢žæ—¶ä¼šåŒæ­¥å†™å…¥ä¸€æ¡åˆåŒè®°å½•(`staff_contract`)
### 3.2 ç¼–辑入职信息
- `PUT /staff/staffOnJob/{id}`
请求体与新增一致;后端会按路径参数 `{id}` æ›´æ–°ï¼Œä¸ä¾èµ– body é‡Œçš„ `id`。
### 3.3 è¯¦æƒ…
- `GET /staff/staffOnJob/{id}`
返回会包含:
1. å‘˜å·¥åŸºç¡€å­—段(含 `nation`、`birthDate`、`entryDate`)
2. æœ€æ–°åˆåŒå­—段(`contractTerm`、`contractStartTime`、`contractEndTime`)
3. `postName`、`deptName`
### 3.4 åˆ†é¡µåˆ—表
- `GET /staff/staffOnJob/listPage`
常用查询参数:
1. `staffState`
2. `staffName`
3. `entryDateStart`
4. `entryDateEnd`
说明:
`entryDateStart/entryDateEnd` çŽ°åœ¨æŒ‰å…¥èŒæ—¥æœŸè¿‡æ»¤ï¼ˆ`entry_date`),不再按合同到期日期过滤。
## 4. å…¼å®¹ç­–略(前端可直接用)
1. æ–°è¡¨å•直接使用 `nation` å­—段即可
2. åŽç«¯ä»ä¼šç»´æŠ¤ `nativePlace` å…¼å®¹åŽ†å²åŠŸèƒ½ï¼ˆå¦‚ç¦»èŒæ¨¡å—è”æŸ¥ï¼‰
3. åŽ†å²æ•°æ®è‹¥ `nation` ä¸ºç©ºï¼ŒæŽ¥å£ä¼šå›žå¡«æ˜¾ç¤ºä¸ºæ—§ `nativePlace` å€¼
## 5. å¯¼å…¥æ¨¡æ¿å˜æ›´
`/staff/staffOnJob/downloadTemplate` å¯¼å…¥æ¨¡æ¿å­—段已按新口径调整,主要包含:
1. å‘˜å·¥ç¼–号、姓名、性别、民族、出生日期、身份证号
2. çŽ°ä½å€ã€è”ç³»ç”µè¯ã€ç´§æ€¥è”ç³»äººç”µè¯ã€å…¥èŒæ—¥æœŸ
3. å²—位、部门、合同年限、合同开始日期、合同结束日期
## 6. å‰ç«¯è”调注意点
1. æ—¥æœŸå­—段统一传 `yyyy-MM-dd`
2. å²—位/部门传 ID:`sysPostId`、`sysDeptId`
3. æ–°å¢žå’Œç¼–辑都要传合同三字段(合同逻辑不变)
4. `staffNo` éœ€è¦å‰ç«¯å…ˆåšå”¯ä¸€æ€§æç¤ºï¼ˆåŽç«¯ä¹Ÿä¼šå…œåº•校验)
src/main/java/com/ruoyi/staff/dto/StaffOnJobImportDto.java
@@ -5,7 +5,6 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
@@ -14,53 +13,53 @@
    @Excel(name = "员工编号", cellType = Excel.ColumnType.STRING, sort = 1)
    private String staffNo;
    @Excel(name = "员工姓名", sort = 2)
    @Excel(name = "姓名", sort = 2)
    private String staffName;
    @Excel(name = "性别", sort = 3, combo = {"男", "女"})
    @Excel(name = "性别", sort = 3, combo = {"male", "female"})
    private String sex;
    @Excel(name = "籍贯", sort = 4)
    private String nativePlace;
    @Excel(name = "民族", sort = 4)
    private String nation;
    @Excel(name = "岗位", sort = 5, prompt = "请填写系统中已存在的岗位名称")
    private String postName;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "出生日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 5)
    private Date birthDate;
    @Excel(name = "部门", sort = 6, prompt = "请填写系统中已存在的部门名称")
    private String deptName;
    @Excel(name = "身份证号", cellType = Excel.ColumnType.STRING, sort = 6)
    private String identityCard;
    @Excel(name = "现住址", sort = 7)
    private String adress;
    @Excel(name = "第一学历", sort = 8)
    private String firstStudy;
    @Excel(name = "专业", sort = 9)
    private String profession;
    @Excel(name = "年龄", sort = 11)
    private String age;
    @Excel(name = "联系电话", cellType = Excel.ColumnType.STRING, sort = 12)
    @Excel(name = "联系电话", cellType = Excel.ColumnType.STRING, sort = 8)
    private String phone;
    @Excel(name = "紧急联系人", sort = 13)
    private String emergencyContact;
    @Excel(name = "紧急联系人联系电话", cellType = Excel.ColumnType.STRING, sort = 14)
    @Excel(name = "紧急联系人电话", cellType = Excel.ColumnType.STRING, sort = 9)
    private String emergencyContactPhone;
    @Excel(name = "合同年限", sort = 15)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "入职日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 10)
    private Date entryDate;
    @Excel(name = "岗位", sort = 11, prompt = "Fill enabled post name")
    private String postName;
    @Excel(name = "部门", sort = 12, prompt = "Fill enabled department name")
    private String deptName;
    @Excel(name = "合同年限", sort = 13)
    private String contractTerm;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "合同开始日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 16)
    @Excel(name = "合同开始日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 14)
    private Date contractStartTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "合同结束日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 17)
    @Excel(name = "合同结束日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 15)
    private Date contractEndTime;
}
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java
@@ -53,6 +53,22 @@
    private String sex;
    /**
     * æ°‘族
     */
    @TableField("nation")
    @Excel(name = "民族", sort = 23)
    private String nation;
    /**
     * å‡ºç”Ÿæ—¥æœŸ
     */
    @TableField("birth_date")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "出生日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 24)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthDate;
    /**
     * ç±è´¯
     */
    @Excel(name = "籍贯", sort = 5)
@@ -105,6 +121,15 @@
    private String phone;
    /**
     * å…¥èŒæ—¥æœŸ
     */
    @TableField("entry_date")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "入职日期", width = 30, dateFormat = "yyyy-MM-dd", sort = 25)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date entryDate;
    /**
     * ç´§æ€¥è”系人
     */
    @Excel(name = "紧急联系人", sort = 14)
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -13,8 +12,8 @@
import com.ruoyi.project.system.domain.SysPost;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysPostMapper;
import com.ruoyi.staff.dto.StaffOnJobImportDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.dto.StaffOnJobImportDto;
import com.ruoyi.staff.mapper.StaffContractMapper;
import com.ruoyi.staff.mapper.StaffLeaveMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
@@ -25,7 +24,6 @@
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -34,18 +32,26 @@
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@AllArgsConstructor
@Service
public class StaffOnJobServiceImpl extends ServiceImpl<StaffOnJobMapper, StaffOnJob>  implements IStaffOnJobService {
public class StaffOnJobServiceImpl extends ServiceImpl<StaffOnJobMapper, StaffOnJob> implements IStaffOnJobService {
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
@@ -53,93 +59,91 @@
    private SysPostMapper sysPostMapper;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Autowired
    private StaffContractMapper staffContractMapper;
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
    //在职员工台账分页查询
    @Override
    public IPage<StaffOnJobDto> staffOnJobListPage(Page page, StaffOnJob staffOnJob) {
        return staffOnJobMapper.staffOnJobListPage(page,staffOnJob);
        IPage<StaffOnJobDto> result = staffOnJobMapper.staffOnJobListPage(page, staffOnJob);
        fillNationFallback(result.getRecords());
        return result;
    }
    //新增入职
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int add(StaffOnJobDto staffOnJobPrams) {
        String[] ignoreProperties = {"id"};//排除id属性
        // åˆ¤æ–­ç¼–号是否存在
        List<StaffOnJob> staffOnJobs = staffOnJobMapper.selectList(Wrappers.<StaffOnJob>lambdaQuery().eq(StaffOnJob::getStaffNo, staffOnJobPrams.getStaffNo()));
        if (staffOnJobs.size()>0){
            throw new BaseException("编号为"+staffOnJobPrams.getStaffNo()+"的员工已经存在,无法新增!!!");
        }
        // åˆ›å»ºå…¥èŒæ•°æ®
        staffOnJobPrams.setContractExpireTime(staffOnJobPrams.getContractEndTime());
        staffOnJobPrams.setStaffState(1);
        staffOnJobMapper.insert(staffOnJobPrams);
    public int add(StaffOnJobDto staffOnJobParams) {
        syncStaffBasicFields(staffOnJobParams);
        // åˆ›å»ºåˆåŒè®°å½•
        List<StaffOnJob> existedStaffNo = staffOnJobMapper.selectList(Wrappers.<StaffOnJob>lambdaQuery()
                .eq(StaffOnJob::getStaffNo, staffOnJobParams.getStaffNo()));
        if (!CollectionUtils.isEmpty(existedStaffNo)) {
            throw new BaseException("Duplicate staffNo: " + staffOnJobParams.getStaffNo());
        }
        staffOnJobParams.setContractExpireTime(staffOnJobParams.getContractEndTime());
        staffOnJobParams.setStaffState(1);
        staffOnJobMapper.insert(staffOnJobParams);
        StaffContract staffContract = new StaffContract();
        staffContract.setStaffOnJobId(staffOnJobPrams.getId());
        staffContract.setContractTerm(staffOnJobPrams.getContractTerm());
        staffContract.setContractStartTime(staffOnJobPrams.getContractStartTime());
        staffContract.setContractEndTime(staffOnJobPrams.getContractEndTime());
        staffContract.setStaffOnJobId(staffOnJobParams.getId());
        staffContract.setContractTerm(staffOnJobParams.getContractTerm());
        staffContract.setContractStartTime(staffOnJobParams.getContractStartTime());
        staffContract.setContractEndTime(staffOnJobParams.getContractEndTime());
        return staffContractMapper.insert(staffContract);
    }
    //更新入职信息
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int update(Long id, StaffOnJobDto staffOnJobParams) {
        // åˆ¤æ–­å¯¹è±¡æ˜¯å¦å­˜åœ¨
        syncStaffBasicFields(staffOnJobParams);
        staffOnJobParams.setId(id);
        StaffOnJob job = staffOnJobMapper.selectById(id);
        if (job == null){
            throw new BaseException("编号为"+staffOnJobParams.getStaffNo()+"的员工不存在,无法更新!!!");
        if (job == null) {
            throw new BaseException("Staff not found, id=" + id);
        }
        String[] ignoreProperties = {"id"};//排除更新属性
        if (StringUtils.isNotBlank(staffOnJobParams.getStaffNo())) {
            List<StaffOnJob> existedStaffNo = staffOnJobMapper.selectList(Wrappers.<StaffOnJob>lambdaQuery()
                    .eq(StaffOnJob::getStaffNo, staffOnJobParams.getStaffNo())
                    .ne(StaffOnJob::getId, id));
            if (!CollectionUtils.isEmpty(existedStaffNo)) {
                throw new BaseException("Duplicate staffNo: " + staffOnJobParams.getStaffNo());
            }
        }
        // èŽ·å–æœ€æ–°åˆåŒæ•°æ®ï¼Œå¹¶ä¸”æ›´æ–°
        String[] ignoreProperties = {"id"};
        StaffContract contract = staffContractMapper.selectOne(Wrappers.<StaffContract>lambdaQuery()
                .eq(StaffContract::getStaffOnJobId, id)
                .last("limit 1")
                .orderByDesc(StaffContract::getId));
        if (contract != null){
            BeanUtils.copyProperties(staffOnJobParams,contract,ignoreProperties);
                .orderByDesc(StaffContract::getId)
                .last("limit 1"));
        if (contract != null) {
            BeanUtils.copyProperties(staffOnJobParams, contract, ignoreProperties);
            staffContractMapper.updateById(contract);
        }
        // æ›´æ–°å‘˜å·¥æ•°æ®
        staffOnJobParams.setContractExpireTime(staffOnJobParams.getContractEndTime());
        return staffOnJobMapper.updateById(staffOnJobParams);
    }
    //删除入职
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delStaffOnJobs(List<Integer> ids) {
        // åˆ é™¤å…¥èŒæ•°æ®
        staffOnJobMapper.deleteBatchIds(ids);
        // åˆ é™¤ç¦»èŒæ•°æ®
        staffLeaveMapper.delete(Wrappers.<StaffLeave>lambdaQuery().in(StaffLeave::getStaffOnJobId, ids));
        // åˆ é™¤åˆåŒæ•°æ®
        return staffContractMapper.delete(Wrappers.<StaffContract>lambdaQuery().in(StaffContract::getStaffOnJobId, ids));
    }
    // ç»­ç­¾åˆåŒ
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int renewContract(Long id, StaffContract staffContract) {
        // åˆ¤æ–­å¯¹è±¡æ˜¯å¦å­˜åœ¨
        StaffOnJob job = staffOnJobMapper.selectById(id);
        if (job == null){
            throw new BaseException("该员工不存在,无法更新!!!");
        if (job == null) {
            throw new BaseException("Staff not found, id=" + id);
        }
        // å¢žåŠ åˆåŒ
        StaffContract newStaffContract = new StaffContract();
        newStaffContract.setStaffOnJobId(id);
        newStaffContract.setContractTerm(staffContract.getContractTerm());
@@ -147,23 +151,21 @@
        newStaffContract.setContractEndTime(staffContract.getContractEndTime());
        staffContractMapper.insert(newStaffContract);
        // æ›´æ–°å‘˜å·¥åˆåŒè¿‡æœŸæ—¶é—´
        job.setContractExpireTime(staffContract.getContractEndTime());
        staffOnJobMapper.updateById(job);
        return 0;
    }
    //在职员工详情
    @Override
    public StaffOnJobDto staffOnJobDetail(Long id) {
        StaffOnJob staffOnJob  = staffOnJobMapper.selectById(id);
        StaffOnJob staffOnJob = staffOnJobMapper.selectById(id);
        if (staffOnJob == null) {
            throw new IllegalArgumentException("该员工不存在");
            throw new BaseException("Staff not found, id=" + id);
        }
        StaffOnJobDto staffOnJobDto = new StaffOnJobDto();
        BeanUtils.copyProperties(staffOnJob, staffOnJobDto);
        // æŸ¥è¯¢å²—位名称
        if (staffOnJob.getSysPostId() != null) {
            SysPost post = sysPostMapper.selectPostById(staffOnJob.getSysPostId().longValue());
            if (post != null) {
@@ -171,30 +173,33 @@
            }
        }
        // æŸ¥è¯¢åˆåŒä¿¡æ¯
        StaffContract contract = staffContractMapper.selectOne(Wrappers.<StaffContract>lambdaQuery()
                .eq(StaffContract::getStaffOnJobId, staffOnJob.getId())
                .last("limit 1")
                .orderByDesc(StaffContract::getId));
        if (contract != null){
                .orderByDesc(StaffContract::getId)
                .last("limit 1"));
        if (contract != null) {
            staffOnJobDto.setContractTerm(contract.getContractTerm());
            staffOnJobDto.setContractStartTime(contract.getContractStartTime());
            staffOnJobDto.setContractEndTime(contract.getContractEndTime());
        }
        fillNationFallback(staffOnJobDto);
        return staffOnJobDto;
    }
    //在职员工导出
    @Override
    public void staffOnJobExport(HttpServletResponse response, StaffOnJob staffOnJob) {
        List<StaffOnJobDto> staffOnJobs = staffOnJobMapper.staffOnJobList(staffOnJob);
        ExcelUtil<StaffOnJobDto> util = new ExcelUtil<StaffOnJobDto>(StaffOnJobDto.class);
        fillNationFallback(staffOnJobs);
        ExcelUtil<StaffOnJobDto> util = new ExcelUtil<>(StaffOnJobDto.class);
        util.exportExcel(response, staffOnJobs, "在职员工台账导出");
    }
    @Override
    public List<StaffOnJobDto> staffOnJobList(StaffOnJob staffOnJob) {
        return staffOnJobMapper.staffOnJobList(staffOnJob);
        List<StaffOnJobDto> staffOnJobs = staffOnJobMapper.staffOnJobList(staffOnJob);
        fillNationFallback(staffOnJobs);
        return staffOnJobs;
    }
    @Override
@@ -204,7 +209,7 @@
            ExcelUtil<StaffOnJobImportDto> util = new ExcelUtil<>(StaffOnJobImportDto.class);
            List<StaffOnJobImportDto> staffOnJobs = util.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(staffOnJobs)) {
                throw new BaseException("模板错误或导入数据为空");
                throw new BaseException("Import data is empty");
            }
            Map<String, SysPost> postMap = buildPostMap();
@@ -219,7 +224,7 @@
            if (e instanceof BaseException) {
                throw (BaseException) e;
            }
            throw new BaseException("导入失败,请检查模板格式是否正确");
            throw new BaseException("Import failed, please check template and data format");
        }
    }
@@ -230,34 +235,37 @@
        String deptName = normalizeValue(row.getDeptName());
        if (StringUtils.isBlank(staffNo)) {
            throw new BaseException("第" + rowNum + "行员工编号不能为空");
            throw new BaseException("Row " + rowNum + " staffNo cannot be blank");
        }
        if (!importedStaffNos.add(staffNo)) {
            throw new BaseException("第" + rowNum + "行员工编号重复:" + staffNo);
            throw new BaseException("Row " + rowNum + " duplicated staffNo: " + staffNo);
        }
        if (StringUtils.isBlank(normalizeValue(row.getStaffName()))) {
            throw new BaseException("第" + rowNum + "行员工姓名不能为空");
            throw new BaseException("Row " + rowNum + " staffName cannot be blank");
        }
        if (StringUtils.isBlank(postName)) {
            throw new BaseException("第" + rowNum + "行岗位不能为空");
            throw new BaseException("Row " + rowNum + " postName cannot be blank");
        }
        if (StringUtils.isBlank(deptName)) {
            throw new BaseException("第" + rowNum + "行部门不能为空");
            throw new BaseException("Row " + rowNum + " deptName cannot be blank");
        }
        if (row.getContractStartTime() == null) {
            throw new BaseException("第" + rowNum + "行合同开始日期不能为空");
            throw new BaseException("Row " + rowNum + " contractStartTime cannot be blank");
        }
        if (row.getContractEndTime() == null) {
            throw new BaseException("第" + rowNum + "行合同结束日期不能为空");
            throw new BaseException("Row " + rowNum + " contractEndTime cannot be blank");
        }
        if (row.getEntryDate() == null) {
            throw new BaseException("Row " + rowNum + " entryDate cannot be blank");
        }
        SysPost post = postMap.get(postName);
        if (post == null) {
            throw new BaseException("第" + rowNum + "行岗位不存在或已停用:" + postName);
            throw new BaseException("Row " + rowNum + " post not found or disabled: " + postName);
        }
        SysDept dept = deptMap.get(deptName);
        if (dept == null) {
            throw new BaseException("第" + rowNum + "行部门不存在或已停用:" + deptName);
            throw new BaseException("Row " + rowNum + " dept not found or disabled: " + deptName);
        }
        StaffOnJobDto staffOnJobDto = new StaffOnJobDto();
@@ -265,30 +273,69 @@
        staffOnJobDto.setStaffNo(staffNo);
        staffOnJobDto.setStaffName(normalizeValue(row.getStaffName()));
        staffOnJobDto.setSex(normalizeValue(row.getSex()));
        staffOnJobDto.setNativePlace(normalizeValue(row.getNativePlace()));
        staffOnJobDto.setNation(normalizeValue(row.getNation()));
        staffOnJobDto.setNativePlace(normalizeValue(row.getNation()));
        staffOnJobDto.setBirthDate(row.getBirthDate());
        staffOnJobDto.setIdentityCard(normalizeValue(row.getIdentityCard()));
        staffOnJobDto.setAdress(normalizeValue(row.getAdress()));
        staffOnJobDto.setFirstStudy(normalizeValue(row.getFirstStudy()));
        staffOnJobDto.setProfession(normalizeValue(row.getProfession()));
        staffOnJobDto.setAge(normalizeValue(row.getAge()));
        staffOnJobDto.setPhone(normalizeValue(row.getPhone()));
        staffOnJobDto.setEmergencyContact(normalizeValue(row.getEmergencyContact()));
        staffOnJobDto.setEmergencyContactPhone(normalizeValue(row.getEmergencyContactPhone()));
        staffOnJobDto.setEntryDate(row.getEntryDate());
        staffOnJobDto.setContractTerm(normalizeValue(row.getContractTerm()));
        staffOnJobDto.setSysPostId(post.getPostId().intValue());
        staffOnJobDto.setSysDeptId(dept.getDeptId().intValue());
        syncStaffBasicFields(staffOnJobDto);
        return staffOnJobDto;
    }
    private void syncStaffBasicFields(StaffOnJobDto staffOnJobDto) {
        if (staffOnJobDto == null) {
            return;
        }
        staffOnJobDto.setStaffNo(normalizeValue(staffOnJobDto.getStaffNo()));
        staffOnJobDto.setStaffName(normalizeValue(staffOnJobDto.getStaffName()));
        staffOnJobDto.setNation(normalizeValue(staffOnJobDto.getNation()));
        staffOnJobDto.setNativePlace(normalizeValue(staffOnJobDto.getNativePlace()));
        if (StringUtils.isBlank(staffOnJobDto.getNation())) {
            staffOnJobDto.setNation(staffOnJobDto.getNativePlace());
        }
        if (StringUtils.isBlank(staffOnJobDto.getNativePlace())) {
            staffOnJobDto.setNativePlace(staffOnJobDto.getNation());
        }
        if (staffOnJobDto.getEntryDate() == null) {
            staffOnJobDto.setEntryDate(staffOnJobDto.getContractStartTime());
        }
    }
    private void fillNationFallback(StaffOnJob staffOnJob) {
        if (staffOnJob == null) {
            return;
        }
        if (StringUtils.isBlank(staffOnJob.getNation())) {
            staffOnJob.setNation(staffOnJob.getNativePlace());
        }
        if (StringUtils.isBlank(staffOnJob.getNativePlace())) {
            staffOnJob.setNativePlace(staffOnJob.getNation());
        }
    }
    private void fillNationFallback(List<? extends StaffOnJob> staffOnJobs) {
        if (CollectionUtils.isEmpty(staffOnJobs)) {
            return;
        }
        staffOnJobs.forEach(this::fillNationFallback);
    }
    private Map<String, SysPost> buildPostMap() {
        SysPost query = new SysPost();
        query.setStatus("0");
        return buildUniqueMap(sysPostMapper.selectPostList(query), SysPost::getPostName, "岗位");
        return buildUniqueMap(sysPostMapper.selectPostList(query), SysPost::getPostName, "post");
    }
    private Map<String, SysDept> buildDeptMap() {
        SysDept query = new SysDept();
        query.setStatus("0");
        return buildUniqueMap(sysDeptMapper.selectDeptList(query), SysDept::getDeptName, "部门");
        return buildUniqueMap(sysDeptMapper.selectDeptList(query), SysDept::getDeptName, "dept");
    }
    private <T> Map<String, T> buildUniqueMap(List<T> dataList, Function<T, String> nameGetter, String fieldName) {
@@ -303,7 +350,7 @@
                .sorted()
                .collect(Collectors.toList());
        if (!duplicateNames.isEmpty()) {
            throw new BaseException("系统中存在重名" + fieldName + ",无法导入:" + String.join("、", duplicateNames));
            throw new BaseException("Duplicate " + fieldName + " names: " + String.join(", ", duplicateNames));
        }
        return groupedMap.entrySet().stream()
@@ -314,38 +361,27 @@
        return value == null ? null : value.trim();
    }
    @Override
    public String exportCopy(HttpServletResponse response, StaffOnJob staffOnJob) throws Exception {
        String url = "/javaWork/product-inventory-management/file/prod/" + staffOnJob.getStaffName() + "-劳动合同书.docx";
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        // è®¾ç½®æ¨¡æ¿æ–‡ä»¶æ‰€åœ¨ç›®å½•(绝对路径,例如:/templates/)
        cfg.setClassForTemplateLoading(StaffOnJobServiceImpl.class, "/static");
        cfg.setDefaultEncoding("UTF-8");
        //2.定义需要填充的变里
        // â‘  æž„造员工信息(实际项目中可从数据库/Excel读取)
        WordDateDto staff = new WordDateDto();
        BeanUtils.copyProperties(staffOnJob, staff);
        // é€šè¿‡åˆåŒå¹´é™ï¼ŒåˆåŒåˆ°æœŸæ—¥æœŸè®¡ç®—合同开始日期,在获取开始日期,结束日期的年月日数字
        // åˆåŒåˆ°æœŸæ—¥æœŸ - åˆåŒå¹´é™ï¼ˆDate类型)
        // 1. å°†Date转换为Instant(时间戳)
        Instant instant = staff.getContractExpireTime().toInstant();
        // ä¹Ÿå¯ä»¥æŒ‡å®šå…·ä½“时区,例如Asia/Shanghai:
        LocalDate localDate = instant.atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();  // åˆåŒç»“束时间
        LocalDate localDate1 = localDate.minusYears(Integer.parseInt(staff.getContractTerm()));// åˆåŒå¼€å§‹æ—¶é—´
        // ç­¾è®¢æ—¥æœŸè½¬æ¢lcoaldate
        LocalDate localDate = instant.atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        LocalDate localDate1 = localDate.minusYears(Integer.parseInt(staff.getContractTerm()));
        LocalDate localDate2 = staff.getSignDate().toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        // è¯•用日期转换lcoaldate
        LocalDate localDate3 = staff.getTrialStartDate().toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        LocalDate localDate4 = staff.getTrialEndDate().toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDate();
        staff.setQyear(localDate2.getYear() + "");
        staff.setQmoth(localDate2.getMonthValue() + "");
        staff.setQday(localDate2.getDayOfMonth() + "");
        if(staff.getDateSelect().equals("A")){
        if (staff.getDateSelect().equals("A")) {
            staff.setSyear(localDate1.getYear() + "");
            staff.setSmoth(localDate1.getMonthValue() + "");
            staff.setSday(localDate1.getDayOfMonth() + "");
@@ -359,8 +395,7 @@
            staff.setSeyear(localDate4.getYear() + "");
            staff.setSemoth(localDate4.getMonthValue() + "");
            staff.setSeday(localDate4.getDayOfMonth() + "");
        }else if (staff.getDateSelect().equals("B")){
        } else if (staff.getDateSelect().equals("B")) {
            staff.setBsyear(localDate1.getYear() + "");
            staff.setBsmoth(localDate1.getMonthValue() + "");
            staff.setBsday(localDate1.getDayOfMonth() + "");
@@ -371,29 +406,24 @@
            staff.setBseyear(localDate4.getYear() + "");
            staff.setBsemoth(localDate4.getMonthValue() + "");
            staff.setBseday(localDate4.getDayOfMonth() + "");
        }else if (staff.getDateSelect().equals("C")){
        } else if (staff.getDateSelect().equals("C")) {
            staff.setCsyear(localDate1.getYear() + "");
            staff.setCsmoth(localDate1.getMonthValue() + "");
            staff.setCsday(localDate1.getDayOfMonth() + "");
        }
        Map<String,Object> data = new HashMap<>();
        data.put("item",staff);
        //3.加载XML æ¨¡æ¿
        Map<String, Object> data = new HashMap<>();
        data.put("item", staff);
        Template template = cfg.getTemplate("劳动合同书.xml");
        //4.生成填充后的 XML å†…容
        StringWriter out = new StringWriter();
        template.process(data, out);
        String filledXml = out.toString();
        //5.将XML内容写入交件并改为.docx æ ¼å¼
        File outputFile = new File(url);
        try(FileOutputStream fos = new FileOutputStream(outputFile);
            OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
        try (FileOutputStream fos = new FileOutputStream(outputFile);
             OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
            osw.write(filledXml);
        }
        return url;
    }
}
src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -19,10 +19,10 @@
            AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
        </if>
        <if test="staffOnJob.entryDateStart != null and staffOnJob.entryDateStart != '' ">
            AND contract_expire_time &gt;= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
            AND IFNULL(entry_date, DATE(create_time)) &gt;= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
        </if>
        <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
            AND  contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
            AND  IFNULL(entry_date, DATE(create_time)) &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
        </if>
    </select>
    <select id="staffOnJobList" resultType="com.ruoyi.staff.dto.StaffOnJobDto">
@@ -48,7 +48,7 @@
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') &lt;= #{date}
        AND IFNULL(entry_date, DATE(create_time)) &lt;= #{date}
    </select>
    <!-- ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•° -->
@@ -56,6 +56,6 @@
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') BETWEEN #{monthStart} AND #{monthEnd}
        AND IFNULL(entry_date, DATE(create_time)) BETWEEN #{monthStart} AND #{monthEnd}
    </select>
</mapper>
src/main/resources/mapper/stock/StockInRecordMapper.xml
@@ -33,6 +33,9 @@
            <if test="params.stockLocation != null and params.stockLocation != ''">
                and sir.stock_location = #{params.stockLocation}
            </if>
            <if test="params.model != null and params.model != ''">
                and pm.model like concat('%',#{params.model},'%')
            </if>
        </where>
        order by sir.id desc
    </select>
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -84,6 +84,9 @@
        <if test="ew.stockLocation != null and ew.stockLocation != ''">
            and si.stock_location = #{ew.stockLocation}
        </if>
        <if test="ew.model != null and ew.model !=''">
            and pm.model like concat('%',#{ew.model},'%')
        </if>
    </select>
    <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
        select si.qualitity,
src/main/resources/mapper/stock/StockOutRecordMapper.xml
@@ -45,6 +45,9 @@
            <if test="params.stockLocation != null and params.stockLocation != ''">
                and sor.stock_location = #{params.stockLocation}
            </if>
        <if test="params.model != null and params.model != ''">
            and pm.model like concat('%',#{params.model},'%')
        </if>
        </where>
        order by sor.id desc
    </select>