Commit 9c36c2cfe9a2caa43a854468a822b20a2991f341

Authored by YunaiV
1 parent be3b3ef1

feat(mes): 添加生产报工相关的请求和响应 VO,重构报工控制器及服务实现

yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  11 +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackPageReqVO;
  12 +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackRespVO;
  13 +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  15 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  16 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  17 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
  18 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
  19 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
  20 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
  21 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  22 +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
  23 +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
  24 +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
  25 +import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
  26 +import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
  27 +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
  28 +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteService;
  29 +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
  30 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  31 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  32 +import io.swagger.v3.oas.annotations.Operation;
  33 +import io.swagger.v3.oas.annotations.Parameter;
  34 +import io.swagger.v3.oas.annotations.tags.Tag;
  35 +import jakarta.annotation.Resource;
  36 +import jakarta.servlet.http.HttpServletResponse;
  37 +import jakarta.validation.Valid;
  38 +import org.springframework.security.access.prepost.PreAuthorize;
  39 +import org.springframework.validation.annotation.Validated;
  40 +import org.springframework.web.bind.annotation.*;
  41 +
  42 +import java.io.IOException;
  43 +import java.util.*;
  44 +import java.util.stream.Collectors;
  45 +
  46 +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
  47 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  48 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  49 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  50 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  51 +
  52 +@Tag(name = "管理后台 - MES 生产报工")
  53 +@RestController
  54 +@RequestMapping("/mes/pro/feedback")
  55 +@Validated
  56 +public class MesProFeedbackController {
  57 +
  58 + @Resource
  59 + private MesProFeedbackService feedbackService;
  60 +
  61 + @Resource
  62 + private MesMdWorkstationService workstationService;
  63 +
  64 + @Resource
  65 + private MesProRouteService routeService;
  66 +
  67 + @Resource
  68 + private MesProProcessService processService;
  69 +
  70 + @Resource
  71 + private MesProWorkOrderService workOrderService;
  72 +
  73 + @Resource
  74 + private MesMdItemService itemService;
  75 +
  76 + @Resource
  77 + private MesMdUnitMeasureService unitMeasureService;
  78 +
  79 + @Resource
  80 + private MesProRouteProcessService routeProcessService;
  81 +
  82 + @Resource
  83 + private AdminUserApi adminUserApi;
  84 +
  85 + @PostMapping("/create")
  86 + @Operation(summary = "创建生产报工")
  87 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:create')")
  88 + public CommonResult<Long> createFeedback(@Valid @RequestBody MesProFeedbackSaveReqVO createReqVO) {
  89 + return success(feedbackService.createFeedback(createReqVO));
  90 + }
  91 +
  92 + @PutMapping("/update")
  93 + @Operation(summary = "更新生产报工")
  94 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
  95 + public CommonResult<Boolean> updateFeedback(@Valid @RequestBody MesProFeedbackSaveReqVO updateReqVO) {
  96 + feedbackService.updateFeedback(updateReqVO);
  97 + return success(true);
  98 + }
  99 +
  100 + @DeleteMapping("/delete")
  101 + @Operation(summary = "删除生产报工")
  102 + @Parameter(name = "id", description = "编号", required = true)
  103 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:delete')")
  104 + public CommonResult<Boolean> deleteFeedback(@RequestParam("id") Long id) {
  105 + feedbackService.deleteFeedback(id);
  106 + return success(true);
  107 + }
  108 +
  109 + @GetMapping("/get")
  110 + @Operation(summary = "获得生产报工")
  111 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  112 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')")
  113 + public CommonResult<MesProFeedbackRespVO> getFeedback(@RequestParam("id") Long id) {
  114 + MesProFeedbackDO feedback = feedbackService.getFeedback(id);
  115 + if (feedback == null) {
  116 + return success(null);
  117 + }
  118 + return success(buildFeedbackRespVOList(List.of(feedback)).get(0));
  119 + }
  120 +
  121 + @GetMapping("/page")
  122 + @Operation(summary = "获得生产报工分页")
  123 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')")
  124 + public CommonResult<PageResult<MesProFeedbackRespVO>> getFeedbackPage(@Valid MesProFeedbackPageReqVO pageReqVO) {
  125 + PageResult<MesProFeedbackDO> pageResult = feedbackService.getFeedbackPage(pageReqVO);
  126 + return success(new PageResult<>(buildFeedbackRespVOList(pageResult.getList()), pageResult.getTotal()));
  127 + }
  128 +
  129 + @GetMapping("/export-excel")
  130 + @Operation(summary = "导出生产报工 Excel")
  131 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:export')")
  132 + @ApiAccessLog(operateType = EXPORT)
  133 + public void exportFeedbackExcel(@Valid MesProFeedbackPageReqVO pageReqVO,
  134 + HttpServletResponse response) throws IOException {
  135 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
  136 + List<MesProFeedbackDO> list = feedbackService.getFeedbackPage(pageReqVO).getList();
  137 + List<MesProFeedbackRespVO> voList = buildFeedbackRespVOList(list);
  138 + ExcelUtils.write(response, "生产报工.xls", "数据", MesProFeedbackRespVO.class, voList);
  139 + }
  140 +
  141 + @PutMapping("/submit")
  142 + @Operation(summary = "提交报工")
  143 + @Parameter(name = "id", description = "编号", required = true)
  144 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
  145 + public CommonResult<Boolean> submitFeedback(@RequestParam("id") Long id) {
  146 + feedbackService.submitFeedback(id);
  147 + return success(true);
  148 + }
  149 +
  150 + @PutMapping("/reject")
  151 + @Operation(summary = "驳回报工")
  152 + @Parameter(name = "id", description = "编号", required = true)
  153 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
  154 + public CommonResult<Boolean> rejectFeedback(@RequestParam("id") Long id) {
  155 + feedbackService.rejectFeedback(id);
  156 + return success(true);
  157 + }
  158 +
  159 + @PutMapping("/execute")
  160 + @Operation(summary = "执行报工")
  161 + @Parameter(name = "id", description = "编号", required = true)
  162 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
  163 + public CommonResult<Boolean> executeFeedback(@RequestParam("id") Long id) {
  164 + feedbackService.executeFeedback(id);
  165 + return success(true);
  166 + }
  167 +
  168 + @PutMapping("/cancel")
  169 + @Operation(summary = "取消报工")
  170 + @Parameter(name = "id", description = "编号", required = true)
  171 + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
  172 + public CommonResult<Boolean> cancelFeedback(@RequestParam("id") Long id) {
  173 + feedbackService.cancelFeedback(id);
  174 + return success(true);
  175 + }
  176 +
  177 + // ==================== 拼接 VO ====================
  178 +
  179 + private List<MesProFeedbackRespVO> buildFeedbackRespVOList(List<MesProFeedbackDO> list) {
  180 + if (CollUtil.isEmpty(list)) {
  181 + return Collections.emptyList();
  182 + }
  183 + // 1.1 物料、计量单位
  184 + Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(
  185 + convertSet(list, MesProFeedbackDO::getItemId));
  186 + Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
  187 + convertSet(list, MesProFeedbackDO::getUnitMeasureId));
  188 + // 1.2 工单
  189 + Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
  190 + convertSet(list, MesProFeedbackDO::getWorkOrderId));
  191 + // 1.3 工作站(逐个查询,因为没有批量方法)
  192 + // TODO @AI:getWorkstationMap 必须有办法
  193 + Set<Long> workstationIds = convertSet(list, MesProFeedbackDO::getWorkstationId);
  194 + Map<Long, MesMdWorkstationDO> workstationMap = new HashMap<>();
  195 + workstationIds.forEach(wsId -> {
  196 + MesMdWorkstationDO ws = workstationService.getWorkstation(wsId);
  197 + if (ws != null) {
  198 + workstationMap.put(wsId, ws);
  199 + }
  200 + });
  201 + // 1.4 工艺路线(逐个查询)
  202 + // TODO @AI:getRouteMap 必须有办法;里面调用 getRouteList;如果不行,就加个;
  203 + Set<Long> routeIds = convertSet(list, MesProFeedbackDO::getRouteId);
  204 + Map<Long, MesProRouteDO> routeMap = new HashMap<>();
  205 + routeIds.forEach(rId -> {
  206 + MesProRouteDO route = routeService.getRoute(rId);
  207 + if (route != null) {
  208 + routeMap.put(rId, route);
  209 + }
  210 + });
  211 + // 1.5 工序
  212 + // TODO @AI:getProcessMap 必须有办法;里面调用 getProcessList;如果不行,就加个;
  213 + Set<Long> processIds = convertSet(list, MesProFeedbackDO::getProcessId);
  214 + List<MesProProcessDO> processList = processService.getProcessList(new ArrayList<>(processIds));
  215 + Map<Long, MesProProcessDO> processMap = processList.stream()
  216 + .collect(Collectors.toMap(MesProProcessDO::getId, p -> p, (a, b) -> a));
  217 + // 1.6 工序的 checkFlag(按路线维度缓存)
  218 + // TODO @AI:collutils 里面有个 convertMultiMap 的方法;
  219 + Map<Long, List<MesProRouteProcessDO>> routeProcessMap = new HashMap<>();
  220 + routeIds.forEach(rId -> routeProcessMap.put(rId, routeProcessService.getRouteProcessListByRouteId(rId)));
  221 + // 1.7 报工人/审核人
  222 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  223 + convertSetByFlatMap(list, feedback -> {
  224 + // TODO @AI:必须要判空,里面已经处理了;
  225 + Set<Long> userIds = new HashSet<>();
  226 + if (feedback.getFeedbackUserId() != null) {
  227 + userIds.add(feedback.getFeedbackUserId());
  228 + }
  229 + if (feedback.getApproveUserId() != null) {
  230 + userIds.add(feedback.getApproveUserId());
  231 + }
  232 + return userIds.stream();
  233 + }));
  234 +
  235 + // 2. 拼接 VO
  236 + return BeanUtils.toBean(list, MesProFeedbackRespVO.class, vo -> {
  237 + // 工作站
  238 + findAndThen(workstationMap, vo.getWorkstationId(), ws ->
  239 + vo.setWorkstationCode(ws.getCode()).setWorkstationName(ws.getName()));
  240 + // 工艺路线
  241 + findAndThen(routeMap, vo.getRouteId(), route ->
  242 + vo.setRouteCode(route.getCode()));
  243 + // 工序
  244 + findAndThen(processMap, vo.getProcessId(), process ->
  245 + vo.setProcessCode(process.getCode()).setProcessName(process.getName()));
  246 + // checkFlag
  247 + // TODO @AI:这个判断有点复杂,是不是上面构建更合适的 map?
  248 + List<MesProRouteProcessDO> rps = routeProcessMap.get(vo.getRouteId());
  249 + if (rps != null) {
  250 + rps.stream()
  251 + .filter(rp -> rp.getProcessId().equals(vo.getProcessId()))
  252 + .findFirst()
  253 + .ifPresent(rp -> vo.setCheckFlag(Boolean.TRUE.equals(rp.getCheckFlag())));
  254 + }
  255 + // 工单
  256 + findAndThen(workOrderMap, vo.getWorkOrderId(), wo ->
  257 + vo.setWorkOrderCode(wo.getCode()).setWorkOrderName(wo.getName()));
  258 + // TODO @芋艿:task code 待 pro_task 服务迁移后补充
  259 + // 物料、计量单位
  260 + findAndThen(itemMap, vo.getItemId(), item ->
  261 + vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpec(item.getSpecification()));
  262 + findAndThen(unitMeasureMap, vo.getUnitMeasureId(), unit ->
  263 + vo.setUnitMeasureName(unit.getName()));
  264 + // 报工人、审核人
  265 + findAndThen(userMap, vo.getFeedbackUserId(), user ->
  266 + vo.setFeedbackUserNickname(user.getNickname()));
  267 + findAndThen(userMap, vo.getApproveUserId(), user ->
  268 + vo.setApproveUserNickname(user.getNickname()));
  269 + });
  270 + }
  271 +
  272 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +@Schema(description = "管理后台 - MES 生产报工分页 Request VO")
  15 +@Data
  16 +@EqualsAndHashCode(callSuper = true)
  17 +@ToString(callSuper = true)
  18 +public class MesProFeedbackPageReqVO extends PageParam {
  19 +
  20 + @Schema(description = "报工单编号", example = "FB202503")
  21 + private String code;
  22 +
  23 + @Schema(description = "报工类型", example = "1")
  24 + private Integer type;
  25 +
  26 + @Schema(description = "生产工单编号", example = "1")
  27 + private Long workOrderId;
  28 +
  29 + @Schema(description = "状态", example = "0")
  30 + private Integer status;
  31 +
  32 + @Schema(description = "报工时间")
  33 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  34 + private LocalDateTime[] feedbackTime;
  35 +
  36 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  5 +import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
  6 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  7 +import cn.idev.excel.annotation.ExcelProperty;
  8 +import io.swagger.v3.oas.annotations.media.Schema;
  9 +import lombok.Data;
  10 +
  11 +import java.math.BigDecimal;
  12 +import java.time.LocalDateTime;
  13 +
  14 +@Schema(description = "管理后台 - MES 生产报工 Response VO")
  15 +@Data
  16 +@ExcelIgnoreUnannotated
  17 +public class MesProFeedbackRespVO {
  18 +
  19 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + @ExcelProperty("编号")
  21 + private Long id;
  22 +
  23 + @Schema(description = "报工单编号", example = "FB202503160001")
  24 + @ExcelProperty("报工单编号")
  25 + private String code;
  26 +
  27 + @Schema(description = "报工类型", example = "1")
  28 + @ExcelProperty(value = "报工类型", converter = DictConvert.class)
  29 + @DictFormat(DictTypeConstants.MES_PRO_FEEDBACK_TYPE)
  30 + private Integer type;
  31 +
  32 + @Schema(description = "报工途径", example = "PC")
  33 + @ExcelProperty("报工途径")
  34 + private String channel;
  35 +
  36 + @Schema(description = "报工时间")
  37 + @ExcelProperty("报工时间")
  38 + private LocalDateTime feedbackTime;
  39 +
  40 + @Schema(description = "工作站编号", example = "1")
  41 + private Long workstationId;
  42 +
  43 + @Schema(description = "工作站编码", example = "WS-001")
  44 + @ExcelProperty("工作站编码")
  45 + private String workstationCode;
  46 +
  47 + @Schema(description = "工作站名称", example = "注塑工作站")
  48 + @ExcelProperty("工作站名称")
  49 + private String workstationName;
  50 +
  51 + @Schema(description = "工艺路线编号", example = "1")
  52 + private Long routeId;
  53 +
  54 + @Schema(description = "工艺路线编码", example = "RT-001")
  55 + private String routeCode;
  56 +
  57 + @Schema(description = "工序编号", example = "1")
  58 + private Long processId;
  59 +
  60 + @Schema(description = "工序编码", example = "PR-001")
  61 + @ExcelProperty("工序编码")
  62 + private String processCode;
  63 +
  64 + @Schema(description = "工序名称", example = "注塑")
  65 + @ExcelProperty("工序名称")
  66 + private String processName;
  67 +
  68 + @Schema(description = "是否需要检验")
  69 + private Boolean checkFlag;
  70 +
  71 + @Schema(description = "生产工单编号", example = "1")
  72 + private Long workOrderId;
  73 +
  74 + @Schema(description = "工单编码", example = "MO202503120008")
  75 + @ExcelProperty("工单编码")
  76 + private String workOrderCode;
  77 +
  78 + @Schema(description = "工单名称", example = "博世螺丝刀")
  79 + @ExcelProperty("工单名称")
  80 + private String workOrderName;
  81 +
  82 + @Schema(description = "生产任务编号", example = "1")
  83 + private Long taskId;
  84 +
  85 + @Schema(description = "任务编码", example = "PT202503150001")
  86 + private String taskCode;
  87 +
  88 + @Schema(description = "产品物料编号", example = "75")
  89 + private Long itemId;
  90 +
  91 + @Schema(description = "物料编码", example = "I-075")
  92 + @ExcelProperty("物料编码")
  93 + private String itemCode;
  94 +
  95 + @Schema(description = "物料名称", example = "博世螺丝刀")
  96 + @ExcelProperty("物料名称")
  97 + private String itemName;
  98 +
  99 + @Schema(description = "规格型号", example = "一字型")
  100 + @ExcelProperty("规格型号")
  101 + private String itemSpec;
  102 +
  103 + @Schema(description = "单位编号", example = "202")
  104 + private Long unitMeasureId;
  105 +
  106 + @Schema(description = "单位名称", example = "个")
  107 + @ExcelProperty("单位")
  108 + private String unitMeasureName;
  109 +
  110 + @Schema(description = "过期日期")
  111 + private LocalDateTime expireDate;
  112 +
  113 + @Schema(description = "批次号", example = "B20250315001")
  114 + @ExcelProperty("批次号")
  115 + private String batchCode;
  116 +
  117 + @Schema(description = "排产数量", example = "5000.00")
  118 + @ExcelProperty("排产数量")
  119 + private BigDecimal scheduledQuantity;
  120 +
  121 + @Schema(description = "本次报工数量", example = "500.00")
  122 + @ExcelProperty("报工数量")
  123 + private BigDecimal feedbackQuantity;
  124 +
  125 + @Schema(description = "合格品数量", example = "490.00")
  126 + @ExcelProperty("合格品数量")
  127 + private BigDecimal qualifiedQuantity;
  128 +
  129 + @Schema(description = "不良品数量", example = "10.00")
  130 + @ExcelProperty("不良品数量")
  131 + private BigDecimal unqualifiedQuantity;
  132 +
  133 + @Schema(description = "待检测数量", example = "0")
  134 + @ExcelProperty("待检测数量")
  135 + private BigDecimal uncheckQuantity;
  136 +
  137 + @Schema(description = "工废数量", example = "6.00")
  138 + private BigDecimal laborScrapQuantity;
  139 +
  140 + @Schema(description = "料废数量", example = "4.00")
  141 + private BigDecimal materialScrapQuantity;
  142 +
  143 + @Schema(description = "其他废品数量", example = "0")
  144 + private BigDecimal otherScrapQuantity;
  145 +
  146 + @Schema(description = "报工用户编号", example = "1")
  147 + private Long feedbackUserId;
  148 +
  149 + @Schema(description = "报工人昵称", example = "张三")
  150 + @ExcelProperty("报工人")
  151 + private String feedbackUserNickname;
  152 +
  153 + @Schema(description = "审核用户编号", example = "1")
  154 + private Long approveUserId;
  155 +
  156 + @Schema(description = "审核人昵称", example = "李四")
  157 + @ExcelProperty("审核人")
  158 + private String approveUserNickname;
  159 +
  160 + @Schema(description = "状态", example = "0")
  161 + @ExcelProperty(value = "状态", converter = DictConvert.class)
  162 + @DictFormat(DictTypeConstants.MES_PRO_FEEDBACK_STATUS)
  163 + private Integer status;
  164 +
  165 + @Schema(description = "备注", example = "备注")
  166 + @ExcelProperty("备注")
  167 + private String remark;
  168 +
  169 + @Schema(description = "预留字段1")
  170 + private String attribute1;
  171 +
  172 + @Schema(description = "预留字段2")
  173 + private String attribute2;
  174 +
  175 + @Schema(description = "预留字段3")
  176 + private Integer attribute3;
  177 +
  178 + @Schema(description = "预留字段4")
  179 + private Integer attribute4;
  180 +
  181 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  182 + @ExcelProperty("创建时间")
  183 + private LocalDateTime createTime;
  184 +
  185 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - MES 生产报工新增/修改 Request VO")
  11 +@Data
  12 +public class MesProFeedbackSaveReqVO {
  13 +
  14 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "报工类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + @NotNull(message = "报工类型不能为空")
  19 + private Integer type;
  20 +
  21 + @Schema(description = "报工途径", example = "PC")
  22 + private String channel;
  23 +
  24 + @Schema(description = "工作站编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "工作站不能为空")
  26 + private Long workstationId;
  27 +
  28 + @Schema(description = "工艺路线编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + @NotNull(message = "工艺路线不能为空")
  30 + private Long routeId;
  31 +
  32 + @Schema(description = "工序编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  33 + @NotNull(message = "工序不能为空")
  34 + private Long processId;
  35 +
  36 + @Schema(description = "生产工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "生产工单不能为空")
  38 + private Long workOrderId;
  39 +
  40 + @Schema(description = "生产任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  41 + @NotNull(message = "生产任务不能为空")
  42 + private Long taskId;
  43 +
  44 + @Schema(description = "产品物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "75")
  45 + @NotNull(message = "产品物料不能为空")
  46 + private Long itemId;
  47 +
  48 + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "202")
  49 + @NotNull(message = "单位不能为空")
  50 + private Long unitMeasureId;
  51 +
  52 + @Schema(description = "过期日期")
  53 + private LocalDateTime expireDate;
  54 +
  55 + @Schema(description = "批次号", example = "B20250315001")
  56 + private String batchCode;
  57 +
  58 + @Schema(description = "排产数量", example = "5000.00")
  59 + private BigDecimal scheduledQuantity;
  60 +
  61 + @Schema(description = "本次报工数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00")
  62 + @NotNull(message = "报工数量不能为空")
  63 + private BigDecimal feedbackQuantity;
  64 +
  65 + @Schema(description = "合格品数量", example = "490.00")
  66 + private BigDecimal qualifiedQuantity;
  67 +
  68 + @Schema(description = "不良品数量", example = "10.00")
  69 + private BigDecimal unqualifiedQuantity;
  70 +
  71 + @Schema(description = "待检测数量", example = "0")
  72 + private BigDecimal uncheckQuantity;
  73 +
  74 + @Schema(description = "工废数量", example = "6.00")
  75 + private BigDecimal laborScrapQuantity;
  76 +
  77 + @Schema(description = "料废数量", example = "4.00")
  78 + private BigDecimal materialScrapQuantity;
  79 +
  80 + @Schema(description = "其他废品数量", example = "0")
  81 + private BigDecimal otherScrapQuantity;
  82 +
  83 + @Schema(description = "备注", example = "备注")
  84 + private String remark;
  85 +
  86 + @Schema(description = "预留字段1")
  87 + private String attribute1;
  88 +
  89 + @Schema(description = "预留字段2")
  90 + private String attribute2;
  91 +
  92 + @Schema(description = "预留字段3")
  93 + private Integer attribute3;
  94 +
  95 + @Schema(description = "预留字段4")
  96 + private Integer attribute4;
  97 +
  98 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
  5 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  10 +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
  11 +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum;
  12 +import com.baomidou.mybatisplus.annotation.KeySequence;
  13 +import com.baomidou.mybatisplus.annotation.TableId;
  14 +import com.baomidou.mybatisplus.annotation.TableName;
  15 +import lombok.*;
  16 +
  17 +import java.math.BigDecimal;
  18 +import java.time.LocalDateTime;
  19 +
  20 +/**
  21 + * MES 生产报工 DO
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@TableName("mes_pro_feedback")
  26 +@KeySequence("mes_pro_feedback_seq")
  27 +@Data
  28 +@EqualsAndHashCode(callSuper = true)
  29 +@ToString(callSuper = true)
  30 +@Builder
  31 +@NoArgsConstructor
  32 +@AllArgsConstructor
  33 +public class MesProFeedbackDO extends BaseDO {
  34 +
  35 + /**
  36 + * 编号
  37 + */
  38 + @TableId
  39 + private Long id;
  40 + /**
  41 + * 报工单编号
  42 + */
  43 + private String code;
  44 + /**
  45 + * 报工类型
  46 + *
  47 + * 枚举 {@link MesProFeedbackTypeEnum}
  48 + */
  49 + private Integer type;
  50 + // TODO @AI:这里需要字典么?
  51 + /**
  52 + * 报工途径
  53 + */
  54 + private String channel;
  55 + /**
  56 + * 报工时间
  57 + */
  58 + private LocalDateTime feedbackTime;
  59 + /**
  60 + * 工作站编号
  61 + *
  62 + * 关联 {@link MesMdWorkstationDO#getId()}
  63 + */
  64 + private Long workstationId;
  65 + /**
  66 + * 工艺路线编号
  67 + *
  68 + * 关联 {@link MesProRouteDO#getId()}
  69 + */
  70 + private Long routeId;
  71 + /**
  72 + * 工序编号
  73 + *
  74 + * 关联 {@link MesProProcessDO#getId()}
  75 + */
  76 + private Long processId;
  77 + /**
  78 + * 生产工单编号
  79 + *
  80 + * 关联 {@link MesProWorkOrderDO#getId()}
  81 + */
  82 + private Long workOrderId;
  83 + // TODO @芋艿:这里待关联;
  84 + /**
  85 + * 生产任务编号
  86 + */
  87 + private Long taskId;
  88 + /**
  89 + * 产品物料编号
  90 + *
  91 + * 关联 {@link MesMdItemDO#getId()}
  92 + */
  93 + private Long itemId;
  94 + /**
  95 + * 单位编号
  96 + *
  97 + * 关联 {@link MesMdUnitMeasureDO#getId()}
  98 + */
  99 + private Long unitMeasureId;
  100 + /**
  101 + * 过期日期
  102 + */
  103 + private LocalDateTime expireDate;
  104 + /**
  105 + * 批次号
  106 + */
  107 + private String batchCode;
  108 + /**
  109 + * 排产数量
  110 + */
  111 + private BigDecimal scheduledQuantity;
  112 + /**
  113 + * 本次报工数量
  114 + */
  115 + private BigDecimal feedbackQuantity;
  116 + /**
  117 + * 合格品数量
  118 + */
  119 + private BigDecimal qualifiedQuantity;
  120 + /**
  121 + * 不良品数量
  122 + */
  123 + private BigDecimal unqualifiedQuantity;
  124 + /**
  125 + * 待检测数量
  126 + */
  127 + private BigDecimal uncheckQuantity;
  128 + /**
  129 + * 工废数量
  130 + */
  131 + private BigDecimal laborScrapQuantity;
  132 + /**
  133 + * 料废数量
  134 + */
  135 + private BigDecimal materialScrapQuantity;
  136 + /**
  137 + * 其他废品数量
  138 + */
  139 + private BigDecimal otherScrapQuantity;
  140 + // TODO @AI:feedbackUserId、approveUserId 关联字段
  141 + /**
  142 + * 报工用户编号
  143 + */
  144 + private Long feedbackUserId;
  145 + /**
  146 + * 审核用户编号
  147 + */
  148 + private Long approveUserId;
  149 + /**
  150 + * 状态
  151 + *
  152 + * 枚举 {@link MesProFeedbackStatusEnum}
  153 + */
  154 + private Integer status;
  155 + /**
  156 + * 备注
  157 + */
  158 + private String remark;
  159 + /**
  160 + * 预留字段1
  161 + */
  162 + private String attribute1;
  163 + /**
  164 + * 预留字段2
  165 + */
  166 + private String attribute2;
  167 + /**
  168 + * 预留字段3
  169 + */
  170 + private Integer attribute3;
  171 + /**
  172 + * 预留字段4
  173 + */
  174 + private Integer attribute4;
  175 +
  176 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.pro.feedback;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackPageReqVO;
  8 +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  12 +import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
  13 +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
  14 +import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
  15 +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
  16 +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
  17 +import jakarta.annotation.Resource;
  18 +import org.springframework.stereotype.Service;
  19 +import org.springframework.transaction.annotation.Transactional;
  20 +import org.springframework.validation.annotation.Validated;
  21 +
  22 +import java.time.LocalDateTime;
  23 +import java.util.List;
  24 +
  25 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  26 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  27 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
  28 +
  29 +/**
  30 + * MES 生产报工 Service 实现类
  31 + *
  32 + * @author 芋道源码
  33 + */
  34 +@Service
  35 +@Validated
  36 +public class MesProFeedbackServiceImpl implements MesProFeedbackService {
  37 +
  38 + @Resource
  39 + private MesProFeedbackMapper feedbackMapper;
  40 +
  41 + @Resource
  42 + private MesProWorkOrderService workOrderService;
  43 +
  44 + @Resource
  45 + private MesProRouteProcessService routeProcessService;
  46 +
  47 + @Override
  48 + public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
  49 + // 1.1 校验工单已确认
  50 + // TODO @AI: workOrderService 里抽个校验方法;
  51 + MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
  52 + if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) {
  53 + throw exception(PRO_FEEDBACK_WORK_ORDER_NOT_CONFIRMED);
  54 + }
  55 + // TODO @芋艿:校验 pro_task 存在且未完成(待 pro_task 服务迁移)
  56 +
  57 + // TODO @AI:2 和 3 融合成 插入;
  58 + // 2. 自动生成报工单编号
  59 + MesProFeedbackDO feedback = BeanUtils.toBean(createReqVO, MesProFeedbackDO.class);
  60 + // TODO @AI:这个 id 前端生成,createReqVO 必须传递;
  61 + feedback.setCode("FB" + IdUtil.getSnowflakeNextIdStr());
  62 + feedback.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus());
  63 +
  64 + // 3. 插入
  65 + feedbackMapper.insert(feedback);
  66 + return feedback.getId();
  67 + }
  68 +
  69 + @Override
  70 + public void updateFeedback(MesProFeedbackSaveReqVO updateReqVO) {
  71 + // 1.1 校验存在
  72 + MesProFeedbackDO feedback = validateFeedbackExists(updateReqVO.getId());
  73 + // 1.2 只能修改草稿状态
  74 + // TODO @AI:抽个校验方法,校验状态为草稿;
  75 + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) {
  76 + throw exception(PRO_FEEDBACK_NOT_PREPARE);
  77 + }
  78 +
  79 + // 2. 更新
  80 + MesProFeedbackDO updateObj = BeanUtils.toBean(updateReqVO, MesProFeedbackDO.class);
  81 + feedbackMapper.updateById(updateObj);
  82 + }
  83 +
  84 + @Override
  85 + public void deleteFeedback(Long id) {
  86 + // 1.1 校验存在
  87 + MesProFeedbackDO feedback = validateFeedbackExists(id);
  88 + // 1.2 只能删除草稿状态
  89 + // TODO @AI:抽个校验方法,校验状态为草稿;
  90 + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) {
  91 + throw exception(PRO_FEEDBACK_NOT_PREPARE);
  92 + }
  93 +
  94 + // 2. 删除
  95 + feedbackMapper.deleteById(id);
  96 + }
  97 +
  98 + @Override
  99 + public MesProFeedbackDO getFeedback(Long id) {
  100 + return feedbackMapper.selectById(id);
  101 + }
  102 +
  103 + @Override
  104 + public PageResult<MesProFeedbackDO> getFeedbackPage(MesProFeedbackPageReqVO pageReqVO) {
  105 + return feedbackMapper.selectPage(pageReqVO);
  106 + }
  107 +
  108 + @Override
  109 + public void submitFeedback(Long id) {
  110 + // 1. 校验存在 + 草稿状态
  111 + MesProFeedbackDO feedback = validateFeedbackExists(id);
  112 + // TODO @AI:抽个校验方法,校验状态为草稿;
  113 + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) {
  114 + throw exception(PRO_FEEDBACK_STATUS_ERROR);
  115 + }
  116 +
  117 + // 2. 更新状态为审批中,记录报工人和报工时间
  118 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
  119 + .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
  120 + .setFeedbackUserId(getLoginUserId())
  121 + .setFeedbackTime(LocalDateTime.now()));
  122 + }
  123 +
  124 + @Override
  125 + public void rejectFeedback(Long id) {
  126 + // 1. 校验存在 + 审批中状态
  127 + MesProFeedbackDO feedback = validateFeedbackExists(id);
  128 + // TODO @AI:抽个校验方法,校验状态为审批中;
  129 + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus())) {
  130 + throw exception(PRO_FEEDBACK_NOT_APPROVING);
  131 + }
  132 +
  133 + // 2. 更新状态为草稿
  134 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
  135 + .setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus()));
  136 + }
  137 +
  138 + @Override
  139 + @Transactional(rollbackFor = Exception.class)
  140 + public void executeFeedback(Long id) {
  141 + // 1. 校验存在 + 审批中状态
  142 + MesProFeedbackDO feedback = validateFeedbackExists(id);
  143 + // TODO @AI:抽个校验方法,校验状态为审批中;
  144 + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus())) {
  145 + throw exception(PRO_FEEDBACK_NOT_APPROVING);
  146 + }
  147 +
  148 + // 2.1 查询工序的 checkFlag,决定目标状态
  149 + boolean checkFlag = getCheckFlag(feedback.getRouteId(), feedback.getProcessId());
  150 + Integer targetStatus = checkFlag
  151 + ? MesProFeedbackStatusEnum.UNCHECK.getStatus() // 需要检验 → 待检验
  152 + : MesProFeedbackStatusEnum.FINISHED.getStatus(); // 无需检验 → 已完成
  153 + // 2.2 更新状态 + 审核人
  154 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
  155 + .setStatus(targetStatus)
  156 + .setApproveUserId(getLoginUserId()));
  157 +
  158 + // 4. 如果不需要检验(直接完成),执行后续操作
  159 + // TODO @AI:BoolUtil.isFalse(checkFlag)
  160 + if (!checkFlag) {
  161 + // TODO @芋艿:WM 物料消耗(WmItemConsumeService)未迁移,此处需补充物料消耗逻辑
  162 + // TODO @芋艿:WM 产品产出(WmProductProduceService)未迁移,此处需补充产品产出逻辑
  163 + // TODO @芋艿:updateProTaskAndWorkorder 待 pro_task 服务迁移后补充级联更新任务/工单的已生产数量
  164 + }
  165 + }
  166 +
  167 + @Override
  168 + public void cancelFeedback(Long id) {
  169 + // 1.1 校验存在
  170 + MesProFeedbackDO feedback = validateFeedbackExists(id);
  171 + // 1.2 只有草稿或审批中可以取消
  172 + // TODO @AI: ObjUtils.equalsAny,看起来更简洁;
  173 + if (ObjUtil.equal(feedback.getStatus(), MesProFeedbackStatusEnum.FINISHED.getStatus())
  174 + || ObjUtil.equal(feedback.getStatus(), MesProFeedbackStatusEnum.CANCELED.getStatus())) {
  175 + throw exception(PRO_FEEDBACK_STATUS_ERROR);
  176 + }
  177 +
  178 + // 2. 更新状态为已取消
  179 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
  180 + .setStatus(MesProFeedbackStatusEnum.CANCELED.getStatus()));
  181 + }
  182 +
  183 + // ==================== 校验方法 ====================
  184 +
  185 + private MesProFeedbackDO validateFeedbackExists(Long id) {
  186 + MesProFeedbackDO feedback = feedbackMapper.selectById(id);
  187 + if (feedback == null) {
  188 + throw exception(PRO_FEEDBACK_NOT_EXISTS);
  189 + }
  190 + return feedback;
  191 + }
  192 +
  193 + /**
  194 + * 获取指定工艺路线中指定工序的 checkFlag
  195 + *
  196 + * @param routeId 工艺路线编号
  197 + * @param processId 工序编号
  198 + * @return 是否需要检验
  199 + */
  200 + private boolean getCheckFlag(Long routeId, Long processId) {
  201 + List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
  202 + // TODO @AI:CollUtil findOne 更简洁;
  203 + return routeProcesses.stream()
  204 + .filter(rp -> ObjUtil.equal(rp.getProcessId(), processId))
  205 + .findFirst()
  206 + .map(rp -> Boolean.TRUE.equals(rp.getCheckFlag()))
  207 + .orElse(false);
  208 + }
  209 +
  210 +}