huminmin
6 天以前 3650353733971d879fce9c1cf27ff77c7e1ae77a
Merge branch 'dev_New_pro' of http://114.132.189.42:9002/r/product-inventory-management-after into dev_New_pro
已添加2个文件
已修改14个文件
924 ■■■■ 文件已修改
.gitignore 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260515_设备巡检异常联动维修单_前端联调文档.md 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInventoryService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 324 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -4,7 +4,7 @@
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
claude.md
target/
!.mvn/wrapper/maven-wrapper.jar
@@ -43,4 +43,4 @@
!*/build/*.java
!*/build/*.html
!*/build/*.xml
!*/build/*.xml
doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
ALTER TABLE `inspection_task`
    ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT '巡检项目' AFTER `task_name`;
ALTER TABLE `inspection_task`
    ADD COLUMN `inspection_result` VARCHAR(1) NULL COMMENT '巡检结果 0异常 1正常' AFTER `remarks`,
    ADD COLUMN `abnormal_description` VARCHAR(500) NULL COMMENT '异常描述' AFTER `inspection_result`,
    ADD COLUMN `device_repair_id` BIGINT NULL COMMENT '关联维修单ID' AFTER `abnormal_description`,
    ADD COLUMN `acceptance_user_id` BIGINT NULL COMMENT '验收人ID' AFTER `device_repair_id`,
    ADD COLUMN `acceptance_name` VARCHAR(100) NULL COMMENT '验收人' AFTER `acceptance_user_id`;
ALTER TABLE `timing_task`
    ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT '巡检项目' AFTER `task_name`,
    ADD COLUMN `is_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用 0否 1是' AFTER `is_active`;
CREATE INDEX `idx_inspection_task_device_repair_id`
    ON `inspection_task` (`device_repair_id`);
CREATE INDEX `idx_inspection_task_inspection_result`
    ON `inspection_task` (`inspection_result`);
CREATE INDEX `idx_timing_task_is_enabled`
    ON `timing_task` (`is_enabled`);
doc/20260515_É豸Ѳ¼ìÒì³£Áª¶¯Î¬ÐÞµ¥_ǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
# è®¾å¤‡å·¡æ£€ä¸Žå®šæ—¶å·¡æ£€å‰ç«¯è”调文档(inspectiontask)
> æ›´æ–°æ—¥æœŸï¼š2026-05-15
> é€‚用模块:设备巡检任务 `inspectiontask`(`/inspectionTask`)与定时巡检任务(`/timingTask`)
## 1. æœ¬æ¬¡æ”¹åЍ
1. å·¡æ£€ä»»åŠ¡æ–°å¢žå­—æ®µï¼š
   - `inspectionProject`(巡检项目)
   - `inspectionResult`(巡检结果,`0`异常 / `1`正常,必填)
   - `abnormalDescription`(异常描述)
   - `deviceRepairId`(关联维修单ID,异常时后端自动回填)
   - `acceptanceUserId`(验收人ID)
   - `acceptanceName`(验收人)
2. å¼‚常校验规则:
   - `inspectionResult=1`(正常):照片非必填。
   - `inspectionResult=0`(异常):必须有照片,且必须填写 `abnormalDescription`。
3. å¼‚常联动规则:
   - å¼‚常保存后自动生成 `device_repair` å¹¶å›žå¡« `deviceRepairId`。
4. å®šæ—¶ä»»åŠ¡æ–°å¢žå­—æ®µï¼š
   - `inspectionProject`(巡检项目)
   - `isEnabled`(是否启用,`0`否 / `1`是)
5. å¤‡æ³¨å¸¦å…¥è§„则:
   - å®šæ—¶ä»»åŠ¡è‡ªåŠ¨ç”Ÿæˆå·¡æ£€è®°å½•æ—¶ï¼Œè‹¥å®šæ—¶ä»»åŠ¡ `remarks` æœ‰å€¼ï¼Œä¼šæ‹¼æŽ¥åˆ°å·¡æ£€è®°å½•备注中。
## 2. æ•°æ®åº“变更
联调前执行 SQL:
- [doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql](/D:/牛马/南通/后端/product-inventory-management-after-jdk25/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql)
> è¯´æ˜Žï¼šè¯¥è„šæœ¬å½“å‰ä½œç”¨äºŽ `inspection_task` ä¸Ž `timing_task` ä¸¤å¼ è¡¨ï¼Œæ–‡ä»¶ååŽ†å²ä¿ç•™æœªæ”¹ã€‚
## 3. å·¡æ£€ä»»åŠ¡æŽ¥å£
### 3.1 ä¿å­˜æŽ¥å£
`POST /inspectionTask/addOrEditInspectionTask`
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| id | long | å¦ | æœ‰å€¼=修改,无值=新增 |
| taskId | int | å»ºè®®å¿…å¡« | è®¾å¤‡ID(用于异常自动建维修单) |
| taskName | string | å»ºè®®å¿…å¡« | è®¾å¤‡åç§° |
| inspectionProject | string | å¦ | å·¡æ£€é¡¹ç›® |
| inspectorId | string | å¦ | å·¡æ£€äººID,支持逗号分隔 |
| inspectionResult | string | æ˜¯ | `0`=异常,`1`=正常 |
| abnormalDescription | string | æ¡ä»¶å¿…å¡« | å¼‚常时必填 |
| acceptanceUserId | long | å¦ | éªŒæ”¶äººID |
| acceptanceName | string | å¦ | éªŒæ”¶äººå§“名 |
| commonFileListDTO | array | æ¡ä»¶å¿…å¡« | é™„件组1(异常时三组至少一组有图) |
| commonFileListAfterDTO | array | æ¡ä»¶å¿…å¡« | é™„件组2(异常时三组至少一组有图) |
| commonFileListBeforeDTO | array | æ¡ä»¶å¿…å¡« | é™„件组3(异常时三组至少一组有图) |
异常示例:
```json
{
  "taskId": 1001,
  "taskName": "空压机A-01",
  "inspectionProject": "润滑系统",
  "inspectorId": "12",
  "inspectionResult": "0",
  "abnormalDescription": "电机异响,温升偏高",
  "acceptanceUserId": 20,
  "commonFileListDTO": [
    {
      "id": 90001,
      "application": "file"
    }
  ]
}
```
正常示例:
```json
{
  "taskId": 1001,
  "taskName": "空压机A-01",
  "inspectionProject": "点检",
  "inspectorId": "12",
  "inspectionResult": "1",
  "acceptanceUserId": 20
}
```
### 3.2 åˆ—表接口
`GET /inspectionTask/list`
返回包含新增字段:
- `inspectionProject`
- `inspectionResult`
- `abnormalDescription`
- `deviceRepairId`
- `acceptanceUserId`
- `acceptanceName`
## 4. å®šæ—¶ä»»åŠ¡æŽ¥å£
### 4.1 ä¿å­˜æŽ¥å£
`POST /timingTask/addOrEditTimingTask`
新增/更新字段:
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| inspectionProject | string | å¦ | å·¡æ£€é¡¹ç›® |
| remarks | string | å¦ | å¤‡æ³¨ |
| isEnabled | int | å¦ | `0`=禁用,`1`=启用;不传默认启用 |
示例:
```json
{
  "taskName": "空压机A-01定时巡检",
  "inspectionProject": "月度巡检",
  "taskId": 1001,
  "inspectorIds": "12,13",
  "frequencyType": "DAILY",
  "frequencyDetail": "09:00",
  "remarks": "重点检查轴承温度",
  "isEnabled": 1
}
```
### 4.2 å¯ç”¨çŠ¶æ€è¡Œä¸º
1. `isEnabled=1`:任务进入调度,按频次自动生成巡检记录。
2. `isEnabled=0`:任务不调度;已存在调度会被移除。
### 4.3 å¤‡æ³¨å¸¦å…¥è§„则
定时任务自动生成巡检记录时:
1. å·¡æ£€è®°å½•备注固定前缀:`自动生成自定时任务ID: {id}`
2. å½“定时任务 `remarks` éžç©ºæ—¶ï¼Œæ‹¼æŽ¥ä¸ºï¼š
   `自动生成自定时任务ID: {id};{remarks}`
## 5. å¼‚常自动建维修单规则
当巡检记录 `inspectionResult=0` æ—¶ï¼š
1. è‹¥ `deviceRepairId` ä¸ºç©ºï¼ŒåŽç«¯è‡ªåŠ¨åˆ›å»º `device_repair`:
   - `deviceLedgerId`:来自 `taskId`
   - `deviceName`:优先 `taskName`,否则取设备台账名称
   - `remark`:异常描述
   - `status`:`0`(待维修)
2. è‹¥å·²æœ‰å…³è”维修单,仅同步更新维修单 `remark`。
## 6. å‰ç«¯æ”¹é€ å»ºè®®
1. å·¡æ£€è¡¨å•新增 `inspectionProject` è¾“入框。
2. å·¡æ£€è¡¨å•保留“正常/异常”联动校验:
   - å¼‚常时强制异常描述 + è‡³å°‘一组图片。
3. å®šæ—¶ä»»åŠ¡è¡¨å•æ–°å¢žâ€œæ˜¯å¦å¯ç”¨â€å¼€å…³å¹¶æ˜ å°„ `isEnabled`。
4. å®šæ—¶ä»»åŠ¡è¡¨å•æ–°å¢ž `inspectionProject` ä¸Ž `remarks` è¾“入项。
5. å·¡æ£€åˆ—表展示 `inspectionProject` å’Œ `deviceRepairId`(支持跳转维修单详情)。
## 7. è”调验收清单
1. å·¡æ£€æ–°å¢ž/修改可正确提交 `inspectionProject` å¹¶åœ¨åˆ—表回显。
2. å¼‚常巡检(有描述+有图)保存成功并回填 `deviceRepairId`。
3. å¼‚常巡检缺描述或缺图片时被拦截。
4. å®šæ—¶ä»»åŠ¡ `isEnabled=0` æ—¶ä¸å†è§¦å‘自动巡检记录。
5. å®šæ—¶ä»»åŠ¡ `isEnabled=1` æ—¶æŒ‰é¢‘次生成巡检记录。
6. å®šæ—¶ä»»åŠ¡æœ‰ `remarks` æ—¶ï¼Œè‡ªåŠ¨å·¡æ£€è®°å½•å¤‡æ³¨å¸¦ä¸Šè¯¥å†…å®¹ã€‚
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -30,6 +30,10 @@
    @Excel(name = "巡检任务名称")
    private String taskName;
    @Schema(description = "巡检项目")
    @Excel(name = "巡检项目")
    private String inspectionProject;
    @Schema(description = "设备id")
    private Integer taskId;
@@ -44,6 +48,22 @@
    @Excel(name = "备注")
    private String remarks;
    @Schema(description = "巡检结果 0 å¼‚常 1 æ­£å¸¸")
    private String inspectionResult;
    @Schema(description = "异常描述")
    private String abnormalDescription;
    @Schema(description = "关联维修单ID")
    private Long deviceRepairId;
    @Schema(description = "验收人ID")
    private Long acceptanceUserId;
    @Schema(description = "验收人")
    @Excel(name = "验收人")
    private String acceptanceName;
    @Schema(description = "任务登记人ID")
    private Long registrantId;
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -33,6 +33,10 @@
    @Excel(name = "巡检任务名称")
    private String taskName;
    @Schema(description = "巡检项目")
    @Excel(name = "巡检项目")
    private String inspectionProject;
    @Schema(description = "设备id")
    private Integer taskId;
@@ -60,6 +64,10 @@
    @Schema(description = "是否激活")
    private boolean isActive;
    @Schema(description = "是否启用 0否 1是")
    @Excel(name = "是否启用", readConverterExp = "0=否,1=是")
    private Integer isEnabled;
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remarks;
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -5,12 +5,17 @@
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
@@ -23,6 +28,7 @@
import org.springframework.transaction.annotation.Transactional;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -43,6 +49,14 @@
    private final FileUtil fileUtil;
    private final DeviceRepairMapper deviceRepairMapper;
    private final DeviceLedgerMapper deviceLedgerMapper;
    private static final String INSPECTION_RESULT_ABNORMAL = "0";
    private static final String INSPECTION_RESULT_NORMAL = "1";
    private static final int REPAIR_STATUS_PENDING = 0;
    @Override
    public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
        LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
@@ -50,14 +64,15 @@
        if (StringUtils.isNotBlank(inspectionTaskDto.getTaskName())) {
            queryWrapper.like(InspectionTask::getTaskName, inspectionTaskDto.getTaskName());
        }
        if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
            queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
        }
        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
        //  æ— æ•°æ®æå‰è¿”回
        if (CollectionUtils.isEmpty(entityPage.getRecords())) {
            return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
        }
        // èŽ·å–id集合
        List<Long> ids = entityPage.getRecords().stream().map(InspectionTask::getId).collect(Collectors.toList());
        //登记人ids
        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
        // æ‰¹é‡æŸ¥è¯¢ç™»è®°äºº
@@ -68,9 +83,6 @@
        } else {
            sysUserMap = new HashMap<>();
        }
        //巡检人ids
        List<String> inspectorIds = entityPage.getRecords().stream().map(InspectionTask::getInspectorId).collect(Collectors.toList());
        //获取所有不重复的用户ID
        Set<Long> allUserIds = entityPage.getRecords().stream()
                .map(InspectionTask::getInspectorId) // èŽ·å–"2,3"这样的字符串
@@ -140,24 +152,230 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto) {
        InspectionTask oldInspectionTask = null;
        if (Objects.nonNull(inspectionTaskDto.getId())) {
            oldInspectionTask = inspectionTaskMapper.selectById(inspectionTaskDto.getId());
            if (oldInspectionTask == null) {
                throw new IllegalArgumentException("巡检任务不存在");
            }
        }
        validateInspectionInput(inspectionTaskDto, oldInspectionTask);
        InspectionTask inspectionTask = new InspectionTask();
        BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
        inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
        inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
        fillAcceptanceInfo(inspectionTask, oldInspectionTask);
        int i;
        if (Objects.isNull(inspectionTaskDto.getId())) {
            i = inspectionTaskMapper.insert(inspectionTask);
        } else {
            i = inspectionTaskMapper.updateById(inspectionTask);
        }
        // ä¿å­˜æ–‡ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListDTO());
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListAfterDTO());
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListBeforeDTO());
        if (i <= 0) {
            return i;
        }
        Long linkedRepairId = syncRepairOrderIfAbnormal(inspectionTask, oldInspectionTask);
        if (linkedRepairId != null && !Objects.equals(linkedRepairId, inspectionTask.getDeviceRepairId())) {
            InspectionTask relationUpdate = new InspectionTask();
            relationUpdate.setId(inspectionTask.getId());
            relationUpdate.setDeviceRepairId(linkedRepairId);
            inspectionTaskMapper.updateById(relationUpdate);
            inspectionTask.setDeviceRepairId(linkedRepairId);
        }
        // ä¿å­˜æ–‡ä»¶ï¼ˆå­—段不传则保留历史)
        saveInspectionAttachments(inspectionTask.getId(), inspectionTaskDto);
        return i;
    }
    private void validateInspectionInput(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
        String inspectionResult = inspectionTaskDto.getInspectionResult();
        if (StringUtils.isBlank(inspectionResult) && oldInspectionTask != null) {
            inspectionResult = oldInspectionTask.getInspectionResult();
        }
        if (StringUtils.isBlank(inspectionResult)) {
            throw new IllegalArgumentException("请选择巡检结果");
        }
        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionResult) && !INSPECTION_RESULT_NORMAL.equals(inspectionResult)) {
            throw new IllegalArgumentException("巡检结果仅支持:0-异常,1-正常");
        }
        inspectionTaskDto.setInspectionResult(inspectionResult);
        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionResult)) {
            return;
        }
        String abnormalDescription = inspectionTaskDto.getAbnormalDescription();
        if (StringUtils.isBlank(abnormalDescription) && oldInspectionTask != null) {
            abnormalDescription = oldInspectionTask.getAbnormalDescription();
        }
        if (StringUtils.isBlank(abnormalDescription)) {
            throw new IllegalArgumentException("巡检结果为异常时,异常描述不能为空");
        }
        inspectionTaskDto.setAbnormalDescription(abnormalDescription);
        if (!hasAnyInspectionPhotoAfterSave(inspectionTaskDto, oldInspectionTask)) {
            throw new IllegalArgumentException("巡检结果为异常时,必须上传至少一张照片");
        }
    }
    private boolean hasAnyInspectionPhotoAfterSave(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
        Long recordId = oldInspectionTask == null ? null : oldInspectionTask.getId();
        return hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListDTO(), ApplicationTypeEnum.FILE, recordId)
                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListAfterDTO(), ApplicationTypeEnum.AFTER_FILE, recordId)
                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListBeforeDTO(), ApplicationTypeEnum.BEFORE_FILE, recordId);
    }
    private boolean hasApplicationPhotoAfterSave(List<StorageBlobDTO> requestPhotos, ApplicationTypeEnum applicationType, Long recordId) {
        if (requestPhotos != null) {
            return !requestPhotos.isEmpty();
        }
        if (recordId == null) {
            return false;
        }
        return CollectionUtils.isNotEmpty(fileUtil.getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(applicationType, RecordTypeEnum.INSPECTION_TASK, recordId));
    }
    private void fillAcceptanceInfo(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
        Long acceptanceUserId = inspectionTask.getAcceptanceUserId();
        if (acceptanceUserId == null && oldInspectionTask != null) {
            acceptanceUserId = oldInspectionTask.getAcceptanceUserId();
        }
        if (acceptanceUserId != null) {
            inspectionTask.setAcceptanceUserId(acceptanceUserId);
        }
        String acceptanceName = inspectionTask.getAcceptanceName();
        if (StringUtils.isBlank(acceptanceName) && acceptanceUserId != null) {
            SysUser acceptanceUser = sysUserMapper.selectUserById(acceptanceUserId);
            if (acceptanceUser != null) {
                acceptanceName = acceptanceUser.getNickName();
            }
        }
        if (StringUtils.isBlank(acceptanceName) && oldInspectionTask != null) {
            acceptanceName = oldInspectionTask.getAcceptanceName();
        }
        inspectionTask.setAcceptanceName(acceptanceName);
    }
    private Long syncRepairOrderIfAbnormal(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionTask.getInspectionResult())) {
            return inspectionTask.getDeviceRepairId();
        }
        Long linkedRepairId = inspectionTask.getDeviceRepairId();
        if (linkedRepairId == null && oldInspectionTask != null) {
            linkedRepairId = oldInspectionTask.getDeviceRepairId();
        }
        if (linkedRepairId != null) {
            DeviceRepair updateRepair = new DeviceRepair();
            updateRepair.setId(linkedRepairId);
            updateRepair.setRemark(inspectionTask.getAbnormalDescription());
            deviceRepairMapper.updateById(updateRepair);
            return linkedRepairId;
        }
        DeviceRepair deviceRepair = buildDeviceRepair(inspectionTask, oldInspectionTask);
        deviceRepairMapper.insert(deviceRepair);
        return deviceRepair.getId();
    }
    private DeviceRepair buildDeviceRepair(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
        DeviceRepair deviceRepair = new DeviceRepair();
        Long deviceLedgerId = resolveDeviceLedgerId(inspectionTask, oldInspectionTask);
        DeviceLedger deviceLedger = resolveDeviceLedger(deviceLedgerId);
        deviceRepair.setDeviceLedgerId(deviceLedgerId);
        deviceRepair.setDeviceName(resolveDeviceName(inspectionTask, oldInspectionTask, deviceLedger));
        deviceRepair.setDeviceModel(deviceLedger == null ? null : deviceLedger.getDeviceModel());
        deviceRepair.setRepairName(resolveRepairReporter(inspectionTask, oldInspectionTask));
        deviceRepair.setRepairTime(new Date());
        deviceRepair.setRemark(inspectionTask.getAbnormalDescription());
        deviceRepair.setMachineryCategory(deviceLedger == null ? null : deviceLedger.getType());
        deviceRepair.setStatus(REPAIR_STATUS_PENDING);
        return deviceRepair;
    }
    private Long resolveDeviceLedgerId(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
        Integer taskId = inspectionTask.getTaskId();
        if (taskId == null && oldInspectionTask != null) {
            taskId = oldInspectionTask.getTaskId();
        }
        if (taskId == null || taskId <= 0) {
            return null;
        }
        return taskId.longValue();
    }
    private DeviceLedger resolveDeviceLedger(Long deviceLedgerId) {
        if (deviceLedgerId == null) {
            return null;
        }
        return deviceLedgerMapper.selectById(deviceLedgerId);
    }
    private String resolveDeviceName(InspectionTask inspectionTask, InspectionTask oldInspectionTask, DeviceLedger deviceLedger) {
        String taskName = inspectionTask.getTaskName();
        if (StringUtils.isBlank(taskName) && oldInspectionTask != null) {
            taskName = oldInspectionTask.getTaskName();
        }
        if (StringUtils.isNotBlank(taskName)) {
            return taskName;
        }
        return deviceLedger == null ? null : deviceLedger.getDeviceName();
    }
    private String resolveRepairReporter(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
        String reporter = inspectionTask.getInspector();
        if (StringUtils.isBlank(reporter) && oldInspectionTask != null) {
            reporter = oldInspectionTask.getInspector();
        }
        if (StringUtils.isNotBlank(reporter)) {
            return reporter;
        }
        String inspectorNameByUserId = resolveInspectorNameByUserId(inspectionTask.getInspectorId());
        if (StringUtils.isBlank(inspectorNameByUserId) && oldInspectionTask != null) {
            inspectorNameByUserId = resolveInspectorNameByUserId(oldInspectionTask.getInspectorId());
        }
        if (StringUtils.isNotBlank(inspectorNameByUserId)) {
            return inspectorNameByUserId;
        }
        try {
            return SecurityUtils.getUsername();
        } catch (Exception ignored) {
            return "system";
        }
    }
    private String resolveInspectorNameByUserId(String inspectorIds) {
        if (StringUtils.isBlank(inspectorIds)) {
            return null;
        }
        String firstInspectorId = Arrays.stream(inspectorIds.split(","))
                .map(String::trim)
                .filter(StringUtils::isNotBlank)
                .findFirst()
                .orElse(null);
        if (!StringUtils.isNumeric(firstInspectorId)) {
            return null;
        }
        SysUser sysUser = sysUserMapper.selectUserById(Long.parseLong(firstInspectorId));
        return sysUser == null ? null : sysUser.getNickName();
    }
    private void saveInspectionAttachments(Long inspectionTaskId, InspectionTaskDto inspectionTaskDto) {
        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.FILE, inspectionTaskDto.getCommonFileListDTO());
        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.AFTER_FILE, inspectionTaskDto.getCommonFileListAfterDTO());
        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.BEFORE_FILE, inspectionTaskDto.getCommonFileListBeforeDTO());
    }
    private void saveAttachmentIfPresent(Long inspectionTaskId, ApplicationTypeEnum applicationTypeEnum, List<StorageBlobDTO> storageBlobDTOS) {
        if (storageBlobDTOS == null) {
            return;
        }
        fileUtil.saveStorageAttachment(applicationTypeEnum, RecordTypeEnum.INSPECTION_TASK, inspectionTaskId, storageBlobDTOS);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delByIds(Long[] ids) {
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -3,7 +3,7 @@
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import lombok.RequiredArgsConstructor;
import com.ruoyi.common.utils.StringUtils;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
@@ -47,6 +47,9 @@
            TimingTask timingTask = tasks.isEmpty() ? null : tasks.get(0);
            if (timingTask == null) {
                throw new JobExecutionException("找不到定时任务: " + taskId);
            }
            if (timingTask.getIsEnabled() != null && timingTask.getIsEnabled() == 0) {
                return;
            }
//            if (!timingTask.isActive()) {
@@ -100,10 +103,15 @@
        // å¤åˆ¶åŸºæœ¬å±žæ€§
        inspectionTask.setTaskName(timingTask.getTaskName());
        inspectionTask.setInspectionProject(timingTask.getInspectionProject());
        inspectionTask.setTaskId(timingTask.getTaskId());
        inspectionTask.setInspectorId(timingTask.getInspectorIds());
        inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
        inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId());
        String remarks = "自动生成自定时任务ID: " + timingTask.getId();
        if (StringUtils.isNotBlank(timingTask.getRemarks())) {
            remarks = remarks + ";" + timingTask.getRemarks();
        }
        inspectionTask.setRemarks(remarks);
        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -35,6 +35,8 @@
    private final TimingTaskMapper timingTaskMapper;
    private final TimingTaskScheduler timingTaskScheduler;
    private final SysUserMapper sysUserMapper;
    private static final int ENABLED = 1;
    private static final int DISABLED = 0;
    @Override
@@ -44,6 +46,12 @@
        LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(timingTask.getTaskName())) {
            queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName());
        }
        if (StringUtils.isNotBlank(timingTask.getInspectionProject())) {
            queryWrapper.like(TimingTask::getInspectionProject, timingTask.getInspectionProject());
        }
        if (timingTask.getIsEnabled() != null) {
            queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
        }
        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
@@ -115,8 +123,17 @@
    @Override
    @Transactional
    public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
        TimingTask oldTimingTask = null;
        if (Objects.nonNull(timingTaskDto.getId())) {
            oldTimingTask = timingTaskMapper.selectById(timingTaskDto.getId());
            if (oldTimingTask == null) {
                throw new IllegalArgumentException("定时任务不存在");
            }
        }
        TimingTask timingTask = new TimingTask();
        BeanUtils.copyProperties(timingTaskDto, timingTask);
        timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask));
        timingTask.setActive(ENABLED == timingTask.getIsEnabled());
        // 1. è§£æžå­—符串为 LocalDate(只包含年月日)
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate localDate = LocalDate.now();
@@ -132,13 +149,12 @@
        // è®¾ç½®åˆ›å»ºäººä¿¡æ¯å’Œé»˜è®¤å€¼
        if (Objects.isNull(timingTaskDto.getId())) {
            timingTask.setRegistrationDate(LocalDate.now());
            timingTask.setActive(true);
            // è®¡ç®—首次执行时间
            LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
            timingTask.setNextExecutionTime(firstExecutionTime);
            int result = timingTaskMapper.insert(timingTask);
            if (result > 0) {
            if (result > 0 && isEnabled(timingTask.getIsEnabled(), timingTask.isActive())) {
                // æ–°å¢žæˆåŠŸåŽæ·»åŠ åˆ°è°ƒåº¦å™¨
                timingTaskScheduler.scheduleTimingTask(timingTask);
            }
@@ -148,8 +164,17 @@
            int result = timingTaskMapper.updateById(timingTask);
            if (result > 0) {
                // æ›´æ–°æˆåŠŸåŽé‡æ–°è°ƒåº¦ä»»åŠ¡
                timingTaskScheduler.rescheduleTimingTask(timingTask);
                boolean oldEnabled = isEnabled(oldTimingTask == null ? null : oldTimingTask.getIsEnabled(), oldTimingTask != null && oldTimingTask.isActive());
                boolean newEnabled = isEnabled(timingTask.getIsEnabled(), timingTask.isActive());
                if (!newEnabled) {
                    timingTaskScheduler.unscheduleTimingTask(timingTask.getId());
                } else if (oldEnabled) {
                    // æ›´æ–°æˆåŠŸåŽé‡æ–°è°ƒåº¦ä»»åŠ¡
                    timingTaskScheduler.rescheduleTimingTask(timingTask);
                } else {
                    // ä»Žç¦ç”¨æ”¹ä¸ºå¯ç”¨æ—¶é‡æ–°åˆ›å»ºè°ƒåº¦ä»»åŠ¡
                    timingTaskScheduler.scheduleTimingTask(timingTask);
                }
            }
            return result;
        }
@@ -451,6 +476,26 @@
        return days;
    }
    private Integer resolveEnabledValue(Integer requestEnabled, TimingTask oldTimingTask) {
        if (requestEnabled != null) {
            return requestEnabled;
        }
        if (oldTimingTask != null) {
            if (oldTimingTask.getIsEnabled() != null) {
                return oldTimingTask.getIsEnabled();
            }
            return oldTimingTask.isActive() ? ENABLED : DISABLED;
        }
        return ENABLED;
    }
    private boolean isEnabled(Integer enabledValue, boolean activeFallback) {
        if (enabledValue != null) {
            return ENABLED == enabledValue;
        }
        return activeFallback;
    }
    @Override
src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java
@@ -5,6 +5,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@@ -16,7 +17,7 @@
 */
