diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java new file mode 100644 index 0000000..b029c36 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java @@ -0,0 +1,272 @@ +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackPageReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackRespVO; +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; +import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService; +import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; +import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; +import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteService; +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +@Tag(name = "管理后台 - MES 生产报工") +@RestController +@RequestMapping("/mes/pro/feedback") +@Validated +public class MesProFeedbackController { + + @Resource + private MesProFeedbackService feedbackService; + + @Resource + private MesMdWorkstationService workstationService; + + @Resource + private MesProRouteService routeService; + + @Resource + private MesProProcessService processService; + + @Resource + private MesProWorkOrderService workOrderService; + + @Resource + private MesMdItemService itemService; + + @Resource + private MesMdUnitMeasureService unitMeasureService; + + @Resource + private MesProRouteProcessService routeProcessService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建生产报工") + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:create')") + public CommonResult createFeedback(@Valid @RequestBody MesProFeedbackSaveReqVO createReqVO) { + return success(feedbackService.createFeedback(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新生产报工") + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')") + public CommonResult updateFeedback(@Valid @RequestBody MesProFeedbackSaveReqVO updateReqVO) { + feedbackService.updateFeedback(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除生产报工") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:delete')") + public CommonResult deleteFeedback(@RequestParam("id") Long id) { + feedbackService.deleteFeedback(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得生产报工") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')") + public CommonResult getFeedback(@RequestParam("id") Long id) { + MesProFeedbackDO feedback = feedbackService.getFeedback(id); + if (feedback == null) { + return success(null); + } + return success(buildFeedbackRespVOList(List.of(feedback)).get(0)); + } + + @GetMapping("/page") + @Operation(summary = "获得生产报工分页") + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')") + public CommonResult> getFeedbackPage(@Valid MesProFeedbackPageReqVO pageReqVO) { + PageResult pageResult = feedbackService.getFeedbackPage(pageReqVO); + return success(new PageResult<>(buildFeedbackRespVOList(pageResult.getList()), pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出生产报工 Excel") + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportFeedbackExcel(@Valid MesProFeedbackPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = feedbackService.getFeedbackPage(pageReqVO).getList(); + List voList = buildFeedbackRespVOList(list); + ExcelUtils.write(response, "生产报工.xls", "数据", MesProFeedbackRespVO.class, voList); + } + + @PutMapping("/submit") + @Operation(summary = "提交报工") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')") + public CommonResult submitFeedback(@RequestParam("id") Long id) { + feedbackService.submitFeedback(id); + return success(true); + } + + @PutMapping("/reject") + @Operation(summary = "驳回报工") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')") + public CommonResult rejectFeedback(@RequestParam("id") Long id) { + feedbackService.rejectFeedback(id); + return success(true); + } + + @PutMapping("/execute") + @Operation(summary = "执行报工") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')") + public CommonResult executeFeedback(@RequestParam("id") Long id) { + feedbackService.executeFeedback(id); + return success(true); + } + + @PutMapping("/cancel") + @Operation(summary = "取消报工") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')") + public CommonResult cancelFeedback(@RequestParam("id") Long id) { + feedbackService.cancelFeedback(id); + return success(true); + } + + // ==================== 拼接 VO ==================== + + private List buildFeedbackRespVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 1.1 物料、计量单位 + Map itemMap = itemService.getItemMap( + convertSet(list, MesProFeedbackDO::getItemId)); + Map unitMeasureMap = unitMeasureService.getUnitMeasureMap( + convertSet(list, MesProFeedbackDO::getUnitMeasureId)); + // 1.2 工单 + Map workOrderMap = workOrderService.getWorkOrderMap( + convertSet(list, MesProFeedbackDO::getWorkOrderId)); + // 1.3 工作站(逐个查询,因为没有批量方法) + // TODO @AI:getWorkstationMap 必须有办法 + Set workstationIds = convertSet(list, MesProFeedbackDO::getWorkstationId); + Map workstationMap = new HashMap<>(); + workstationIds.forEach(wsId -> { + MesMdWorkstationDO ws = workstationService.getWorkstation(wsId); + if (ws != null) { + workstationMap.put(wsId, ws); + } + }); + // 1.4 工艺路线(逐个查询) + // TODO @AI:getRouteMap 必须有办法;里面调用 getRouteList;如果不行,就加个; + Set routeIds = convertSet(list, MesProFeedbackDO::getRouteId); + Map routeMap = new HashMap<>(); + routeIds.forEach(rId -> { + MesProRouteDO route = routeService.getRoute(rId); + if (route != null) { + routeMap.put(rId, route); + } + }); + // 1.5 工序 + // TODO @AI:getProcessMap 必须有办法;里面调用 getProcessList;如果不行,就加个; + Set processIds = convertSet(list, MesProFeedbackDO::getProcessId); + List processList = processService.getProcessList(new ArrayList<>(processIds)); + Map processMap = processList.stream() + .collect(Collectors.toMap(MesProProcessDO::getId, p -> p, (a, b) -> a)); + // 1.6 工序的 checkFlag(按路线维度缓存) + // TODO @AI:collutils 里面有个 convertMultiMap 的方法; + Map> routeProcessMap = new HashMap<>(); + routeIds.forEach(rId -> routeProcessMap.put(rId, routeProcessService.getRouteProcessListByRouteId(rId))); + // 1.7 报工人/审核人 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(list, feedback -> { + // TODO @AI:必须要判空,里面已经处理了; + Set userIds = new HashSet<>(); + if (feedback.getFeedbackUserId() != null) { + userIds.add(feedback.getFeedbackUserId()); + } + if (feedback.getApproveUserId() != null) { + userIds.add(feedback.getApproveUserId()); + } + return userIds.stream(); + })); + + // 2. 拼接 VO + return BeanUtils.toBean(list, MesProFeedbackRespVO.class, vo -> { + // 工作站 + findAndThen(workstationMap, vo.getWorkstationId(), ws -> + vo.setWorkstationCode(ws.getCode()).setWorkstationName(ws.getName())); + // 工艺路线 + findAndThen(routeMap, vo.getRouteId(), route -> + vo.setRouteCode(route.getCode())); + // 工序 + findAndThen(processMap, vo.getProcessId(), process -> + vo.setProcessCode(process.getCode()).setProcessName(process.getName())); + // checkFlag + // TODO @AI:这个判断有点复杂,是不是上面构建更合适的 map? + List rps = routeProcessMap.get(vo.getRouteId()); + if (rps != null) { + rps.stream() + .filter(rp -> rp.getProcessId().equals(vo.getProcessId())) + .findFirst() + .ifPresent(rp -> vo.setCheckFlag(Boolean.TRUE.equals(rp.getCheckFlag()))); + } + // 工单 + findAndThen(workOrderMap, vo.getWorkOrderId(), wo -> + vo.setWorkOrderCode(wo.getCode()).setWorkOrderName(wo.getName())); + // TODO @芋艿:task code 待 pro_task 服务迁移后补充 + // 物料、计量单位 + findAndThen(itemMap, vo.getItemId(), item -> + vo.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpec(item.getSpecification())); + findAndThen(unitMeasureMap, vo.getUnitMeasureId(), unit -> + vo.setUnitMeasureName(unit.getName())); + // 报工人、审核人 + findAndThen(userMap, vo.getFeedbackUserId(), user -> + vo.setFeedbackUserNickname(user.getNickname())); + findAndThen(userMap, vo.getApproveUserId(), user -> + vo.setApproveUserNickname(user.getNickname())); + }); + } + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackPageReqVO.java new file mode 100644 index 0000000..784893f --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackPageReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - MES 生产报工分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MesProFeedbackPageReqVO extends PageParam { + + @Schema(description = "报工单编号", example = "FB202503") + private String code; + + @Schema(description = "报工类型", example = "1") + private Integer type; + + @Schema(description = "生产工单编号", example = "1") + private Long workOrderId; + + @Schema(description = "状态", example = "0") + private Integer status; + + @Schema(description = "报工时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] feedbackTime; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java new file mode 100644 index 0000000..47ae6af --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java @@ -0,0 +1,185 @@ +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.mes.enums.DictTypeConstants; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - MES 生产报工 Response VO") +@Data +@ExcelIgnoreUnannotated +public class MesProFeedbackRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "报工单编号", example = "FB202503160001") + @ExcelProperty("报工单编号") + private String code; + + @Schema(description = "报工类型", example = "1") + @ExcelProperty(value = "报工类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.MES_PRO_FEEDBACK_TYPE) + private Integer type; + + @Schema(description = "报工途径", example = "PC") + @ExcelProperty("报工途径") + private String channel; + + @Schema(description = "报工时间") + @ExcelProperty("报工时间") + private LocalDateTime feedbackTime; + + @Schema(description = "工作站编号", example = "1") + private Long workstationId; + + @Schema(description = "工作站编码", example = "WS-001") + @ExcelProperty("工作站编码") + private String workstationCode; + + @Schema(description = "工作站名称", example = "注塑工作站") + @ExcelProperty("工作站名称") + private String workstationName; + + @Schema(description = "工艺路线编号", example = "1") + private Long routeId; + + @Schema(description = "工艺路线编码", example = "RT-001") + private String routeCode; + + @Schema(description = "工序编号", example = "1") + private Long processId; + + @Schema(description = "工序编码", example = "PR-001") + @ExcelProperty("工序编码") + private String processCode; + + @Schema(description = "工序名称", example = "注塑") + @ExcelProperty("工序名称") + private String processName; + + @Schema(description = "是否需要检验") + private Boolean checkFlag; + + @Schema(description = "生产工单编号", example = "1") + private Long workOrderId; + + @Schema(description = "工单编码", example = "MO202503120008") + @ExcelProperty("工单编码") + private String workOrderCode; + + @Schema(description = "工单名称", example = "博世螺丝刀") + @ExcelProperty("工单名称") + private String workOrderName; + + @Schema(description = "生产任务编号", example = "1") + private Long taskId; + + @Schema(description = "任务编码", example = "PT202503150001") + private String taskCode; + + @Schema(description = "产品物料编号", example = "75") + private Long itemId; + + @Schema(description = "物料编码", example = "I-075") + @ExcelProperty("物料编码") + private String itemCode; + + @Schema(description = "物料名称", example = "博世螺丝刀") + @ExcelProperty("物料名称") + private String itemName; + + @Schema(description = "规格型号", example = "一字型") + @ExcelProperty("规格型号") + private String itemSpec; + + @Schema(description = "单位编号", example = "202") + private Long unitMeasureId; + + @Schema(description = "单位名称", example = "个") + @ExcelProperty("单位") + private String unitMeasureName; + + @Schema(description = "过期日期") + private LocalDateTime expireDate; + + @Schema(description = "批次号", example = "B20250315001") + @ExcelProperty("批次号") + private String batchCode; + + @Schema(description = "排产数量", example = "5000.00") + @ExcelProperty("排产数量") + private BigDecimal scheduledQuantity; + + @Schema(description = "本次报工数量", example = "500.00") + @ExcelProperty("报工数量") + private BigDecimal feedbackQuantity; + + @Schema(description = "合格品数量", example = "490.00") + @ExcelProperty("合格品数量") + private BigDecimal qualifiedQuantity; + + @Schema(description = "不良品数量", example = "10.00") + @ExcelProperty("不良品数量") + private BigDecimal unqualifiedQuantity; + + @Schema(description = "待检测数量", example = "0") + @ExcelProperty("待检测数量") + private BigDecimal uncheckQuantity; + + @Schema(description = "工废数量", example = "6.00") + private BigDecimal laborScrapQuantity; + + @Schema(description = "料废数量", example = "4.00") + private BigDecimal materialScrapQuantity; + + @Schema(description = "其他废品数量", example = "0") + private BigDecimal otherScrapQuantity; + + @Schema(description = "报工用户编号", example = "1") + private Long feedbackUserId; + + @Schema(description = "报工人昵称", example = "张三") + @ExcelProperty("报工人") + private String feedbackUserNickname; + + @Schema(description = "审核用户编号", example = "1") + private Long approveUserId; + + @Schema(description = "审核人昵称", example = "李四") + @ExcelProperty("审核人") + private String approveUserNickname; + + @Schema(description = "状态", example = "0") + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.MES_PRO_FEEDBACK_STATUS) + private Integer status; + + @Schema(description = "备注", example = "备注") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "预留字段1") + private String attribute1; + + @Schema(description = "预留字段2") + private String attribute2; + + @Schema(description = "预留字段3") + private Integer attribute3; + + @Schema(description = "预留字段4") + private Integer attribute4; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java new file mode 100644 index 0000000..430f888 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - MES 生产报工新增/修改 Request VO") +@Data +public class MesProFeedbackSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "报工类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "报工类型不能为空") + private Integer type; + + @Schema(description = "报工途径", example = "PC") + private String channel; + + @Schema(description = "工作站编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "工作站不能为空") + private Long workstationId; + + @Schema(description = "工艺路线编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "工艺路线不能为空") + private Long routeId; + + @Schema(description = "工序编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "工序不能为空") + private Long processId; + + @Schema(description = "生产工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "生产工单不能为空") + private Long workOrderId; + + @Schema(description = "生产任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "生产任务不能为空") + private Long taskId; + + @Schema(description = "产品物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "75") + @NotNull(message = "产品物料不能为空") + private Long itemId; + + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "202") + @NotNull(message = "单位不能为空") + private Long unitMeasureId; + + @Schema(description = "过期日期") + private LocalDateTime expireDate; + + @Schema(description = "批次号", example = "B20250315001") + private String batchCode; + + @Schema(description = "排产数量", example = "5000.00") + private BigDecimal scheduledQuantity; + + @Schema(description = "本次报工数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "500.00") + @NotNull(message = "报工数量不能为空") + private BigDecimal feedbackQuantity; + + @Schema(description = "合格品数量", example = "490.00") + private BigDecimal qualifiedQuantity; + + @Schema(description = "不良品数量", example = "10.00") + private BigDecimal unqualifiedQuantity; + + @Schema(description = "待检测数量", example = "0") + private BigDecimal uncheckQuantity; + + @Schema(description = "工废数量", example = "6.00") + private BigDecimal laborScrapQuantity; + + @Schema(description = "料废数量", example = "4.00") + private BigDecimal materialScrapQuantity; + + @Schema(description = "其他废品数量", example = "0") + private BigDecimal otherScrapQuantity; + + @Schema(description = "备注", example = "备注") + private String remark; + + @Schema(description = "预留字段1") + private String attribute1; + + @Schema(description = "预留字段2") + private String attribute2; + + @Schema(description = "预留字段3") + private Integer attribute3; + + @Schema(description = "预留字段4") + private Integer attribute4; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java new file mode 100644 index 0000000..9c3330b --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java @@ -0,0 +1,176 @@ +package cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * MES 生产报工 DO + * + * @author 芋道源码 + */ +@TableName("mes_pro_feedback") +@KeySequence("mes_pro_feedback_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MesProFeedbackDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 报工单编号 + */ + private String code; + /** + * 报工类型 + * + * 枚举 {@link MesProFeedbackTypeEnum} + */ + private Integer type; + // TODO @AI:这里需要字典么? + /** + * 报工途径 + */ + private String channel; + /** + * 报工时间 + */ + private LocalDateTime feedbackTime; + /** + * 工作站编号 + * + * 关联 {@link MesMdWorkstationDO#getId()} + */ + private Long workstationId; + /** + * 工艺路线编号 + * + * 关联 {@link MesProRouteDO#getId()} + */ + private Long routeId; + /** + * 工序编号 + * + * 关联 {@link MesProProcessDO#getId()} + */ + private Long processId; + /** + * 生产工单编号 + * + * 关联 {@link MesProWorkOrderDO#getId()} + */ + private Long workOrderId; + // TODO @芋艿:这里待关联; + /** + * 生产任务编号 + */ + private Long taskId; + /** + * 产品物料编号 + * + * 关联 {@link MesMdItemDO#getId()} + */ + private Long itemId; + /** + * 单位编号 + * + * 关联 {@link MesMdUnitMeasureDO#getId()} + */ + private Long unitMeasureId; + /** + * 过期日期 + */ + private LocalDateTime expireDate; + /** + * 批次号 + */ + private String batchCode; + /** + * 排产数量 + */ + private BigDecimal scheduledQuantity; + /** + * 本次报工数量 + */ + private BigDecimal feedbackQuantity; + /** + * 合格品数量 + */ + private BigDecimal qualifiedQuantity; + /** + * 不良品数量 + */ + private BigDecimal unqualifiedQuantity; + /** + * 待检测数量 + */ + private BigDecimal uncheckQuantity; + /** + * 工废数量 + */ + private BigDecimal laborScrapQuantity; + /** + * 料废数量 + */ + private BigDecimal materialScrapQuantity; + /** + * 其他废品数量 + */ + private BigDecimal otherScrapQuantity; + // TODO @AI:feedbackUserId、approveUserId 关联字段 + /** + * 报工用户编号 + */ + private Long feedbackUserId; + /** + * 审核用户编号 + */ + private Long approveUserId; + /** + * 状态 + * + * 枚举 {@link MesProFeedbackStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 预留字段1 + */ + private String attribute1; + /** + * 预留字段2 + */ + private String attribute2; + /** + * 预留字段3 + */ + private Integer attribute3; + /** + * 预留字段4 + */ + private Integer attribute4; + +} diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java new file mode 100644 index 0000000..384a007 --- /dev/null +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java @@ -0,0 +1,210 @@ +package cn.iocoder.yudao.module.mes.service.pro.feedback; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackPageReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; +import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; +import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; +import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; +import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; +import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; + +/** + * MES 生产报工 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class MesProFeedbackServiceImpl implements MesProFeedbackService { + + @Resource + private MesProFeedbackMapper feedbackMapper; + + @Resource + private MesProWorkOrderService workOrderService; + + @Resource + private MesProRouteProcessService routeProcessService; + + @Override + public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) { + // 1.1 校验工单已确认 + // TODO @AI: workOrderService 里抽个校验方法; + MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId()); + if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) { + throw exception(PRO_FEEDBACK_WORK_ORDER_NOT_CONFIRMED); + } + // TODO @芋艿:校验 pro_task 存在且未完成(待 pro_task 服务迁移) + + // TODO @AI:2 和 3 融合成 插入; + // 2. 自动生成报工单编号 + MesProFeedbackDO feedback = BeanUtils.toBean(createReqVO, MesProFeedbackDO.class); + // TODO @AI:这个 id 前端生成,createReqVO 必须传递; + feedback.setCode("FB" + IdUtil.getSnowflakeNextIdStr()); + feedback.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus()); + + // 3. 插入 + feedbackMapper.insert(feedback); + return feedback.getId(); + } + + @Override + public void updateFeedback(MesProFeedbackSaveReqVO updateReqVO) { + // 1.1 校验存在 + MesProFeedbackDO feedback = validateFeedbackExists(updateReqVO.getId()); + // 1.2 只能修改草稿状态 + // TODO @AI:抽个校验方法,校验状态为草稿; + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) { + throw exception(PRO_FEEDBACK_NOT_PREPARE); + } + + // 2. 更新 + MesProFeedbackDO updateObj = BeanUtils.toBean(updateReqVO, MesProFeedbackDO.class); + feedbackMapper.updateById(updateObj); + } + + @Override + public void deleteFeedback(Long id) { + // 1.1 校验存在 + MesProFeedbackDO feedback = validateFeedbackExists(id); + // 1.2 只能删除草稿状态 + // TODO @AI:抽个校验方法,校验状态为草稿; + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) { + throw exception(PRO_FEEDBACK_NOT_PREPARE); + } + + // 2. 删除 + feedbackMapper.deleteById(id); + } + + @Override + public MesProFeedbackDO getFeedback(Long id) { + return feedbackMapper.selectById(id); + } + + @Override + public PageResult getFeedbackPage(MesProFeedbackPageReqVO pageReqVO) { + return feedbackMapper.selectPage(pageReqVO); + } + + @Override + public void submitFeedback(Long id) { + // 1. 校验存在 + 草稿状态 + MesProFeedbackDO feedback = validateFeedbackExists(id); + // TODO @AI:抽个校验方法,校验状态为草稿; + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.PREPARE.getStatus())) { + throw exception(PRO_FEEDBACK_STATUS_ERROR); + } + + // 2. 更新状态为审批中,记录报工人和报工时间 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id) + .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus()) + .setFeedbackUserId(getLoginUserId()) + .setFeedbackTime(LocalDateTime.now())); + } + + @Override + public void rejectFeedback(Long id) { + // 1. 校验存在 + 审批中状态 + MesProFeedbackDO feedback = validateFeedbackExists(id); + // TODO @AI:抽个校验方法,校验状态为审批中; + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus())) { + throw exception(PRO_FEEDBACK_NOT_APPROVING); + } + + // 2. 更新状态为草稿 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id) + .setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void executeFeedback(Long id) { + // 1. 校验存在 + 审批中状态 + MesProFeedbackDO feedback = validateFeedbackExists(id); + // TODO @AI:抽个校验方法,校验状态为审批中; + if (ObjUtil.notEqual(feedback.getStatus(), MesProFeedbackStatusEnum.APPROVING.getStatus())) { + throw exception(PRO_FEEDBACK_NOT_APPROVING); + } + + // 2.1 查询工序的 checkFlag,决定目标状态 + boolean checkFlag = getCheckFlag(feedback.getRouteId(), feedback.getProcessId()); + Integer targetStatus = checkFlag + ? MesProFeedbackStatusEnum.UNCHECK.getStatus() // 需要检验 → 待检验 + : MesProFeedbackStatusEnum.FINISHED.getStatus(); // 无需检验 → 已完成 + // 2.2 更新状态 + 审核人 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id) + .setStatus(targetStatus) + .setApproveUserId(getLoginUserId())); + + // 4. 如果不需要检验(直接完成),执行后续操作 + // TODO @AI:BoolUtil.isFalse(checkFlag) + if (!checkFlag) { + // TODO @芋艿:WM 物料消耗(WmItemConsumeService)未迁移,此处需补充物料消耗逻辑 + // TODO @芋艿:WM 产品产出(WmProductProduceService)未迁移,此处需补充产品产出逻辑 + // TODO @芋艿:updateProTaskAndWorkorder 待 pro_task 服务迁移后补充级联更新任务/工单的已生产数量 + } + } + + @Override + public void cancelFeedback(Long id) { + // 1.1 校验存在 + MesProFeedbackDO feedback = validateFeedbackExists(id); + // 1.2 只有草稿或审批中可以取消 + // TODO @AI: ObjUtils.equalsAny,看起来更简洁; + if (ObjUtil.equal(feedback.getStatus(), MesProFeedbackStatusEnum.FINISHED.getStatus()) + || ObjUtil.equal(feedback.getStatus(), MesProFeedbackStatusEnum.CANCELED.getStatus())) { + throw exception(PRO_FEEDBACK_STATUS_ERROR); + } + + // 2. 更新状态为已取消 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id) + .setStatus(MesProFeedbackStatusEnum.CANCELED.getStatus())); + } + + // ==================== 校验方法 ==================== + + private MesProFeedbackDO validateFeedbackExists(Long id) { + MesProFeedbackDO feedback = feedbackMapper.selectById(id); + if (feedback == null) { + throw exception(PRO_FEEDBACK_NOT_EXISTS); + } + return feedback; + } + + /** + * 获取指定工艺路线中指定工序的 checkFlag + * + * @param routeId 工艺路线编号 + * @param processId 工序编号 + * @return 是否需要检验 + */ + private boolean getCheckFlag(Long routeId, Long processId) { + List routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId); + // TODO @AI:CollUtil findOne 更简洁; + return routeProcesses.stream() + .filter(rp -> ObjUtil.equal(rp.getProcessId(), processId)) + .findFirst() + .map(rp -> Boolean.TRUE.equals(rp.getCheckFlag())) + .orElse(false); + } + +}