package cn.iocoder.yudao.module.mes.controller.admin.pro.batch; 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.bagcode.vo.MesProBagCodeRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchTraceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.batch.vo.MesProBatchWorkerRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchWorkerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils; import cn.iocoder.yudao.module.mes.framework.qrcode.QrLabelInfo; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; 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.Parameters; 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.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_BATCH_NOT_EXISTS; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_EXPORT_TOO_MANY; @Tag(name = "管理后台 - MES 生产批次") @RestController @RequestMapping("/mes/pro/batch") @Validated public class MesProBatchController { @Resource private MesProBatchService batchService; @Resource private AdminUserApi adminUserApi; @Resource private MesMdmItemService mdmItemService; @Resource private MesProPalletService palletService; @Resource private MesProBagCodeService bagCodeService; @PostMapping("/create") @Operation(summary = "创建生产批次(自动生成批次号)") @PreAuthorize("@ss.hasPermission('mes:pro-batch:create')") public CommonResult createBatch(@Valid @RequestBody MesProBatchSaveReqVO createReqVO) { return success(batchService.createBatch(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新生产批次") @PreAuthorize("@ss.hasPermission('mes:pro-batch:update')") public CommonResult updateBatch(@Valid @RequestBody MesProBatchSaveReqVO updateReqVO) { batchService.updateBatch(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除生产批次") @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('mes:pro-batch:delete')") public CommonResult deleteBatch(@RequestParam("id") Long id) { batchService.deleteBatch(id); return success(true); } @PutMapping("/update-status") @Operation(summary = "更新生产批次状态") @Parameters({ @Parameter(name = "id", description = "编号", required = true, example = "1024"), @Parameter(name = "status", description = "目标状态(字典 mes_pro_batch_status)", required = true, example = "20") }) @PreAuthorize("@ss.hasPermission('mes:pro-batch:update')") public CommonResult updateBatchStatus(@RequestParam("id") Long id, @RequestParam("status") Integer status) { batchService.updateBatchStatus(id, status); return success(true); } @GetMapping("/get") @Operation(summary = "获得生产批次详情(含人员配置)") @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('mes:pro-batch:query')") public CommonResult getBatch(@RequestParam("id") Long id) { MesProBatchDO batch = batchService.getBatch(id); if (batch == null) { return success(null); } MesProBatchRespVO respVO = buildBatchRespVOList(Collections.singletonList(batch)).get(0); // 填充人员配置 respVO.setWorkers(buildWorkerRespVOList(batchService.getBatchWorkerListByBatchId(id))); return success(respVO); } @GetMapping("/page") @Operation(summary = "获得生产批次分页") @PreAuthorize("@ss.hasPermission('mes:pro-batch:query')") public CommonResult> getBatchPage(@Valid MesProBatchPageReqVO pageReqVO) { PageResult pageResult = batchService.getBatchPage(pageReqVO); return success(new PageResult<>(buildBatchRespVOList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/simple-list") @Operation(summary = "获得生产批次精简列表(供下拉选择)") @PreAuthorize("@ss.hasPermission('mes:pro-batch:query')") public CommonResult> getBatchSimpleList() { MesProBatchPageReqVO pageReqVO = new MesProBatchPageReqVO(); pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = batchService.getBatchPage(pageReqVO).getList(); return success(BeanUtils.toBean(list, MesProBatchRespVO.class)); } @GetMapping("/export-excel") @Operation(summary = "导出生产批次 Excel") @PreAuthorize("@ss.hasPermission('mes:pro-batch:export')") @ApiAccessLog(operateType = EXPORT) public void exportBatchExcel(@Valid MesProBatchPageReqVO pageReqVO, HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); List list = batchService.getBatchPage(pageReqVO).getList(); List voList = buildBatchRespVOList(list); ExcelUtils.write(response, "生产批次.xls", "数据", MesProBatchRespVO.class, voList); } @GetMapping("/get-by-code") @Operation(summary = "扫码追溯:根据批次号(二维码内容)反查批次、人员、托盘、袋码信息") @Parameter(name = "code", description = "批次号(二维码内容)", required = true, example = "PC20260818L01001") @PreAuthorize("@ss.hasPermission('mes:pro-batch:query')") public CommonResult getBatchByCode(@RequestParam("code") String code) { MesProBatchDO batch = batchService.getBatchByCode(code); if (batch == null) { throw exception(PRO_BATCH_NOT_EXISTS); } return success(buildBatchTraceRespVO(batch)); } @GetMapping("/export-qr") @Operation(summary = "批量导出批次二维码图片 ZIP(批次图 + 该批次全部托盘图)") @Parameter(name = "batchId", description = "生产批次编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('mes:pro-batch:export')") @ApiAccessLog(operateType = EXPORT) public void exportBatchQr(@RequestParam("batchId") Long batchId, HttpServletResponse response) throws IOException { MesProBatchDO batch = batchService.getBatch(batchId); if (batch == null) { throw exception(PRO_BATCH_NOT_EXISTS); } List pallets = palletService.getPalletListByBatchId(batchId); if (pallets.size() + 1 > QrCodeImageUtils.MAX_QR_EXPORT) { throw exception(PRO_QR_EXPORT_TOO_MANY); } QrCodeImageUtils.writeZip(response, "批次二维码-" + batch.getCode(), buildBatchQrLabels(batch, pallets)); } // ==================== 拼接 VO ==================== private List buildBatchRespVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } // 1. 获得关联数据(责任人、品种物料) Map userMap = adminUserApi.getUserMap( convertSet(list, MesProBatchDO::getOwnerUserId)); Map itemMap = mdmItemService.getItemMap( convertSet(list, MesProBatchDO::getItemId)); // 2. 构建结果 return BeanUtils.toBean(list, MesProBatchRespVO.class, vo -> { MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname())); MapUtils.findAndThen(itemMap, vo.getItemId(), item -> { vo.setItemCode(item.getCode()); vo.setItemName(item.getName()); vo.setItemSpecification(item.getSpecification()); }); }); } private List buildWorkerRespVOList(List workers) { if (CollUtil.isEmpty(workers)) { return Collections.emptyList(); } Set userIds = new HashSet<>(convertList(workers, MesProBatchWorkerDO::getUserId)); Map userMap = adminUserApi.getUserMap(userIds); return BeanUtils.toBean(workers, MesProBatchWorkerRespVO.class, vo -> MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserName(user.getNickname()))); } private MesProBatchTraceRespVO buildBatchTraceRespVO(MesProBatchDO batch) { MesProBatchTraceRespVO respVO = BeanUtils.toBean(batch, MesProBatchTraceRespVO.class); // 品种信息 if (batch.getItemId() != null) { MesMdmItemDO item = mdmItemService.getItem(batch.getItemId()); if (item != null) { respVO.setItemCode(item.getCode()) .setItemName(item.getName()) .setItemSpecification(item.getSpecification()); } } // 责任人 if (batch.getOwnerUserId() != null) { AdminUserRespDTO user = adminUserApi.getUser(batch.getOwnerUserId()); if (user != null) { respVO.setOwnerUserName(user.getNickname()); } } // 人员配置 respVO.setWorkers(buildWorkerRespVOList(batchService.getBatchWorkerListByBatchId(batch.getId()))); // 托盘列表 respVO.setPallets(BeanUtils.toBean(palletService.getPalletListByBatchId(batch.getId()), MesProPalletRespVO.class)); // 袋码列表(封顶 2000,bagTotal 记录总数) List bags = bagCodeService.getBagCodeListByBatchId(batch.getId()); respVO.setBagTotal(bags.size()); if (bags.size() > QrCodeImageUtils.MAX_QR_EXPORT) { bags = bags.subList(0, QrCodeImageUtils.MAX_QR_EXPORT); } respVO.setBags(buildBagCodeRespVOList(bags)); return respVO; } private List buildBagCodeRespVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } // 获得批次的产线信息,拼接袋码的产线编码/名称 Map batchMap = convertMap( batchService.getBatchList(convertSet(list, MesProBagCodeDO::getBatchId)), MesProBatchDO::getId); return BeanUtils.toBean(list, MesProBagCodeRespVO.class, vo -> MapUtils.findAndThen(batchMap, vo.getBatchId(), batch -> vo.setLineCode(batch.getLineCode()).setLineName(batch.getLineName()))); } private List buildBatchQrLabels(MesProBatchDO batch, List pallets) { // 获得关联数据(品种、责任人)——批次与托盘同属一批,共用同一份 Map itemMap = mdmItemService.getItemMap( convertSet(Collections.singletonList(batch), MesProBatchDO::getItemId)); Map userMap = adminUserApi.getUserMap( convertSet(Collections.singletonList(batch), MesProBatchDO::getOwnerUserId)); // 构建标签:批次图 + 全部托盘图 List labels = new ArrayList<>(pallets.size() + 1); labels.add(MesProQrLabelBuilder.buildBatchLabel(batch, itemMap, userMap)); for (MesProPalletDO pallet : pallets) { labels.add(MesProQrLabelBuilder.buildPalletLabel(pallet, batch, itemMap, userMap)); } return labels; } }