@Data
@TableName("product_record")
public class ProductRecord {
public class ProductRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -51,6 +51,19 @@
        return R.ok(stockInventoryDtoIPage);
    }
    /**
     * æŸ¥è¯¢å¯¹åº”批号和数量
     * @param page
     * @param stockInventoryDto
     * @return
     */
    @GetMapping("/getBatchNoQty")
    @Operation(summary = "查询对应批号和数量")
    public R getBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
        IPage<StockInventoryDto> stockInventoryDtoIPage = stockInventoryService.getBatchNoQty(page, stockInventoryDto);
        return R.ok(stockInventoryDtoIPage);
    }
    @PostMapping("/addstockInventory")
    @Operation(summary = "新增库存")
    public R addstockInventory(@RequestBody StockInventoryDto stockInventoryDto) {
@@ -85,7 +98,7 @@
    }
    @PostMapping("importStockInventory")
    @PostMapping("/importStockInventory")
    @Operation(summary = "导入库存")
    public R importStockInventory(MultipartFile file) {
        return stockInventoryService.importStockInventory(file);
@@ -105,13 +118,13 @@
        stockInventoryService.exportStockInventory(response, stockInventoryDto);
    }
    @GetMapping("stockInventoryPage")
    @GetMapping("/stockInventoryPage")
    @Operation(summary = "库存报表查询")
    public R stockInventoryPage(Page page, StockInventoryDto stockInventoryDto) {
        return R.ok(stockInventoryService.stockInventoryPage(stockInventoryDto,page));
    }
    @GetMapping("stockInAndOutRecord")
    @GetMapping("/stockInAndOutRecord")
    @Operation(summary = "统计各个产品的入库和出库记录")
    public R stockInAndOutRecord(StockInventoryDto stockInventoryDto,Page page) {
        return R.ok(stockInventoryService.stockInAndOutRecord(stockInventoryDto,page));
@@ -128,7 +141,6 @@
    public R thawStock(@RequestBody StockInventoryDto stockInventoryDto) {
        return R.ok(stockInventoryService.thawStock(stockInventoryDto));
    }
    @GetMapping("/getByModelId")
    @Operation(summary = "根据产品规格ID获取入库记录")
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
@@ -76,4 +76,7 @@
    @Schema(description = "不合格库存ID")
    private Long unQualifiedId;
    @Schema(description = "产品id")
    private Long productId;
}
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -56,4 +56,6 @@
    List<StockInventory> listSelectableBatchNoByProductModelIds(@Param("productModelIds") List<Long> productModelIds);
    List<StockInventory> getByModelId(@Param("productModelId") Long productModelId);
    IPage<StockInventoryDto> getBatchNoQty(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
}
src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -47,4 +47,6 @@
    Boolean thawStock(StockInventoryDto stockInventoryDto);
    List<StockInventory> getByModelId(Long modelId);
    IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto);
}
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -29,7 +29,7 @@
import com.ruoyi.stock.service.StockOutRecordService;
import com.ruoyi.stock.service.StockUninventoryService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@@ -51,15 +51,16 @@
 * @since 2026-01-21 04:16:36
 */
