package cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice; 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.crm.api.customer.CrmCustomerApi; import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticeLedgerRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticePageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticeRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.MesWmSalesNoticeSaveReqVO; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi; import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO; import cn.iocoder.yudao.module.mes.service.wm.document.MesWmDocumentRenderer; import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeLineService; import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService; import cn.iocoder.yudao.module.system.service.companyconfig.SystemCompanyConfigService; 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.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; 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.convertSet; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_SALES_NOTICE_NOT_EXISTS; @Tag(name = "管理后台 - MES 发货通知单") @RestController @RequestMapping("/mes/wm/sales-notice") @Validated public class MesWmSalesNoticeController { @Resource private MesWmSalesNoticeService salesNoticeService; @Resource private CrmCustomerApi crmCustomerApi; @Resource private MesWmSalesNoticeLineService salesNoticeLineService; @Resource private MdmItemApi mdmItemApi; @Resource private MdmUnitMeasureApi mdmUnitMeasureApi; @Resource private SystemCompanyConfigService companyConfigService; @Resource private cn.iocoder.yudao.module.system.api.user.AdminUserApi adminUserApi; @PostMapping("/create") @Operation(summary = "创建发货通知单") @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:create')") public CommonResult createSalesNotice(@Valid @RequestBody MesWmSalesNoticeSaveReqVO createReqVO) { return success(salesNoticeService.createSalesNotice(createReqVO)); } @PutMapping("/update") @Operation(summary = "修改发货通知单") @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:update')") public CommonResult updateSalesNotice(@Valid @RequestBody MesWmSalesNoticeSaveReqVO updateReqVO) { salesNoticeService.updateSalesNotice(updateReqVO); return success(true); } @DeleteMapping("/delete") @Operation(summary = "删除发货通知单") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:delete')") public CommonResult deleteSalesNotice(@RequestParam("id") Long id) { salesNoticeService.deleteSalesNotice(id); return success(true); } @GetMapping("/get") @Operation(summary = "获得发货通知单") @Parameter(name = "id", description = "编号", required = true, example = "1024") public CommonResult getSalesNotice(@RequestParam("id") Long id) { MesWmSalesNoticeDO notice = salesNoticeService.getSalesNotice(id); if (notice == null) { return success(null); } return success(buildRespVOList(Collections.singletonList(notice)).get(0)); } @GetMapping("/page") @Operation(summary = "获得发货通知单分页") public CommonResult> getSalesNoticePage( @Valid MesWmSalesNoticePageReqVO pageReqVO) { PageResult pageResult = salesNoticeService.getSalesNoticePage(pageReqVO); return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/ledger-page") @Operation(summary = "获得发货台账分页(按产品平铺)") public CommonResult> getSalesNoticeLedgerPage( @Valid MesWmSalesNoticePageReqVO pageReqVO) { int pageNo = pageReqVO.getPageNo(); int pageSize = pageReqVO.getPageSize(); // 1. 先查出符合条件的全部发货单,再平铺明细后手动分页(明细行数无法在 SQL 层直接分页) List notices; try { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); notices = salesNoticeService.getSalesNoticePage(pageReqVO).getList(); } finally { pageReqVO.setPageSize(pageSize); } if (CollUtil.isEmpty(notices)) { return success(new PageResult<>(Collections.emptyList(), 0L)); } List lines = salesNoticeLineService.getSalesNoticeLineListByNoticeIds( convertSet(notices, MesWmSalesNoticeDO::getId)); Map> lineMap = lines.stream() .collect(java.util.stream.Collectors.groupingBy(MesWmSalesNoticeLineDO::getNoticeId)); Map customerMap = crmCustomerApi.getCustomerMap( convertSet(notices, MesWmSalesNoticeDO::getClientId)); Map itemMap = mdmItemApi.getItemMap( convertSet(lines, MesWmSalesNoticeLineDO::getItemId)); Map unitMap = CollUtil.isEmpty(itemMap) ? Collections.emptyMap() : mdmUnitMeasureApi.getUnitMeasureList( convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)) .getCheckedData().stream() .collect(java.util.stream.Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e)); // 2. 按单据顺序平铺成「一行一个产品」 List rows = new ArrayList<>(); for (MesWmSalesNoticeDO notice : notices) { List noticeLines = lineMap.getOrDefault(notice.getId(), Collections.emptyList()); CrmCustomerRespDTO customer = customerMap.get(notice.getClientId()); for (MesWmSalesNoticeLineDO line : noticeLines) { MesWmSalesNoticeLedgerRespVO row = new MesWmSalesNoticeLedgerRespVO(); row.setNoticeId(notice.getId()); row.setNoticeCode(notice.getCode()); row.setSaleOrderCode(notice.getSaleOrderCode()); row.setClientId(notice.getClientId()); row.setClientName(customer != null ? customer.getName() : null); row.setSalesDate(notice.getSalesDate()); row.setRecipientName(notice.getRecipientName()); row.setRecipientAddress(notice.getRecipientAddress()); row.setLogisticsCompany(notice.getLogisticsCompany()); row.setExpressFee(notice.getExpressFee()); row.setInvoiceStatus(notice.getInvoiceStatus()); row.setStatus(notice.getStatus()); row.setOutStatus(notice.getOutStatus()); row.setLineId(line.getId()); row.setItemId(line.getItemId()); MdmItemRespDTO item = itemMap.get(line.getItemId()); row.setItemCode(item != null ? item.getCode() : null); row.setItemName(item != null ? item.getName() : null); row.setItemSpecification(item != null ? item.getSpecification() : null); MdmUnitMeasureRespDTO unit = item != null ? unitMap.get(item.getUnitMeasureId()) : null; row.setUnitName(unit != null ? unit.getName() : null); row.setQuantity(line.getQuantity()); row.setOutCount(line.getOutCount()); row.setBatchCode(line.getBatchCode()); row.setLineRemark(line.getRemark()); rows.add(row); } } // 3. 手动分页 long total = rows.size(); if (pageSize == PageParam.PAGE_SIZE_NONE) { return success(new PageResult<>(rows, total)); } int from = Math.min(Math.max(pageNo - 1, 0) * pageSize, rows.size()); int to = Math.min(from + pageSize, rows.size()); return success(new PageResult<>(rows.subList(from, to), total)); } @GetMapping("/export-excel") @Operation(summary = "导出发货通知单 Excel") @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:export')") @ApiAccessLog(operateType = EXPORT) public void exportSalesNoticeExcel(@Valid MesWmSalesNoticePageReqVO pageReqVO, HttpServletResponse response) throws IOException { pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); PageResult pageResult = salesNoticeService.getSalesNoticePage(pageReqVO); ExcelUtils.write(response, "发货通知单.xls", "数据", MesWmSalesNoticeRespVO.class, buildRespVOList(pageResult.getList())); } @GetMapping("/export-word") @Operation(summary = "导出发货单 Word(带电子章,可手填签字后归档)") @Parameter(name = "id", description = "发货通知单编号", required = true) @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:export')") @ApiAccessLog(operateType = EXPORT) public void exportSalesNoticeWord(@RequestParam("id") Long id, HttpServletResponse response) throws IOException { MesWmSalesNoticeDO notice = salesNoticeService.getSalesNotice(id); if (notice == null) { throw exception(WM_SALES_NOTICE_NOT_EXISTS); } List lines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(id); Map itemMap = mdmItemApi.getItemMap( convertSet(lines, MesWmSalesNoticeLineDO::getItemId)); Map unitMap = mdmUnitMeasureApi .getUnitMeasureList(convertSet(itemMap.values(), MdmItemRespDTO::getUnitMeasureId)) .getCheckedData().stream() .collect(java.util.stream.Collectors.toMap(MdmUnitMeasureRespDTO::getId, e -> e)); Map fields = new LinkedHashMap<>(); fields.put("clientName", resolveClientName(notice.getClientId())); fields.put("code", notice.getCode()); fields.put("salesDate", notice.getSalesDate() == null ? null : notice.getSalesDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); fields.put("saleOrderCode", notice.getSaleOrderCode()); fields.put("recipientName", notice.getRecipientName()); fields.put("recipientTelephone", notice.getRecipientTelephone()); fields.put("recipientAddress", notice.getRecipientAddress()); fields.put("logisticsCompany", notice.getLogisticsCompany()); fields.put("expressFee", notice.getExpressFee() == null ? "0.00" : notice.getExpressFee().toPlainString()); fields.put("creator", resolveCreatorName(notice.getCreator())); fields.put("remark", notice.getRemark()); List> items = new ArrayList<>(); for (MesWmSalesNoticeLineDO line : lines) { MdmItemRespDTO item = itemMap.get(line.getItemId()); Map row = new LinkedHashMap<>(); row.put("itemName", item != null ? item.getName() : null); row.put("itemSpecification", item != null ? item.getSpecification() : null); MdmUnitMeasureRespDTO unit = item != null ? unitMap.get(item.getUnitMeasureId()) : null; row.put("unitName", unit != null ? unit.getName() : null); row.put("quantity", line.getQuantity() == null ? null : line.getQuantity().toPlainString()); row.put("batchCode", line.getBatchCode()); row.put("itemRemark", line.getRemark()); items.add(row); } var companyConfig = companyConfigService.getCompanyConfig(); byte[] content = MesWmDocumentRenderer.renderSalesDelivery(fields, items, companyConfig != null ? companyConfig.getCompanyName() : null); writeWord(response, "发货单-" + notice.getCode() + ".docx", content); } private String resolveClientName(Long clientId) { if (clientId == null) { return null; } CrmCustomerRespDTO customer = crmCustomerApi.getCustomer(clientId); return customer != null ? customer.getName() : null; } private String resolveCreatorName(String creator) { if (creator == null) { return null; } try { var user = adminUserApi.getUser(Long.valueOf(creator)); return user != null ? user.getNickname() : null; } catch (NumberFormatException ignored) { return creator; } } private static void writeWord(HttpServletResponse response, String filename, byte[] content) throws IOException { String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20"); response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encoded); response.getOutputStream().write(content); } @PutMapping("/submit") @Operation(summary = "提交发货通知单审批") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:update')") public CommonResult submitSalesNotice(@RequestParam("id") Long id, @RequestParam(value = "processDefinitionKey", required = false) String processDefinitionKey) { salesNoticeService.submitSalesNotice(id, processDefinitionKey, getLoginUserId()); return success(true); } @GetMapping("/approve-process-list") @Operation(summary = "获取发货审批流程列表") public CommonResult>> getSalesNoticeApproveProcessList() { return success(salesNoticeService.getSalesNoticeApproveProcessDefinitionList()); } @PostMapping("/generate-from-sale-order") @Operation(summary = "从销售订单生成发货通知单") @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:create')") public CommonResult generateFromSaleOrder(@RequestBody Map params) { Long saleOrderId = params.get("saleOrderId"); return success(salesNoticeService.generateSalesNoticeFromSaleOrder(saleOrderId)); } @PostMapping("/generate-product-sales") @Operation(summary = "从发货通知单生成销售出库单") @Parameter(name = "id", description = "发货通知单编号", required = true) @PreAuthorize("@ss.hasPermission('mes:wm-product-sales:create')") public CommonResult generateProductSales(@RequestParam("id") Long id) { return success(salesNoticeService.generateProductSalesFromNotice(id)); } // ==================== 拼接 VO ==================== private List buildRespVOList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } // 1. 获得关联数据 Map customerMap = crmCustomerApi.getCustomerMap( convertSet(list, MesWmSalesNoticeDO::getClientId)); // 2. 构建结果 return BeanUtils.toBean(list, MesWmSalesNoticeRespVO.class, vo -> { MapUtils.findAndThen(customerMap, vo.getClientId(), customer -> vo.setClientName(customer.getName())); }); } }