工序计划展示,作业人员推送

This commit is contained in:
tzy 2026-04-14 23:02:00 +08:00
parent 4c39bbab96
commit e5aab8b9a7
24 changed files with 1198 additions and 45 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ mvnw.cmd
maven-wrapper.properties
**/logs/
/logs/

View File

@ -0,0 +1,71 @@
package com.ruoyi.system.controller;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.system.domain.bo.EquipmentInfoBo;
import com.ruoyi.system.domain.vo.EquipmentInfoVo;
import com.ruoyi.system.service.IEquipmentInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/equipment")
public class EquipmentInfoController extends BaseController {
private final IEquipmentInfoService equipmentInfoService;
@GetMapping("/list")
public TableDataInfo<EquipmentInfoVo> list(EquipmentInfoBo bo, PageQuery pageQuery) {
return equipmentInfoService.queryPageList(bo, pageQuery);
}
@GetMapping("/options")
public R<List<EquipmentInfoVo>> options(@RequestParam(required = false) String workCenterName,
@RequestParam(required = false) String keyword) {
return R.ok(equipmentInfoService.queryOptions(workCenterName, keyword));
}
@GetMapping("/{id}")
public R<EquipmentInfoVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
return R.ok(equipmentInfoService.queryById(id));
}
@RepeatSubmit
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody EquipmentInfoBo bo) {
return toAjax(equipmentInfoService.insertByBo(bo));
}
@RepeatSubmit
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody EquipmentInfoBo bo) {
return toAjax(equipmentInfoService.updateByBo(bo));
}
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
return toAjax(equipmentInfoService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}

View File

@ -0,0 +1,100 @@
package com.ruoyi.system.controller;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRespDto;
import com.ruoyi.system.service.IWorkbenchService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 工作台看板数据接口
* <p>
* 说明
* <ul>
* <li>时间参数格式yyyy-MM-dd HH:mm:ss</li>
* <li>workCenterName按工作中心过滤来自工序计划明细的 work_center_name</li>
* </ul>
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/system/workbench")
public class WorkbenchController {
private final IWorkbenchService workbenchService;
/**
* 顶部概览卡片一次返回 4 张卡保证口径一致
*
* <p>rangeType 仅在未传 startTime/endTime 时生效</p>
*
* @param rangeType 时间范围类型day|week|month|year默认 month
* @param startTime 自定义开始时间可选格式 yyyy-MM-dd HH:mm:ss
* @param endTime 自定义结束时间可选格式 yyyy-MM-dd HH:mm:ss
* @param workCenterName 工作中心名称可选
*/
@GetMapping("/overview")
public R<WorkbenchOverviewRespDto> overview(@RequestParam(required = false) String rangeType,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String workCenterName) {
return R.ok(workbenchService.overview(rangeType, startTime, endTime, workCenterName));
}
/**
* 工单数量柱状图
*
* <p>当未传 startTime/endTime 默认统计当年 1~12 </p>
*
* @param startTime 统计开始时间可选格式 yyyy-MM-dd HH:mm:ss
* @param endTime 统计结束时间可选格式 yyyy-MM-dd HH:mm:ss
* @param groupBy 分组粒度month|day默认 month
* @param workCenterName 工作中心名称可选
*/
@GetMapping("/workOrderBar")
public R<WorkbenchWorkOrderBarRespDto> workOrderBar(@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String groupBy,
@RequestParam(required = false) String workCenterName) {
return R.ok(workbenchService.workOrderBar(startTime, endTime, groupBy, workCenterName));
}
/**
* 工单实时进度
*
* @param startTime 统计开始时间可选格式 yyyy-MM-dd HH:mm:ss不传则按默认 month 范围
* @param endTime 统计结束时间可选格式 yyyy-MM-dd HH:mm:ss不传则按默认 month 范围
* @param topN 返回条数默认 5
* @param workCenterName 工作中心名称可选
*/
@GetMapping("/workOrderProgress")
public R<WorkbenchWorkOrderProgressRespDto> workOrderProgress(@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) Integer topN,
@RequestParam(required = false) String workCenterName) {
return R.ok(workbenchService.workOrderProgress(startTime, endTime, topN, workCenterName));
}
/**
* 工序不良率 TopN 排名
*
* <p>不良率 = 不良数 / (合格数 + 不良数)</p>
*
* @param startTime 统计开始时间可选格式 yyyy-MM-dd HH:mm:ss不传则按默认 month 范围
* @param endTime 统计结束时间可选格式 yyyy-MM-dd HH:mm:ss不传则按默认 month 范围
* @param topN 返回条数默认 10
* @param workCenterName 工作中心名称可选
*/
@GetMapping("/processDefectRank")
public R<WorkbenchProcessDefectRankRespDto> processDefectRank(@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) Integer topN,
@RequestParam(required = false) String workCenterName) {
return R.ok(workbenchService.processDefectRank(startTime, endTime, topN, workCenterName));
}
}

View File