@Service
@AllArgsConstructor
@RequiredArgsConstructor
public class StockInventoryServiceImpl extends ServiceImpl<StockInventoryMapper, StockInventory> implements StockInventoryService {
    private  StockInventoryMapper stockInventoryMapper;
    private StockInRecordService stockInRecordService;
    private StockOutRecordService stockOutRecordService;
    private StockUninventoryService stockUninventoryService;
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private ProductModelMapper productModelMapper;
    private final StockInventoryMapper stockInventoryMapper;
    private final StockInRecordService stockInRecordService;
    private final StockOutRecordService stockOutRecordService;
    private final StockUninventoryService stockUninventoryService;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final ProductModelMapper productModelMapper;
    @Override
    public IPage<StockInventoryDto> pagestockInventory(Page page, StockInventoryDto stockInventoryDto) {
        return stockInventoryMapper.pagestockInventory(page, stockInventoryDto);
@@ -105,8 +106,8 @@
            newStockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
            newStockInventory.setWarnNum(stockInventoryDto.getWarnNum());
            stockInventoryMapper.insert(newStockInventory);
        }else {
             stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
        } else {
            stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
        }
        return true;
    }
@@ -116,7 +117,7 @@
    @Transactional(rollbackFor = Exception.class)
    public Boolean subtractStockInventory(StockInventoryDto stockInventoryDto) {
        LambdaQueryWrapper<StockInventory> eq = new QueryWrapper<StockInventory>().lambda()
            .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
                .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
        if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) {
            eq.isNull(StockInventory::getBatchNo);
            stockInventoryDto.setBatchNo(null);
@@ -336,7 +337,7 @@
                        }
                        stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                        this.addstockInventory(stockInventoryDto);
                        this.addStockInRecordOnly(stockInventoryDto);
                        successCount++;
                    }
@@ -392,28 +393,28 @@
        List<StockInventoryExportData> list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto);
        ExcelUtil<StockInventoryExportData> util = new ExcelUtil<>(StockInventoryExportData.class);
        util.exportExcel(response,list, "库存信息");
        util.exportExcel(response, list, "库存信息");
    }
    @Override
    public IPage<StockInRecordDto> stockInventoryPage(StockInventoryDto stockInventoryDto, Page page) {
        return stockInventoryMapper.stockInventoryPage(stockInventoryDto,page);
        return stockInventoryMapper.stockInventoryPage(stockInventoryDto, page);
    }
    @Override
    public IPage<StockInventoryDto> stockInAndOutRecord(StockInventoryDto stockInventoryDto, Page page) {
        return stockInventoryMapper.stockInAndOutRecord(stockInventoryDto,page);
        return stockInventoryMapper.stockInAndOutRecord(stockInventoryDto, page);
    }
    @Override
    public Boolean frozenStock(StockInventoryDto stockInventoryDto) {
        StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
        if (stockInventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
        if (stockInventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
            throw new RuntimeException("冻结数量不能超过库存数量");
        }
        if (ObjectUtils.isEmpty(stockInventory.getLockedQuantity())) {
            stockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
        }else {
        } else {
            stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().add(stockInventoryDto.getLockedQuantity()));
        }
        return this.updateById(stockInventory);
@@ -422,7 +423,7 @@
    @Override
    public Boolean thawStock(StockInventoryDto stockInventoryDto) {
        StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
            throw new RuntimeException("解冻数量不能超过冻结数量");
        }
        stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
@@ -433,4 +434,9 @@
    public List<StockInventory> getByModelId(Long modelId) {
        return stockInventoryMapper.getByModelId(modelId);
    }
    @Override
    public IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
        return stockInventoryMapper.getBatchNoQty(page, stockInventoryDto);
    }
}
src/main/resources/mapper/basic/CustomerMapper.xml
@@ -26,6 +26,7 @@
        from customer c
        left join sys_user u on c.usage_user = u.user_id
        <where>
            and c.usage_status = 1
            <if test="c.customerName != null and c.customerName != ''">
                and customer_name like concat('%', #{c.customerName}, '%')
            </if>
@@ -107,4 +108,4 @@
            </if>
        </where>
    </select>
</mapper>
</mapper>
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -99,96 +99,102 @@
        INNER JOIN product_tree pt ON p.parent_id = pt.id
        )
        select
            batch_no,
            MAX(qualifiedId) as qualifiedId,
            MAX(unQualifiedId) as unQualifiedId,
            SUM(qualifiedQuantity) as qualifiedQuantity,
            SUM(unQualifiedQuantity) as unQualifiedQuantity,
            SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
            SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
            SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
            SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
            SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
            SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
            product_model_id,
            MAX(create_time) as create_time,
            MAX(update_time) as update_time,
            MAX(warn_num) as warn_num,
            MAX(version) as version,
            model,
            MAX(remark) as remark,
            unit,
            product_name,
            product_id,
            'combined' as stockType
        GROUP_CONCAT(DISTINCT batch_no ORDER BY batch_no SEPARATOR ',') as batch_no,
        MAX(qualifiedId) as qualifiedId,
        MAX(unQualifiedId) as unQualifiedId,
        SUM(qualifiedQuantity) as qualifiedQuantity,
        SUM(unQualifiedQuantity) as unQualifiedQuantity,
        SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
        SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
        SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
        SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
        SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
        SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
        product_model_id,
        MAX(create_time) as create_time,
        MAX(update_time) as update_time,
        MAX(warn_num) as warn_num,
        MAX(version) as version,
        model,
        MAX(remark) as remark,
        unit,
        product_name,
        product_id,
        'combined' as stockType
        from (
            select
            si.batch_no,
            si.id as qualifiedId,
            null as unQualifiedId,
            si.qualitity as qualifiedQuantity,
            0 as unQualifiedQuantity,
            COALESCE(si.locked_quantity, 0) as locked_quantity,
            COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
            0 as unQualifiedLockedQuantity,
            si.product_model_id,
            si.create_time,
            si.update_time,
            COALESCE(si.warn_num, 0) as warn_num,
            si.version,
            (si.qualitity - COALESCE(si.locked_quantity, 0)) as un_locked_quantity,
            pm.model,
            si.remark,
            pm.unit,
            p.product_name,
            p.id as product_id,
            (
                select IFNULL(SUM(sor.stock_out_num), 0)
                from stock_out_record sor
                where sor.product_model_id = si.product_model_id
                  and (si.batch_no is null and sor.batch_no is null or si.batch_no = sor.batch_no)
                  and sor.type = '0'
                  and sor.approval_status = 0
            ) as qualifiedPendingOut,
            0 as unqualifiedPendingOut
            from stock_inventory si
            left join product_model pm on si.product_model_id = pm.id
            left join product p on pm.product_id = p.id
        select
        si.batch_no,
        si.id as qualifiedId,
        null as unQualifiedId,
        si.qualitity as qualifiedQuantity,
        0 as unQualifiedQuantity,
        COALESCE(si.locked_quantity, 0) as locked_quantity,
        COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
        0 as unQualifiedLockedQuantity,
        si.product_model_id,
        si.create_time,
        si.update_time,
        COALESCE(si.warn_num, 0) as warn_num,
        si.version,
        (si.qualitity - COALESCE(si.locked_quantity, 0)) as un_locked_quantity,
        pm.model,
        si.remark,
        pm.unit,
        p.product_name,
        p.id as product_id,
        (
        select IFNULL(SUM(sor.stock_out_num), 0)
        from stock_out_record sor
        where sor.product_model_id = si.product_model_id
        and (
        (si.batch_no is null and sor.batch_no is null)
        or si.batch_no = sor.batch_no
        )
        and sor.type = '0'
        and sor.approval_status = 0
        ) as qualifiedPendingOut,
        0 as unQualifiedPendingOut
        from stock_inventory si
        left join product_model pm on si.product_model_id = pm.id
        left join product p on pm.product_id = p.id
            union all
        union all
            select
            su.batch_no,
            null as qualifiedId,
            su.id as unQualifiedId,
            0 as qualifiedQuantity,
            su.qualitity as unQualifiedQuantity,
            COALESCE(su.locked_quantity, 0) as locked_quantity,
            0 as qualifiedLockedQuantity,
            COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
            su.product_model_id,
            su.create_time,
            su.update_time,
            0 as warn_num,
            su.version,
            (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity,
            pm.model,
            su.remark,
            pm.unit,
            p.product_name,
            p.id as product_id,
            0 as qualifiedPendingOut,
            (
                select IFNULL(SUM(sor.stock_out_num), 0)
                from stock_out_record sor
                where sor.product_model_id = su.product_model_id
                  and (su.batch_no is null and sor.batch_no is null or su.batch_no = sor.batch_no)
                  and sor.type = '1'
                  and sor.approval_status = 0
            ) as unqualifiedPendingOut
            from stock_uninventory su
            left join product_model pm on su.product_model_id = pm.id
            left join product p on pm.product_id = p.id
        select
        su.batch_no,
        null as qualifiedId,
        su.id as unQualifiedId,
        0 as qualifiedQuantity,
        su.qualitity as unQualifiedQuantity,
        COALESCE(su.locked_quantity, 0) as locked_quantity,
        0 as qualifiedLockedQuantity,
        COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
        su.product_model_id,
        su.create_time,
        su.update_time,
        0 as warn_num,
        su.version,
        (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity,
        pm.model,
        su.remark,
        pm.unit,
        p.product_name,
        p.id as product_id,
        0 as qualifiedPendingOut,
        (
        select IFNULL(SUM(sor.stock_out_num), 0)
        from stock_out_record sor
        where sor.product_model_id = su.product_model_id
        and (
        (su.batch_no is null and sor.batch_no is null)
        or su.batch_no = sor.batch_no
        )
        and sor.type = '1'
        and sor.approval_status = 0
        ) as unQualifiedPendingOut
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        ) as combined
        <where>
            <if test="ew.productName != null and ew.productName !=''">
@@ -196,14 +202,20 @@
                select distinct p.product_name
                from product p
                left join product_model pm on p.id = pm.product_id
                where p.product_name like concat('%',#{ew.productName},'%') or pm.model like concat('%',#{ew.productName},'%')
                where p.product_name like concat('%',#{ew.productName},'%')
                or pm.model like concat('%',#{ew.productName},'%')
                )
            </if>
            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
                and combined.product_id in (select id from product_tree)
            </if>
        </where>
        group by batch_no, product_model_id, model, unit, product_name, product_id
        group by
        product_model_id,
        model,
        unit,
        product_name,
        product_id
    </select>
    <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
@@ -515,4 +527,138 @@
        where si.product_model_id = #{productModelId}
    </select>
    <select id="getBatchNoQty" resultType="com.ruoyi.stock.dto.StockInventoryDto">
        select
        batch_no,
        MAX(qualifiedId) as qualifiedId,
        MAX(unQualifiedId) as unQualifiedId,
        SUM(qualifiedQuantity) as qualifiedQuantity,
        SUM(unQualifiedQuantity) as unQualifiedQuantity,
        SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
        SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
        SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
        SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
        SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
        SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
        product_model_id,
        model,
        unit,
        product_name,
        product_id,
        MAX(create_time) as create_time,
        MAX(update_time) as update_time,
        MAX(warn_num) as warn_num,
        MAX(version) as version,
        MAX(remark) as remark,
        'combined' as stockType
        from (
        select
        si.batch_no,
        si.id as qualifiedId,
        null as unQualifiedId,
        si.qualitity as qualifiedQuantity,
        0 as unQualifiedQuantity,
        COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
        0 as unQualifiedLockedQuantity,
        si.product_model_id,
        pm.model,
        pm.unit,
        p.product_name,
        p.id as product_id,
        si.create_time,
        si.update_time,
        COALESCE(si.warn_num, 0) as warn_num,
        si.version,
        si.remark,
        (
        select IFNULL(SUM(sor.stock_out_num), 0)
        from stock_out_record sor
        where sor.product_model_id = si.product_model_id
        and (
        (si.batch_no is null and sor.batch_no is null)
        or si.batch_no = sor.batch_no
        )
        and sor.type = '0'
        and sor.approval_status = 0
        ) as qualifiedPendingOut,
        0 as unQualifiedPendingOut
        from stock_inventory si
        left join product_model pm on si.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        union all
        select
        su.batch_no,
        null as qualifiedId,
        su.id as unQualifiedId,
        0 as qualifiedQuantity,
        su.qualitity as unQualifiedQuantity,
        0 as qualifiedLockedQuantity,
        COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
        su.product_model_id,
        pm.model,
        pm.unit,
        p.product_name,
        p.id as product_id,
        su.create_time,
        su.update_time,
        0 as warn_num,
        su.version,
        su.remark,
        0 as qualifiedPendingOut,
        (
        select IFNULL(SUM(sor.stock_out_num), 0)
        from stock_out_record sor
        where sor.product_model_id = su.product_model_id
        and (
        (su.batch_no is null and sor.batch_no is null)
        or su.batch_no = sor.batch_no
        )
        and sor.type = '1'
        and sor.approval_status = 0
        ) as unQualifiedPendingOut
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        ) as combined
        <where>
            <if test="ew.productModelId != null and ew.productModelId > 0">
                and combined.product_model_id = #{ew.productModelId}
            </if>
            <if test="ew.productId != null and ew.productId > 0">
                and combined.product_id = #{ew.productId}
            </if>
        </where>
        group by
        batch_no,
        product_model_id,
        model,
        unit,
        product_name,
        product_id
        order by
        batch_no
    </select>
</mapper>