@ -0,0 +1,28 @@
package com.ruoyi.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("equipment_info")
public class EquipmentInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String equipmentName;
private String equipmentCode;
private String operator;
private String workCenterName;
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.system.domain.bo;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@EqualsAndHashCode(callSuper = true)
public class EquipmentInfoBo extends BaseEntity {
@NotNull(message = "id不能为空", groups = {EditGroup.class})
private Long id;
@NotBlank(message = "设备名称不能为空", groups = {AddGroup.class, EditGroup.class})
private String equipmentName;
@NotBlank(message = "设备编号不能为空", groups = {AddGroup.class, EditGroup.class})
private String equipmentCode;
@NotBlank(message = "作业人员不能为空", groups = {AddGroup.class, EditGroup.class})
private String operator;
@NotBlank(message = "所属工段不能为空", groups = {AddGroup.class, EditGroup.class})
private String workCenterName;
}

View File

@ -81,5 +81,7 @@ public class SfcOperationPlanningMainBo extends BaseEntity {
@NotNull(message = "计划结束时间不能为空", groups = { AddGroup.class, EditGroup.class })
private Date planFinishTime;
private String workCenterName;
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class WorkbenchOverviewAggDto {
/**
* 工单数工序计划主表记录数
*/
private Long workOrderCount;
/**
* 完工工单数当前实现口径见 WorkbenchMapper.selectOverviewAgg 的说明
*/
private Long finishedWorkOrderCount;
/**
* 在制品数量当前实现口径计划数 - 完工数小于 0 0
*/
private BigDecimal wipCount;
/**
* 不良品数不良数量汇总
*/
private BigDecimal ngCount;
}

View File

@ -0,0 +1,44 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class WorkbenchOverviewRespDto {
/**
* 数据统计时点服务端当前时间
*/
private String asOf;
/**
* 4 张卡片固定顺序完工工单数/生产工单数/在制品/不良品数
*/
private List<Card> cards;
@Data
public static class Card {
/**
* 唯一标识前端用于识别卡片finishedWorkOrderCount/workOrderCount/wipCount/ngCount
*/
private String key;
/**
* 卡片标题
*/
private String title;
/**
* 主数值整数
*/
private Long value;
/**
* 环比增长率 (本期-上期)/上期
* <p>例如 0.68 表示 +68%</p>
*/
private BigDecimal mom;
/**
* 同比增长率 (本期-去年同期)/去年同期
* <p>例如 -0.05 表示 -5%</p>
*/
private BigDecimal yoy;
}
}

View File

@ -0,0 +1,39 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class WorkbenchProcessDefectRankRespDto {
/**
* 排名列表
*/
private List<Item> items;
@Data
public static class Item {
/**
* 名次 1 开始
*/
private Integer rank;
/**
* 工序/作业名称
*/
private String processName;
/**
* 不良率不良数 / (合格数 + 不良数)
* <p>例如 0.40 表示 40%</p>
*/
private BigDecimal defectRate;
/**
* 不良数量
*/
private Long defectQty;
/**
* 送检数量合格数 + 不良数
*/
private Long inspectedQty;
}
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class WorkbenchProcessDefectRankRowDto {
/**
* 工序/作业名称来自明细 process_name
*/
private String processName;
/**
* 不良数量unqualified_qty 汇总
*/
private BigDecimal defectQty;
/**
* 送检数量qualified_qty + unqualified_qty 汇总
*/
private BigDecimal inspectedQty;
/**
* 不良率defectQty / inspectedQty
*/
private BigDecimal defectRate;
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
@Data
public class WorkbenchWorkOrderBarPointDto {
/**
* 分组桶标识
* <ul>
* <li>groupBy=dayyyyy-MM-dd</li>
* <li>groupBy=month数字月份1~12</li>
* </ul>
*/
private String bucket;
/**
* 分组内工单数count distinct 主表 id
*/
private Long cnt;
}

View File

@ -0,0 +1,33 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.util.List;
@Data
public class WorkbenchWorkOrderBarRespDto {
/**
* X 轴分组名称
* <ul>
* <li>groupBy=month固定为 1月~12月</li>
* <li>groupBy=day yyyy-MM-dd</li>
* </ul>
*/
private List<String> xAxis;
/**
* 系列数据当前只有 1 个系列工单数
*/
private List<Series> series;
@Data
public static class Series {
/**
* 系列名称例如工单数
*/
private String name;
/**
* xAxis 一一对应的数据点
*/
private List<Long> data;
}
}

View File

@ -0,0 +1,19 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.util.List;
@Data
public class WorkbenchWorkOrderProgressRespDto {
private List<Item> items;
@Data
public static class Item {
private String workOrderNo;
private String status;
private Long progress;
private Long totalQty;
}
}

View File

@ -0,0 +1,14 @@
package com.ruoyi.system.domain.dto.workbench;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class WorkbenchWorkOrderProgressRowDto {
private String workOrderNo;
private String status;
private BigDecimal progress;
private BigDecimal totalQty;
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ExcelIgnoreUnannotated
public class EquipmentInfoVo {
@ExcelProperty("id")
private Long id;
@ExcelProperty("设备名称")
private String equipmentName;
@ExcelProperty("设备编号")
private String equipmentCode;
@ExcelProperty("作业人员")
private String operator;
@ExcelProperty("所属工段")
private String workCenterName;
@ExcelProperty("创建时间")
private Date createTime;
}

View File

@ -0,0 +1,9 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.EquipmentInfo;
import com.ruoyi.system.domain.vo.EquipmentInfoVo;
public interface EquipmentInfoMapper extends BaseMapperPlus<EquipmentInfoMapper, EquipmentInfo, EquipmentInfoVo> {
}

View File

@ -0,0 +1,79 @@
package com.ruoyi.system.mapper;
import com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewAggDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRowDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarPointDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRowDto;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* 工作台看板统计查询
*
* <p>这里的查询只负责按口径聚合出结果不做时间范围推导补点环比/同比等计算</p>
*/
public interface WorkbenchMapper {
/**
* 概览卡片所需的聚合汇总
*
* <ul>
* <li>workOrderCount工单数主表记录数</li>
* <li>finishedWorkOrderCount完工工单数当前实现口径取每个工单最大工序号的状态=4 认为完工</li>
* <li>wipCount在制品当前实现口径计划数 - 完工数小于 0 0 </li>
* <li>ngCount不良品数明细不良数汇总</li>
* </ul>
*
* @param startTime 统计开始时间
* @param endTime 统计结束时间
* @param workCenterName 工作中心过滤可为空
*/
WorkbenchOverviewAggDto selectOverviewAgg(@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("workCenterName") String workCenterName);
/**
* 工单数量柱状图的分组统计点
*
* @param startTime 统计开始时间
* @param endTime 统计结束时间
* @param groupBy day|month
* @param workCenterName 工作中心过滤可为空
*/
List<WorkbenchWorkOrderBarPointDto> selectWorkOrderBar(@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("groupBy") String groupBy,
@Param("workCenterName") String workCenterName);
/**
* 工单实时进度列表
*
* <p>进度=合格数汇总/计划数汇总*100</p>
*
* @param startTime 统计开始时间
* @param endTime 统计结束时间
* @param topN 返回条数
* @param workCenterName 工作中心过滤可为空
*/
List<WorkbenchWorkOrderProgressRowDto> selectWorkOrderProgress(@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("topN") Integer topN,
@Param("workCenterName") String workCenterName);
/**
* 工序不良率 TopN
*
* <p>defectRate = defectQty / inspectedQtyinspectedQty = okQty + ngQty</p>
*
* @param startTime 统计开始时间
* @param endTime 统计结束时间
* @param topN 返回条数
* @param workCenterName 工作中心过滤可为空
*/
List<WorkbenchProcessDefectRankRowDto> selectProcessDefectRank(@Param("startTime") Date startTime,
@Param("endTime") Date endTime,
@Param("topN") Integer topN,
@Param("workCenterName") String workCenterName);
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.system.service;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.bo.EquipmentInfoBo;
import com.ruoyi.system.domain.vo.EquipmentInfoVo;
import java.util.Collection;
import java.util.List;
public interface IEquipmentInfoService {
EquipmentInfoVo queryById(Long id);
TableDataInfo<EquipmentInfoVo> queryPageList(EquipmentInfoBo bo, PageQuery pageQuery);
List<EquipmentInfoVo> queryList(EquipmentInfoBo bo);
List<EquipmentInfoVo> queryOptions(String workCenterName, String keyword);
Boolean insertByBo(EquipmentInfoBo bo);
Boolean updateByBo(EquipmentInfoBo bo);
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,57 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarRespDto;
/**
* 工作台看板统计服务
* <p>
* 统一约定时间字符串入参格式为 yyyy-MM-dd HH:mm:ss
* </p>
*/
public interface IWorkbenchService {
/**
* 顶部 4 张概览卡片
*
* <p>mom/yoy 增长率不是百分数例如 0.68 表示 +68%</p>
*
* @param rangeType day|week|month|year默认 month仅在未传 startTime/endTime 时生效
* @param startTime 自定义开始时间可选
* @param endTime 自定义结束时间可选
* @param workCenterName 工作中心名称可选
*/
WorkbenchOverviewRespDto overview(String rangeType, String startTime, String endTime, String workCenterName);
/**
* 工单数量柱状图
*
* @param startTime 开始时间可选不传则默认当年 1~12
* @param endTime 结束时间可选不传则默认当年 1~12
* @param groupBy month|day默认 month
* @param workCenterName 工作中心名称可选
*/
WorkbenchWorkOrderBarRespDto workOrderBar(String startTime, String endTime, String groupBy, String workCenterName);
/**
* 工单实时进度列表
*
* @param startTime 开始时间可选不传则默认 month 范围
* @param endTime 结束时间可选不传则默认 month 范围
* @param topN 返回条数默认 5
* @param workCenterName 工作中心名称可选
*/
WorkbenchWorkOrderProgressRespDto workOrderProgress(String startTime, String endTime, Integer topN, String workCenterName);
/**
* 工序不良率 TopN
*
* @param startTime 开始时间可选不传则默认 month 范围
* @param endTime 结束时间可选不传则默认 month 范围
* @param topN TopN默认 10
* @param workCenterName 工作中心名称可选
*/
WorkbenchProcessDefectRankRespDto processDefectRank(String startTime, String endTime, Integer topN, String workCenterName);
}

View File

@ -0,0 +1,87 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.EquipmentInfo;
import com.ruoyi.system.domain.bo.EquipmentInfoBo;
import com.ruoyi.system.domain.vo.EquipmentInfoVo;
import com.ruoyi.system.mapper.EquipmentInfoMapper;
import com.ruoyi.system.service.IEquipmentInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class EquipmentInfoServiceImpl implements IEquipmentInfoService {
private final EquipmentInfoMapper baseMapper;
@Override
public EquipmentInfoVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
@Override
public TableDataInfo<EquipmentInfoVo> queryPageList(EquipmentInfoBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<EquipmentInfo> lqw = buildQueryWrapper(bo);
Page<EquipmentInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@Override
public List<EquipmentInfoVo> queryList(EquipmentInfoBo bo) {
LambdaQueryWrapper<EquipmentInfo> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
@Override
public List<EquipmentInfoVo> queryOptions(String workCenterName, String keyword) {
LambdaQueryWrapper<EquipmentInfo> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(workCenterName), EquipmentInfo::getWorkCenterName, workCenterName);
if (StringUtils.isNotBlank(keyword)) {
String kw = keyword.trim();
lqw.and(w -> w.like(EquipmentInfo::getEquipmentName, kw).or().like(EquipmentInfo::getEquipmentCode, kw));
}
lqw.orderByAsc(EquipmentInfo::getWorkCenterName)
.orderByAsc(EquipmentInfo::getEquipmentName)
.orderByAsc(EquipmentInfo::getEquipmentCode);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<EquipmentInfo> buildQueryWrapper(EquipmentInfoBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<EquipmentInfo> lqw = Wrappers.lambdaQuery();
lqw.eq(StringUtils.isNotBlank(bo.getEquipmentCode()), EquipmentInfo::getEquipmentCode, bo.getEquipmentCode());
lqw.like(StringUtils.isNotBlank(bo.getEquipmentName()), EquipmentInfo::getEquipmentName, bo.getEquipmentName());
lqw.eq(StringUtils.isNotBlank(bo.getOperator()), EquipmentInfo::getOperator, bo.getOperator());
lqw.eq(StringUtils.isNotBlank(bo.getWorkCenterName()), EquipmentInfo::getWorkCenterName, bo.getWorkCenterName());
return lqw;
}
@Override
public Boolean insertByBo(EquipmentInfoBo bo) {
EquipmentInfo add = BeanUtil.toBean(bo, EquipmentInfo.class);
return baseMapper.insert(add) > 0;
}
@Override
public Boolean updateByBo(EquipmentInfoBo bo) {
EquipmentInfo update = BeanUtil.toBean(bo, EquipmentInfo.class);
return baseMapper.updateById(update) > 0;
}
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@ -2,15 +2,14 @@ package com.ruoyi.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.helper.LoginHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.bo.SfcOperationPlanningMainBo;
@ -384,38 +383,12 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
*/
@Override
public TableDataInfo<OperationPlanResultDTO> queryCascadePageList(SfcOperationPlanningMainBo bo, PageQuery pageQuery) {
// 1. 获取当前登录用户信息与工段
boolean isAdmin = false;
String currentUserWorkCenter = "";
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser != null && loginUser.getDeptName() != null) {
isAdmin = com.ruoyi.common.helper.LoginHelper.isAdmin();
if (loginUser.getDeptName() != null) {
currentUserWorkCenter = loginUser.getDeptName();
}
}
} catch (Exception e) {
log.warn("获取当前登录用户失败,可能未登录或在定时任务中执行");
}
// 2. 构建主表查询条件
// 1. 构建主表查询条件
LambdaQueryWrapper<SfcOperationPlanningMain> lqw = buildQueryWrapper(bo);
// 过滤生产令号包含暂停的记录
lqw.notLike(SfcOperationPlanningMain::getHbytSclh, "暂停");
// 3. 如果不是管理员且有所属工段且部门名称是指定的具体工段之一则只查询明细表中包含该工段的订单主表 ID
boolean isSpecificWorkCenter = false;
if (!isAdmin && StringUtils.isNotBlank(currentUserWorkCenter)) {
List<String> targetWorkCenters = java.util.Arrays.asList("机一工段", "装二工段", "装一工段", "铆焊工段", "机二工段", "委外中心");
if (targetWorkCenters.contains(currentUserWorkCenter)) {
isSpecificWorkCenter = true;
lqw.inSql(SfcOperationPlanningMain::getId,
"SELECT main_id FROM sfc_operation_planning_detail WHERE work_center_name = '" + currentUserWorkCenter + "'");
}
}
// 4. 分页查询主表数据
// 2. 分页查询主表数据
Page<SfcOperationPlanningMain> page = baseMapper.selectPage(pageQuery.build(), lqw);
List<SfcOperationPlanningMain> mainList = page.getRecords();
@ -423,19 +396,22 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
return TableDataInfo.build(new ArrayList<>());
}
// 5. 提取当前页所有主表的 ID
// 3. 提取当前页所有主表的 ID
List<Long> mainIds = mainList.stream().map(SfcOperationPlanningMain::getId).collect(Collectors.toList());
// 6. 根据主表 ID 批量查询子表数据并按工序号升序排序 (这里查询所有的明细用于计算外层的总进度)
List<SfcOperationPlanningDetail> detailList = detailMapper.selectList(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.in(SfcOperationPlanningDetail::getMainId, mainIds)
.orderByAsc(SfcOperationPlanningDetail::getOperNumber));
// 4. 根据主表 ID 批量查询子表数据并按工序号升序排序 (这里查询所有的明细用于计算外层的总进度)
var detailQuery = Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.in(SfcOperationPlanningDetail::getMainId, mainIds);
if (StringUtils.isNotBlank(bo.getWorkCenterName())) {
detailQuery.eq(SfcOperationPlanningDetail::getWorkCenterName, bo.getWorkCenterName().trim());
}
List<SfcOperationPlanningDetail> detailList = detailMapper.selectList(detailQuery.orderByAsc(SfcOperationPlanningDetail::getOperNumber));
// 7. 按主表 ID 对子表数据进行分组
// 5. 按主表 ID 对子表数据进行分组
Map<Long, List<SfcOperationPlanningDetail>> detailMap = detailList.stream()
.collect(Collectors.groupingBy(SfcOperationPlanningDetail::getMainId));
// 8. 组装返回给前端的层级结构
// 6. 组装返回给前端的层级结构
List<OperationPlanResultDTO> resultList = new ArrayList<>();
for (SfcOperationPlanningMain main : mainList) {
OperationPlanResultDTO resultDTO = new OperationPlanResultDTO();
@ -472,13 +448,7 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
}
}
// 构建明细列表并在构建时进行权限过滤只有指定的几个工段用户才只能看到自己的数据其他非管理员都能看所有
for (SfcOperationPlanningDetail detail : relatedDetails) {
// 权限过滤如果当前用户属于这几个特定的工段则只显示自己工段的明细
if (isSpecificWorkCenter && !currentUserWorkCenter.equals(detail.getWorkCenterName())) {
continue;
}
ProcessDetailDTO detailDTO = new ProcessDetailDTO();
detailDTO.setOperNo(detail.getOperNumber() != null ? detail.getOperNumber().intValue() : null);
detailDTO.setProcessName(detail.getProcessName());
@ -593,8 +563,23 @@ public class SfcOperationPlanningMainServiceImpl implements ISfcOperationPlannin
lqw.eq(StringUtils.isNotBlank(bo.getProductCode()), SfcOperationPlanningMain::getProductCode, bo.getProductCode());
lqw.like(StringUtils.isNotBlank(bo.getProductName()), SfcOperationPlanningMain::getProductName, bo.getProductName());
lqw.eq(StringUtils.isNotBlank(bo.getProSpecification()), SfcOperationPlanningMain::getProSpecification, bo.getProSpecification());
lqw.eq(bo.getPlanStartTime() != null, SfcOperationPlanningMain::getPlanStartTime, bo.getPlanStartTime());
lqw.eq(bo.getPlanFinishTime() != null, SfcOperationPlanningMain::getPlanFinishTime, bo.getPlanFinishTime());
lqw.ge(bo.getPlanStartTime() != null, SfcOperationPlanningMain::getPlanStartTime, bo.getPlanStartTime());
lqw.le(bo.getPlanFinishTime() != null, SfcOperationPlanningMain::getPlanFinishTime, bo.getPlanFinishTime());
if (StringUtils.isNotBlank(bo.getWorkCenterName())) {
String workCenterName = bo.getWorkCenterName().trim();
List<Long> filteredMainIds = detailMapper.selectList(Wrappers.<SfcOperationPlanningDetail>lambdaQuery()
.select(SfcOperationPlanningDetail::getMainId)
.eq(SfcOperationPlanningDetail::getWorkCenterName, workCenterName))
.stream()
.map(SfcOperationPlanningDetail::getMainId)
.distinct()
.collect(Collectors.toList());
if (filteredMainIds.isEmpty()) {
lqw.eq(SfcOperationPlanningMain::getId, -1L);
} else {
lqw.in(SfcOperationPlanningMain::getId, filteredMainIds);
}
}
return lqw;
}

View File

@ -0,0 +1,285 @@
package com.ruoyi.system.service.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewAggDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRowDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarPointDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRespDto;
import com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRowDto;
import com.ruoyi.system.mapper.WorkbenchMapper;
import com.ruoyi.system.service.IWorkbenchService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class WorkbenchServiceImpl implements IWorkbenchService {
private final WorkbenchMapper workbenchMapper;
@Override
public WorkbenchOverviewRespDto overview(String rangeType, String startTime, String endTime, String workCenterName) {
Date asOf = new Date();
TimeRange current = resolveRange(rangeType, startTime, endTime, asOf);
TimeRange prev = previousRange(current);
TimeRange yoy = yearOverYearRange(current);
WorkbenchOverviewAggDto curAgg = defaultAgg(workbenchMapper.selectOverviewAgg(current.startTime, current.endTime, trimToNull(workCenterName)));
WorkbenchOverviewAggDto prevAgg = defaultAgg(workbenchMapper.selectOverviewAgg(prev.startTime, prev.endTime, trimToNull(workCenterName)));
WorkbenchOverviewAggDto yoyAgg = defaultAgg(workbenchMapper.selectOverviewAgg(yoy.startTime, yoy.endTime, trimToNull(workCenterName)));
WorkbenchOverviewRespDto resp = new WorkbenchOverviewRespDto();
resp.setAsOf(DateUtil.formatDateTime(asOf));
List<WorkbenchOverviewRespDto.Card> cards = new ArrayList<>();
cards.add(buildCard("finishedWorkOrderCount", "完工工单数",
nvl(curAgg.getFinishedWorkOrderCount()), nvl(prevAgg.getFinishedWorkOrderCount()), nvl(yoyAgg.getFinishedWorkOrderCount())));
cards.add(buildCard("workOrderCount", "生产工单数",
nvl(curAgg.getWorkOrderCount()), nvl(prevAgg.getWorkOrderCount()), nvl(yoyAgg.getWorkOrderCount())));
cards.add(buildCard("wipCount", "在制品",
toLong(curAgg.getWipCount()), toLong(prevAgg.getWipCount()), toLong(yoyAgg.getWipCount())));
cards.add(buildCard("ngCount", "不良品数",
toLong(curAgg.getNgCount()), toLong(prevAgg.getNgCount()), toLong(yoyAgg.getNgCount())));
resp.setCards(cards);
return resp;
}
@Override
public WorkbenchWorkOrderBarRespDto workOrderBar(String startTime, String endTime, String groupBy, String workCenterName) {
Date asOf = new Date();
TimeRange range = resolveBarRange(startTime, endTime, asOf);
String gb = normalizeGroupBy(groupBy);
List<WorkbenchWorkOrderBarPointDto> points = workbenchMapper.selectWorkOrderBar(range.startTime, range.endTime, gb, trimToNull(workCenterName));
Map<String, Long> bucketToCnt = new HashMap<>();
if (points != null) {
for (WorkbenchWorkOrderBarPointDto p : points) {
if (p != null && StringUtils.isNotBlank(p.getBucket())) {
bucketToCnt.put(p.getBucket(), nvl(p.getCnt()));
}
}
}
WorkbenchWorkOrderBarRespDto resp = new WorkbenchWorkOrderBarRespDto();
WorkbenchWorkOrderBarRespDto.Series series = new WorkbenchWorkOrderBarRespDto.Series();
series.setName("工单数");
if ("day".equals(gb)) {
List<String> xAxis = new ArrayList<>();
List<Long> data = new ArrayList<>();
Date cursor = DateUtil.beginOfDay(range.startTime);
Date endDay = DateUtil.beginOfDay(range.endTime);
while (!cursor.after(endDay)) {
String bucket = DateUtil.format(cursor, "yyyy-MM-dd");
xAxis.add(bucket);
data.add(bucketToCnt.getOrDefault(bucket, 0L));
cursor = DateUtil.offsetDay(cursor, 1);
}
resp.setXAxis(xAxis);
series.setData(data);
} else {
List<String> xAxis = new ArrayList<>();
List<Long> data = new ArrayList<>();
for (int m = 1; m <= 12; m++) {
xAxis.add(m + "");
data.add(bucketToCnt.getOrDefault(String.valueOf(m), 0L));
}
resp.setXAxis(xAxis);
series.setData(data);
}
List<WorkbenchWorkOrderBarRespDto.Series> seriesList = new ArrayList<>();
seriesList.add(series);
resp.setSeries(seriesList);
return resp;
}
@Override
public WorkbenchWorkOrderProgressRespDto workOrderProgress(String startTime, String endTime, Integer topN, String workCenterName) {
Date asOf = new Date();
TimeRange range = resolveRange("month", startTime, endTime, asOf);
int limit = (topN == null || topN <= 0) ? 5 : topN;
List<WorkbenchWorkOrderProgressRowDto> rows = workbenchMapper.selectWorkOrderProgress(range.startTime, range.endTime, limit, trimToNull(workCenterName));
List<WorkbenchWorkOrderProgressRespDto.Item> items = new ArrayList<>();
if (rows != null) {
for (WorkbenchWorkOrderProgressRowDto r : rows) {
WorkbenchWorkOrderProgressRespDto.Item item = new WorkbenchWorkOrderProgressRespDto.Item();
item.setWorkOrderNo(r.getWorkOrderNo());
item.setStatus(r.getStatus());
item.setProgress(toLong(r.getProgress()));
item.setTotalQty(toLong(r.getTotalQty()));
items.add(item);
}
}
WorkbenchWorkOrderProgressRespDto resp = new WorkbenchWorkOrderProgressRespDto();
resp.setItems(items);
return resp;
}
@Override
public WorkbenchProcessDefectRankRespDto processDefectRank(String startTime, String endTime, Integer topN, String workCenterName) {
Date asOf = new Date();
TimeRange range = resolveRange("month", startTime, endTime, asOf);
int limit = (topN == null || topN <= 0) ? 10 : topN;
List<WorkbenchProcessDefectRankRowDto> rows = workbenchMapper.selectProcessDefectRank(range.startTime, range.endTime, limit, trimToNull(workCenterName));
List<WorkbenchProcessDefectRankRespDto.Item> items = new ArrayList<>();
if (rows != null) {
int rank = 1;
for (WorkbenchProcessDefectRankRowDto r : rows) {
WorkbenchProcessDefectRankRespDto.Item item = new WorkbenchProcessDefectRankRespDto.Item();
item.setRank(rank++);
item.setProcessName(r.getProcessName());
item.setDefectRate(scale(r.getDefectRate(), 4));
item.setDefectQty(toLong(r.getDefectQty()));
item.setInspectedQty(toLong(r.getInspectedQty()));
items.add(item);
}
}
WorkbenchProcessDefectRankRespDto resp = new WorkbenchProcessDefectRankRespDto();
resp.setItems(items);
return resp;
}
private WorkbenchOverviewRespDto.Card buildCard(String key, String title, long cur, long prev, long yoy) {
WorkbenchOverviewRespDto.Card card = new WorkbenchOverviewRespDto.Card();
card.setKey(key);
card.setTitle(title);
card.setValue(cur);
card.setMom(calcRate(cur, prev));
card.setYoy(calcRate(cur, yoy));
return card;
}
private BigDecimal calcRate(long current, long base) {
if (base == 0L) {
return current == 0L ? BigDecimal.ZERO : BigDecimal.ONE;
}
return BigDecimal.valueOf(current)
.subtract(BigDecimal.valueOf(base))
.divide(BigDecimal.valueOf(base), 4, RoundingMode.HALF_UP);
}
private BigDecimal scale(BigDecimal val, int scale) {
if (val == null) {
return BigDecimal.ZERO;
}
return val.setScale(scale, RoundingMode.HALF_UP);
}
private WorkbenchOverviewAggDto defaultAgg(WorkbenchOverviewAggDto agg) {
if (agg != null) {
if (agg.getWorkOrderCount() == null) agg.setWorkOrderCount(0L);
if (agg.getFinishedWorkOrderCount() == null) agg.setFinishedWorkOrderCount(0L);
if (agg.getWipCount() == null) agg.setWipCount(BigDecimal.ZERO);
if (agg.getNgCount() == null) agg.setNgCount(BigDecimal.ZERO);
return agg;
}
WorkbenchOverviewAggDto a = new WorkbenchOverviewAggDto();
a.setWorkOrderCount(0L);
a.setFinishedWorkOrderCount(0L);
a.setWipCount(BigDecimal.ZERO);
a.setNgCount(BigDecimal.ZERO);
return a;
}
private long nvl(Long v) {
return v == null ? 0L : v;
}
private long toLong(BigDecimal v) {
return v == null ? 0L : v.setScale(0, RoundingMode.HALF_UP).longValue();
}
private String trimToNull(String s) {
if (StringUtils.isBlank(s)) {
return null;
}
String t = s.trim();
return t.isEmpty() ? null : t;
}
private String normalizeGroupBy(String groupBy) {
if ("day".equalsIgnoreCase(groupBy)) {
return "day";
}
return "month";
}
private TimeRange resolveBarRange(String startTime, String endTime, Date asOf) {
if (StringUtils.isBlank(startTime) || StringUtils.isBlank(endTime)) {
Date start = DateUtil.beginOfYear(asOf);
Date end = DateUtil.endOfYear(asOf);
return new TimeRange(start, end);
}
return new TimeRange(parseDateTime(startTime), parseDateTime(endTime));
}
private TimeRange resolveRange(String rangeType, String startTime, String endTime, Date asOf) {
if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
return new TimeRange(parseDateTime(startTime), parseDateTime(endTime));
}
String rt = StringUtils.defaultIfBlank(rangeType, "month").toLowerCase();
Date start;
if ("day".equals(rt)) {
start = DateUtil.beginOfDay(asOf);
} else if ("week".equals(rt)) {
start = DateUtil.beginOfWeek(asOf);
} else if ("year".equals(rt)) {
start = DateUtil.beginOfYear(asOf);
} else {
start = DateUtil.beginOfMonth(asOf);
}
return new TimeRange(start, asOf);
}
private Date parseDateTime(String s) {
return DateUtil.parse(s.trim(), "yyyy-MM-dd HH:mm:ss");
}
private TimeRange previousRange(TimeRange current) {
long durationMs = current.endTime.getTime() - current.startTime.getTime();
Date prevEnd = new Date(current.startTime.getTime() - 1);
Date prevStart = new Date(prevEnd.getTime() - durationMs);
return new TimeRange(prevStart, prevEnd);
}
private TimeRange yearOverYearRange(TimeRange current) {
Date start = DateUtil.offset(current.startTime, DateField.YEAR, -1);
Date end = DateUtil.offset(current.endTime, DateField.YEAR, -1);
return new TimeRange(start, end);
}
private static class TimeRange {
private final Date startTime;
private final Date endTime;
private TimeRange(Date startTime, Date endTime) {
if (startTime != null && endTime != null && startTime.after(endTime)) {
this.startTime = endTime;
this.endTime = startTime;
} else {
this.startTime = startTime;
this.endTime = endTime;
}
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.EquipmentInfoMapper">
<resultMap type="com.ruoyi.system.domain.EquipmentInfo" id="EquipmentInfoResult">
<result property="id" column="id"/>
<result property="equipmentName" column="equipment_name"/>
<result property="equipmentCode" column="equipment_code"/>
<result property="operator" column="operator"/>
<result property="workCenterName" column="work_center_name"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>
</resultMap>
</mapper>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.WorkbenchMapper">
<select id="selectOverviewAgg" resultType="com.ruoyi.system.domain.dto.workbench.WorkbenchOverviewAggDto">
SELECT
COALESCE(COUNT(t.main_id), 0) AS workOrderCount,
COALESCE(SUM(t.finished_flag), 0) AS finishedWorkOrderCount,
COALESCE(SUM(CASE WHEN (t.plan_qty - t.finish_qty) > 0 THEN (t.plan_qty - t.finish_qty) ELSE 0 END), 0) AS wipCount,
COALESCE(SUM(t.ng_qty), 0) AS ngCount
FROM (
SELECT
a.main_id,
a.plan_qty,
a.ng_qty,
CASE WHEN COALESCE(d2.oper_status, '') = '4' THEN 1 ELSE 0 END AS finished_flag,
COALESCE(SUM(COALESCE(d2.qualified_qty, 0)), 0) AS finish_qty
FROM (
SELECT
d.main_id,
MAX(d.oper_number) AS max_oper,
MAX(COALESCE(d.oper_qty, 0)) AS plan_qty,
SUM(COALESCE(d.unqualified_qty, 0)) AS ng_qty
FROM sfc_operation_planning_detail d
INNER JOIN sfc_operation_planning_main m ON m.id = d.main_id
WHERE m.plan_start_time <![CDATA[>=]]> #{startTime}
AND m.plan_start_time <![CDATA[<=]]> #{endTime}
<if test="workCenterName != null and workCenterName != ''">
AND d.work_center_name = #{workCenterName}
</if>
GROUP BY d.main_id
) a
LEFT JOIN sfc_operation_planning_detail d2
ON d2.main_id = a.main_id
AND d2.oper_number = a.max_oper
GROUP BY a.main_id, a.plan_qty, a.ng_qty, finished_flag
) t
</select>
<select id="selectWorkOrderBar" resultType="com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderBarPointDto">
SELECT
<choose>
<when test="groupBy == 'day'">
DATE_FORMAT(m.plan_start_time, '%Y-%m-%d')
</when>
<otherwise>
DATE_FORMAT(m.plan_start_time, '%c')
</otherwise>
</choose>
AS bucket,
COUNT(DISTINCT m.id) AS cnt
FROM sfc_operation_planning_main m
LEFT JOIN sfc_operation_planning_detail d ON d.main_id = m.id
WHERE m.plan_start_time <![CDATA[>=]]> #{startTime}
AND m.plan_start_time <![CDATA[<=]]> #{endTime}
<if test="workCenterName != null and workCenterName != ''">
AND d.work_center_name = #{workCenterName}
</if>
GROUP BY bucket
ORDER BY
<choose>
<when test="groupBy == 'day'">
bucket
</when>
<otherwise>
CAST(bucket AS UNSIGNED)
</otherwise>
</choose>
</select>
<select id="selectWorkOrderProgress" resultType="com.ruoyi.system.domain.dto.workbench.WorkbenchWorkOrderProgressRowDto">
SELECT
m.mo_number AS workOrderNo,
CASE
WHEN SUM(CASE WHEN COALESCE(d.oper_status, '') = '4' THEN 1 ELSE 0 END) = COUNT(1) THEN '已完工'
WHEN SUM(COALESCE(d.trans_in_qty, 0)) > 0
OR SUM(COALESCE(d.qualified_qty, 0) + COALESCE(d.unqualified_qty, 0)) > 0
OR SUM(CASE WHEN COALESCE(d.oper_status, '') = '3' THEN 1 ELSE 0 END) > 0
THEN '已开工'
ELSE '未开工'
END AS status,
CASE
WHEN SUM(COALESCE(d.oper_qty, 0)) = 0 THEN 0
ELSE ROUND(SUM(COALESCE(d.qualified_qty, 0)) / SUM(COALESCE(d.oper_qty, 0)) * 100, 0)
END AS progress,
COALESCE(MAX(COALESCE(d.oper_qty, 0)), 0) AS totalQty
FROM sfc_operation_planning_main m
INNER JOIN sfc_operation_planning_detail d ON d.main_id = m.id
WHERE m.plan_start_time <![CDATA[>=]]> #{startTime}
AND m.plan_start_time <![CDATA[<=]]> #{endTime}
<if test="workCenterName != null and workCenterName != ''">
AND d.work_center_name = #{workCenterName}
</if>
GROUP BY m.mo_number
ORDER BY m.plan_start_time DESC, progress DESC
LIMIT #{topN}
</select>
<select id="selectProcessDefectRank" resultType="com.ruoyi.system.domain.dto.workbench.WorkbenchProcessDefectRankRowDto">
SELECT
d.process_name AS processName,
COALESCE(SUM(COALESCE(d.unqualified_qty, 0)), 0) AS defectQty,
COALESCE(SUM(COALESCE(d.qualified_qty, 0) + COALESCE(d.unqualified_qty, 0)), 0) AS inspectedQty,
CASE
WHEN SUM(COALESCE(d.qualified_qty, 0) + COALESCE(d.unqualified_qty, 0)) = 0 THEN 0
ELSE SUM(COALESCE(d.unqualified_qty, 0)) / SUM(COALESCE(d.qualified_qty, 0) + COALESCE(d.unqualified_qty, 0))
END AS defectRate
FROM sfc_operation_planning_detail d
INNER JOIN sfc_operation_planning_main m ON m.id = d.main_id
WHERE m.plan_start_time <![CDATA[>=]]> #{startTime}
AND m.plan_start_time <![CDATA[<=]]> #{endTime}
<if test="workCenterName != null and workCenterName != ''">
AND d.work_center_name = #{workCenterName}
</if>
GROUP BY d.process_name
ORDER BY defectRate DESC, defectQty DESC
LIMIT #{topN}
</select>
</mapper>