From 627376da076341191546c657218a87081008b609 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 09:13:10 +0800 Subject: [PATCH 01/19] =?UTF-8?q?feat(sys):=E8=B4=B9=E7=94=A8=E7=A7=91?= =?UTF-8?q?=E7=9B=AE=E5=A2=9E=E5=8A=A0=E5=A4=8D=E5=88=B6=E3=80=81=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、新增 POST /expenseAccountApi/copy 复制接口(回显式,清空主键及科目编码) 2、新增 GET/POST /expenseAccountApi/export 导出接口(按列表查询条件+数据权限全量导出) 3、新增 ExpenseAccountExportVO 导出对象(27列业务字段) --- .../ExpenseAccountApplicationService.java | 51 +++++++++++++++++++ .../expenseAccount/ExpenseAccountApi.java | 45 ++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/expenseAccount/ExpenseAccountApplicationService.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/expenseAccount/ExpenseAccountApplicationService.java index 6bfb55e13..11dddd5a9 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/expenseAccount/ExpenseAccountApplicationService.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/expenseAccount/ExpenseAccountApplicationService.java @@ -13,6 +13,7 @@ import com.mhd.basic.application.service.contractManage.ContractManageApplicatio import com.mhd.basic.domain.expenseAccount.service.ExpenseAccountDomainService; import com.mhd.basic.infrastructure.feign.vo.SysDictDataVo; import com.mhd.basic.interfaces.dto.expenseAccount.ExpenseAccountDTO; +import com.mhd.basic.interfaces.dto.expenseAccount.ExpenseAccountExportVO; import com.mhd.basic.interfaces.dto.expenseAccount.SearchExpenseAccountDTO; import com.mhd.common.core.enums.DictCode; import com.mhd.common.core.exception.ServiceException; @@ -27,6 +28,7 @@ import com.mhd.system.api.SystemServiceFeign; import com.mhd.system.api.model.LoginUser; import com.mhd.common.core.domain.po.UserPo; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -378,6 +380,55 @@ public class ExpenseAccountApplicationService { return expenseAccountDomainService.getInfo(expenseAccountId); } + /** + * 复制费用科目-获取原科目数据供前端回显 + *

前端拿到数据后回显到新增弹窗,用户填写新科目编码后通过 /edit 接口(id 为空走新增)提交保存。 + * 清空科目编码是为了避免直接复制出重复编码(费用科目编码无唯一性校验)。

+ */ + public ExpenseAccountPO copy(ExpenseAccountDTO expenseAccountDTO) { + if (expenseAccountDTO == null || expenseAccountDTO.getExpenseAccountId() == null) { + throw new ServiceException("原费用科目id不能为空"); + } + // 获取原费用科目信息 + ExpenseAccountPO original = expenseAccountDomainService.getInfo(expenseAccountDTO.getExpenseAccountId()); + if (null == original) { + throw new ServiceException("原费用科目信息未找到"); + } + // 清空主键,前端提交时走新增 + original.setExpenseAccountId(null); + // 清空科目编码,强制用户输入新编码,避免编码重复 + original.setSubjectCode(null); + // 清空审计字段,新增时由 ExpenseAccountAssembler 重新生成 + original.setCreateBy(null); + original.setCreateByName(null); + original.setCreateTime(null); + original.setUpdateBy(null); + original.setUpdateByName(null); + original.setUpdateTime(null); + original.setDelFlag(null); + // 清空非表字段 + original.setUnit(null); + original.setExpenseAccountPOList(null); + return original; + } + + /** + * 构建费用科目导出数据(不分页全量,按列表页查询条件 + 数据权限过滤) + */ + public List buildExportList(ExpenseAccountDO expenseAccountDO) { + List exportList = new ArrayList<>(); + List list = this.queryList(expenseAccountDO); + if (list == null || list.isEmpty()) { + return exportList; + } + for (ExpenseAccountPO po : list) { + ExpenseAccountExportVO vo = new ExpenseAccountExportVO(); + BeanUtils.copyProperties(po, vo); + exportList.add(vo); + } + return exportList; + } + /** * 修改状态 * @param expenseAccountDTO diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facade/expenseAccount/ExpenseAccountApi.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facade/expenseAccount/ExpenseAccountApi.java index 8c22cd084..655fe1446 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facade/expenseAccount/ExpenseAccountApi.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facade/expenseAccount/ExpenseAccountApi.java @@ -1,5 +1,7 @@ package com.mhd.basic.interfaces.facade.expenseAccount; +import java.io.IOException; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; @@ -13,9 +15,11 @@ import com.mhd.basic.domain.expenseAccount.repository.todo.SearchExpenseAccountD import com.mhd.basic.domain.sysMultistageDict.repository.po.SysMultistageDictPo; import com.mhd.basic.interfaces.assember.expenseAccount.ExpenseAccountAssembler; import com.mhd.basic.interfaces.dto.expenseAccount.ExpenseAccountDTO; +import com.mhd.basic.interfaces.dto.expenseAccount.ExpenseAccountExportVO; import com.mhd.basic.interfaces.dto.expenseAccount.SearchExpenseAccountDTO; import com.mhd.common.core.constant.HttpStatus; import com.mhd.common.core.domain.entity.R; +import com.mhd.common.core.utils.poi.ExcelUtil; import com.sun.org.apache.bcel.internal.generic.NEW; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang.StringUtils; @@ -26,6 +30,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import com.mhd.common.core.web.controller.BaseController; import com.mhd.common.core.web.domain.AjaxResult; import com.mhd.common.core.web.page.TableDataInfo; @@ -336,5 +341,45 @@ public class ExpenseAccountApi extends BaseController{ return AjaxResult.success(expenseAccountPO.getUnit()); } + /** + * 复制费用科目 + * 返回原科目数据(已清空主键、科目编码等字段)供前端回显到新增弹窗,用户填写新科目编码后通过 /edit 接口提交新增 + */ + @ApiOperation("复制费用科目-获取原科目数据用于回显") + @PostMapping("/copy") + public AjaxResult copy(@RequestBody ExpenseAccountDTO expenseAccountDTO) + { + try { + ExpenseAccountPO expenseAccountPO = expenseAccountApplicationService.copy(expenseAccountDTO); + return AjaxResult.success(expenseAccountPO); + }catch (Exception e){ + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 导出费用科目 + * 按列表页当前查询条件 + 数据权限过滤,不分页全量导出 + */ + @ApiOperation("导出费用科目") + @RequestMapping(value = "/export", method = {RequestMethod.GET, RequestMethod.POST}, + produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + public void export(ExpenseAccountDTO expenseAccountDTO, HttpServletResponse response) throws IOException + { + //转换实体(不用Assembler,避免其自动填充createByName/updateByName干扰查询条件,与/list一致) + ExpenseAccountDO expenseAccountDO = new ExpenseAccountDO(); + BeanUtils.copyProperties(expenseAccountDTO, expenseAccountDO); + //查询全量(不分页),并转换为导出对象 + List list = expenseAccountApplicationService.buildExportList(expenseAccountDO); + + //设置下载头,防止浏览器乱码 + String fileName = URLEncoder.encode("费用科目导出", "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelUtil util = new ExcelUtil<>(ExpenseAccountExportVO.class); + util.exportExcel(response, list, "费用科目"); + } } \ No newline at end of file From afa502eeafc817ef7c53db90ebe8801b903596f5 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 09:14:03 +0800 Subject: [PATCH 02/19] =?UTF-8?q?feat(system):=E4=BB=93=E5=BA=93=E7=AE=A1?= =?UTF-8?q?=E7=90=86-=E5=BA=93=E4=BD=8D=E7=AE=A1=E7=90=86=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=A4=8D=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需求:库位管理列表增加复制功能,快速复用已有库位配置新建库位。 实现(仿照bms复制账单copyBill的回显式复制): 一、新增接口 POST /storageLocationApi/copyStorageLocation(StorageLocationApi): id判空+try-catch兜底,异常返回"复制库单失败:xxx" 二、StorageLocationApplicationService新增copyStorageLocation: 取原库位PO后清空唯一性字段(storageLocationId不清空会误走修改分支覆盖原库位、 storageLocationCode同组织同仓库唯一须用户重填、locationBarCode物理唯一)、 清空审计字段(提交时Assembler按登录人重写)、状态重置初始值 (status=1空/openingUp=1启用/nullify=1未作废);其余业务字段保留回显 三、前端拿PO回显到新增页,用户改编码后走现有edit接口(id为空即新增)提交, 天然复用编码唯一性校验、仓库/库区/库位组回填、库位组计数+1,Domain层零改动 --- .../StorageLocationApplicationService.java | 34 +++++++++++++++++++ .../storageLocation/StorageLocationApi.java | 19 +++++++++++ 2 files changed, 53 insertions(+) diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageLocation/StorageLocationApplicationService.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageLocation/StorageLocationApplicationService.java index 566c37e41..7bd6df9fa 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageLocation/StorageLocationApplicationService.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageLocation/StorageLocationApplicationService.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON; import com.mhd.basic.domain.storageLocation.repository.po.StorageLocationPO; import com.mhd.basic.domain.storageLocation.repository.todo.StorageLocationDO; import com.mhd.basic.domain.storageLocation.service.StorageLocationDomainService; +import com.mhd.basic.interfaces.dto.storageLocation.StorageLocationDTO; import com.mhd.basic.domain.storageSection.repository.todo.StorageSectionDO; import com.mhd.basic.infrastructure.feign.vo.SysDictDataVo; import com.mhd.common.core.domain.po.UserPo; @@ -207,6 +208,39 @@ public class StorageLocationApplicationService { return storageLocationDomainService.getInfo(storageLocationId); } + /** + * 复制库位-获取原库位数据供前端回显 + * 前端拿到数据后跳转到新增库位页面,用户修改编码后通过edit接口提交新增 + */ + public StorageLocationPO copyStorageLocation(StorageLocationDTO storageLocationDTO) { + if (storageLocationDTO.getStorageLocationId() == null) { + throw new ServiceException("原库位id不能为空"); + } + // 获取原库位信息 + StorageLocationPO originalStorageLocation = storageLocationDomainService.getInfo(storageLocationDTO.getStorageLocationId()); + if (null == originalStorageLocation) { + throw new ServiceException("原库位信息未找到"); + } + // 清空唯一性字段:库位id不清空会导致提交后走修改分支覆盖原库位; + // 库位编码同组织同仓库唯一,须由用户重新填写;条码为物理唯一标识,一并清空 + originalStorageLocation.setStorageLocationId(null); + originalStorageLocation.setStorageLocationCode(null); + originalStorageLocation.setLocationBarCode(null); + // 状态重置为初始值:新库位无库存(空)、启用、未作废 + originalStorageLocation.setStatus(1); + originalStorageLocation.setOpeningUp(1); + originalStorageLocation.setNullify(1); + // 清空审计字段,提交新增时由Assembler按当前登录人重新生成 + originalStorageLocation.setCreateBy(null); + originalStorageLocation.setCreateByName(null); + originalStorageLocation.setCreateTime(null); + originalStorageLocation.setUpdateBy(null); + originalStorageLocation.setUpdateByName(null); + originalStorageLocation.setUpdateTime(null); + // 其余业务字段(仓库/库区/库位组、分类/种类/混放策略、限制、长宽高、坐标、备注)保留回显 + return originalStorageLocation; + } + public int getStorageLocationCount(StorageLocationDO storageLocationDO) { return storageLocationDomainService.getStorageLocationCount(storageLocationDO); diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facadeApi/storageLocation/StorageLocationApi.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facadeApi/storageLocation/StorageLocationApi.java index 6f071de45..2c211ae5d 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facadeApi/storageLocation/StorageLocationApi.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/facadeApi/storageLocation/StorageLocationApi.java @@ -19,6 +19,7 @@ import com.mhd.basic.domain.storageLocation.repository.todo.StorageLocationDO; import com.mhd.basic.domain.storageLocation.repository.po.StorageLocationPO; import com.mhd.basic.application.service.storageLocation.StorageLocationApplicationService; import com.mhd.basic.interfaces.assember.storageLocation.StorageLocationAssembler; +import com.mhd.common.core.exception.ServiceException; import javax.annotation.Resource; import com.mhd.common.core.web.controller.BaseController; import com.mhd.common.core.web.domain.AjaxResult; @@ -127,6 +128,24 @@ public class StorageLocationApi extends BaseController{ { return AjaxResult.success(storageLocationApplicationService.getInfo(storageLocationId)); } + + /** + * 复制库位-获取原库位数据用于回显 + */ + @ApiOperation("复制库位-获取原库位数据用于回显") + @PostMapping("/copyStorageLocation") + public AjaxResult copyStorageLocation(@RequestBody StorageLocationDTO storageLocationDTO) + { + if(storageLocationDTO.getStorageLocationId() == null){ + throw new ServiceException("原库位id不能为空"); + } + try { + StorageLocationPO result = storageLocationApplicationService.copyStorageLocation(storageLocationDTO); + return AjaxResult.success(result); + }catch (Exception e){ + return AjaxResult.error("复制库位失败:"+e.getMessage()); + } + } @ApiOperation("获取库位总数") @PostMapping(value = "/getStorageLocationCount") public AjaxResult getStorageLocationCount(@RequestBody StorageLocationDTO storageLocationDTO) From 6349181cc304c6a89aabe6599480f09a8d50b15d Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 09:20:11 +0800 Subject: [PATCH 03/19] =?UTF-8?q?feat(sys):=E8=B4=B9=E7=94=A8=E7=A7=91?= =?UTF-8?q?=E7=9B=AE=E5=AF=BC=E5=87=BA=E5=A2=9E=E5=8A=A0=E6=96=B0vo?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=20=E6=96=B0=E5=A2=9E=20ExpenseAccountExportV?= =?UTF-8?q?O=20=E5=AF=BC=E5=87=BA=E5=AF=B9=E8=B1=A1=EF=BC=8827=E5=88=97?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=AD=97=E6=AE=B5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExpenseAccountExportVO.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java new file mode 100644 index 000000000..a7d59ebaf --- /dev/null +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java @@ -0,0 +1,98 @@ +package com.mhd.basic.interfaces.dto.expenseAccount; + +import com.mhd.common.core.annotation.Excel; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 费用科目导出对象 expense_account + * + * @author rcx + * @date 2026-08-28 + */ +@Data +public class ExpenseAccountExportVO { + + @Excel(name = "组织名称") + private String organizationName; + + @Excel(name = "科目编码") + private String subjectCode; + + @Excel(name = "科目名称") + private String subjectName; + + @Excel(name = "显示顺序") + private Integer showSeq; + + @Excel(name = "计费属性") + private String billingAttributes; + + @Excel(name = "所属模块") + private String belongModule; + + @Excel(name = "是否一级科目", readConverterExp = "1=是,2=否") + private Integer firstSubjectFlag; + + @Excel(name = "上级科目编码") + private String upperSubjectCode; + + @Excel(name = "上级科目名称") + private String upperSubject; + + @Excel(name = "科目类型", readConverterExp = "1=系统科目,2=非系统科目") + private Integer accountType; + + @Excel(name = "收支类型", readConverterExp = "1=收入,2=支出") + private Integer accountExpenseType; + + @Excel(name = "服务项编码") + private String serviceItemsCode; + + @Excel(name = "服务项名称") + private String serviceItemsName; + + @Excel(name = "服务项税率") + private BigDecimal taxRate; + + @Excel(name = "费率") + private BigDecimal rate; + + @Excel(name = "启用状态", readConverterExp = "1=启用,2=停用") + private Integer status; + + @Excel(name = "自有车辆固定显示", readConverterExp = "1=是,2=否") + private Integer ownStatus; + + @Excel(name = "社会车辆固定显示", readConverterExp = "1=是,2=否") + private Integer societyStatus; + + @Excel(name = "创建订单固定显示", readConverterExp = "0=否,1=是") + private Integer fixedOrderDisplay; + + @Excel(name = "入库单固定显示", readConverterExp = "0=否,1=是") + private Integer inOrderDisplay; + + @Excel(name = "出库单固定显示", readConverterExp = "0=否,1=是") + private Integer outOrderDisplay; + + @Excel(name = "计费单位") + private String billingUnit; + + @Excel(name = "计费单位2") + private String billingUnit2; + + @Excel(name = "NC科目编码") + private String ncSubjectCode; + + @Excel(name = "备注") + private String remark; + + @Excel(name = "创建人") + private String createByName; + + @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date createTime; +} From d0773a40d2461dd0c988c474322c2c014e651bf9 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 16:12:56 +0800 Subject: [PATCH 04/19] =?UTF-8?q?feat(transport):=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E6=95=B0=E6=8D=AE-=E8=BF=90=E8=90=A5=E6=95=B0=E6=8D=AE-?= =?UTF-8?q?=E8=BD=A6=E8=BE=86=E7=AE=A1=E7=90=86=E5=A2=9E=E5=8A=A0=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 /vehicle/export 导出接口(GET/POST,兼容前端 download 工具), 复用列表查询条件与数据权限,不分页导出全量数据为 xlsx; 新增 VehicleExportVO 导出对象(40列,状态字段转中文)。 --- .../vehicle/VehicleApplicationService.java | 28 +++ .../dto/vehicle/VehicleExportVO.java | 178 ++++++++++++++++++ .../interfaces/facade/vehicle/VehicleApi.java | 32 ++++ 3 files changed, 238 insertions(+) create mode 100644 mhd_transport/src/main/java/com/linke/transport/interfaces/dto/vehicle/VehicleExportVO.java diff --git a/mhd_transport/src/main/java/com/linke/transport/application/service/vehicle/VehicleApplicationService.java b/mhd_transport/src/main/java/com/linke/transport/application/service/vehicle/VehicleApplicationService.java index b3e4d2147..5f306f16d 100644 --- a/mhd_transport/src/main/java/com/linke/transport/application/service/vehicle/VehicleApplicationService.java +++ b/mhd_transport/src/main/java/com/linke/transport/application/service/vehicle/VehicleApplicationService.java @@ -32,6 +32,7 @@ import com.linke.transport.interfaces.assemble.vehicle.VehicleAssembler; import com.mhd.common.core.constant.Constants; import com.mhd.common.core.domain.dto.SyncTmsVehicleDTO; import com.linke.transport.interfaces.dto.vehicle.VehicleDto; +import com.linke.transport.interfaces.dto.vehicle.VehicleExportVO; import com.mhd.common.core.annotation.fieldsetvalue.NeedSetValueField; import com.mhd.common.core.annotation.fieldsetvalue.NeedSetValueMethod; import com.mhd.common.core.constant.NoticeConstants; @@ -238,6 +239,33 @@ public class VehicleApplicationService { return vehiclePos; } + /** + * 构建车辆管理导出数据(不分页,按列表查询条件导出) + * + * @param vehiclePo 查询条件 + * @param dataPermission 数据权限 + * @return 导出数据 + */ + public List buildExportList(VehiclePo vehiclePo, DataPermission dataPermission) { + List vehiclePos = selectVehicleList(vehiclePo, dataPermission); + List exportList = new ArrayList<>(); + if (CollUtil.isEmpty(vehiclePos)) { + return exportList; + } + for (VehiclePo po : vehiclePos) { + VehicleExportVO vo = new VehicleExportVO(); + BeanUtils.copyProperties(po, vo); + // 司机名称集合拼接为字符串(List与String类型不匹配,copyProperties不会复制,需手动设置) + if (CollUtil.isNotEmpty(po.getDriverNames())) { + vo.setDriverNames(String.join(",", po.getDriverNames())); + } + // 所属组织与归属组织一致 + vo.setBelongOrganizationName(po.getOrganizationName()); + exportList.add(vo); + } + return exportList; + } + /** * 查询未绑定车辆列表 * diff --git a/mhd_transport/src/main/java/com/linke/transport/interfaces/dto/vehicle/VehicleExportVO.java b/mhd_transport/src/main/java/com/linke/transport/interfaces/dto/vehicle/VehicleExportVO.java new file mode 100644 index 000000000..b98554e7c --- /dev/null +++ b/mhd_transport/src/main/java/com/linke/transport/interfaces/dto/vehicle/VehicleExportVO.java @@ -0,0 +1,178 @@ +package com.linke.transport.interfaces.dto.vehicle; + +import com.mhd.common.core.annotation.Excel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 车辆管理导出对象(基础数据-运营数据-车辆管理列表,列顺序与页面列表一致) + * + * @author gen + */ +@Data +@ApiModel(value = "车辆管理导出对象", description = "车辆管理导出对象") +public class VehicleExportVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + @Excel(name = "车辆主体", readConverterExp = "1=主车,2=挂车") + @ApiModelProperty(name = "车辆主体(1-主车,2-挂车)") + private Integer vehicleBody; + + @Excel(name = "车牌号码") + @ApiModelProperty(name = "车牌号码") + private String vehicleLicensePlateNumber; + + @Excel(name = "跨境车牌") + @ApiModelProperty(name = "跨境车牌") + private String crossPlate; + + @Excel(name = "车辆类型") + @ApiModelProperty(name = "车辆类型") + private String vehicleTypeName; + + @Excel(name = "类型", readConverterExp = "0=自由车辆,1=社会车辆") + @ApiModelProperty(name = "类型(车辆来源)") + private Integer vehicleSource; + + @Excel(name = "部门") + @ApiModelProperty(name = "部门") + private String departmentName; + + @Excel(name = "车辆认证", readConverterExp = "0=无状态,1=未认证,2=等待审核,3=已通过,4=已驳回") + @ApiModelProperty(name = "车辆认证状态") + private Integer authenticationStatus; + + @Excel(name = "司机姓名") + @ApiModelProperty(name = "司机姓名(多个以逗号分隔)") + private String driverNames; + + @Excel(name = "副驾1") + @ApiModelProperty(name = "副驾1") + private String pilot1; + + @Excel(name = "副驾2") + @ApiModelProperty(name = "副驾2") + private String pilot2; + + @Excel(name = "车队名称") + @ApiModelProperty(name = "车队名称") + private String motorcadeName; + + @Excel(name = "年检有效期", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "年检有效期") + private Date annualValidityPeriod; + + @Excel(name = "保险有效期", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "保险有效期") + private Date insuranceValidityPeriod; + + @Excel(name = "运载状态", readConverterExp = "0=无状态,1=空载,2=负载") + @ApiModelProperty(name = "运载状态") + private Integer carryStatus; + + @Excel(name = "使用性质") + @ApiModelProperty(name = "使用性质") + private String vehicleUseNature; + + @Excel(name = "总质量(kg)") + @ApiModelProperty(name = "总质量(kg)") + private BigDecimal vehicleTotalMass; + + @Excel(name = "核定载质量(kg)") + @ApiModelProperty(name = "核定载质量(kg)") + private BigDecimal vehicleApprovedLoadQuantity; + + @Excel(name = "车轴数") + @ApiModelProperty(name = "车轴数") + private String otherAxlesNumberName; + + @Excel(name = "车长(米)") + @ApiModelProperty(name = "车长(米)") + private String otherCarLengthName; + + @Excel(name = "容积(方)") + @ApiModelProperty(name = "容积(方)") + private String otherVehicleVolumeName; + + @Excel(name = "车牌颜色") + @ApiModelProperty(name = "车牌颜色") + private String otherLicensePlateColorName; + + @Excel(name = "行驶证有效期限", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "行驶证有效期限") + private Date vehicleCheckoutPeriodValidity; + + @Excel(name = "道路运输证号") + @ApiModelProperty(name = "道路运输证号") + private String roadTransportCertificateNo; + + @Excel(name = "道路运输证有效期限", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "道路运输证有效期限") + private Date roadTransportCertificateExpireDate; + + @Excel(name = "道路运输证校验状态", readConverterExp = "0=无状态,1=未核验,2=核验通过,3=核验失败") + @ApiModelProperty(name = "道路运输证校验状态") + private Integer roadTransportCertificateCheckoutStatus; + + @Excel(name = "经营许可证号") + @ApiModelProperty(name = "经营许可证号") + private String businessLicenseNumber; + + @Excel(name = "经营许可证业户名称") + @ApiModelProperty(name = "经营许可证业户名称") + private String specializedHouseholdsName; + + @Excel(name = "道路运输经营许可证有效期", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "道路运输经营许可证有效期(截止日期)") + private Date businessLicenseExpireEndDate; + + @Excel(name = "经营许可证校验状态", readConverterExp = "0=无状态,1=未核验,2=核验通过,3=核验失败") + @ApiModelProperty(name = "经营许可证校验状态") + private Integer businessLicenseCheckoutStatus; + + @Excel(name = "入网校验状态", readConverterExp = "0=无状态,1=未核验,2=已入网,3=未入网") + @ApiModelProperty(name = "入网校验状态") + private Integer netInStatus; + + @Excel(name = "入网查询时间", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "入网查询时间") + private Date netInDate; + + @Excel(name = "使用状态", readConverterExp = "0=无状态,1=正常,2=黑名单") + @ApiModelProperty(name = "使用状态") + private Integer useStatus; + + @Excel(name = "归属组织") + @ApiModelProperty(name = "归属组织") + private String organizationName; + + @Excel(name = "添加人员") + @ApiModelProperty(name = "添加人员") + private String createByName; + + @Excel(name = "添加时间", width = 25, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(name = "添加时间") + private Date createTime; + + @Excel(name = "审核人员") + @ApiModelProperty(name = "审核人员") + private String checkoutPersonName; + + @Excel(name = "审核时间", width = 20, dateFormat = "yyyy-MM-dd") + @ApiModelProperty(name = "审核时间") + private Date checkoutDate; + + @Excel(name = "更新时间", width = 25, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(name = "更新时间") + private Date updateTime; + + @Excel(name = "所属组织") + @ApiModelProperty(name = "所属组织(与归属组织一致)") + private String belongOrganizationName; +} diff --git a/mhd_transport/src/main/java/com/linke/transport/interfaces/facade/vehicle/VehicleApi.java b/mhd_transport/src/main/java/com/linke/transport/interfaces/facade/vehicle/VehicleApi.java index 661c393d5..cbd1f33b7 100644 --- a/mhd_transport/src/main/java/com/linke/transport/interfaces/facade/vehicle/VehicleApi.java +++ b/mhd_transport/src/main/java/com/linke/transport/interfaces/facade/vehicle/VehicleApi.java @@ -9,6 +9,7 @@ import com.linke.transport.domain.vehicle.repository.po.VehiclePo; import com.linke.transport.interfaces.assemble.vehicle.VehicleAssembler; import com.mhd.common.core.domain.dto.SyncTmsVehicleDTO; import com.linke.transport.interfaces.dto.vehicle.VehicleDto; +import com.linke.transport.interfaces.dto.vehicle.VehicleExportVO; import com.mhd.common.core.domain.dto.StatisticalMattersDTO; import com.mhd.common.core.domain.entity.R; import com.mhd.common.core.domain.todo.StatisticalMattersDo; @@ -16,6 +17,7 @@ import com.mhd.common.core.exception.ServiceException; import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException; import com.mhd.common.core.utils.IgnoreNullUtil; import com.mhd.common.core.utils.bean.BeanUtils; +import com.mhd.common.core.utils.poi.ExcelUtil; import com.mhd.common.core.web.controller.BaseController; import com.mhd.common.core.web.domain.AjaxResult; import com.mhd.common.core.web.page.TableDataInfo; @@ -28,6 +30,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; import java.util.List; /** @@ -65,6 +70,33 @@ public class VehicleApi extends BaseController return getDataTable(list); } + /** + * 导出车辆管理列表 + */ + @Log(title = "车辆管理-导出", description = "导出车辆管理列表", businessType = BusinessType.EXPORT) + @ApiOperation("导出车辆管理列表") + // 同时支持 GET(query 参数)与 POST(form 表单,兼容前端 this.download() 工具) + @RequestMapping(value = "/export", method = {RequestMethod.GET, RequestMethod.POST}, produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + public void export(VehicleDto vehicleDto, HttpServletResponse response) throws IOException + { + VehiclePo vehiclePo = new VehiclePo(); + //转换实体 + BeanUtils.copyProperties(vehicleDto, vehiclePo); + //判断当前登录人数据权限 + DataPermission dataPermission = commonApplicationService.dataPermission(vehiclePo.getPermissionMenuId()); + //查询全量(不分页) + List list = vehicleApplicationService.buildExportList(vehiclePo, dataPermission); + + //设置下载头,防止浏览器乱码 + String fileName = URLEncoder.encode("车辆管理导出", "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelUtil util = new ExcelUtil<>(VehicleExportVO.class); + util.exportExcel(response, list, "车辆管理数据"); + } + /** * 车辆绑定副驾 */ From 7a01315ca8510bffca30199219f88d02a9f86d58 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 16:17:48 +0800 Subject: [PATCH 05/19] =?UTF-8?q?feat(sys)=EF=BC=9A=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E4=BC=98=E5=8C=96=E4=B8=8E=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExpenseAccountExportVO.java | 93 +++++++------------ 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java index a7d59ebaf..31f09e3fc 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/interfaces/dto/expenseAccount/ExpenseAccountExportVO.java @@ -15,53 +15,30 @@ import java.util.Date; @Data public class ExpenseAccountExportVO { - @Excel(name = "组织名称") - private String organizationName; - - @Excel(name = "科目编码") - private String subjectCode; - - @Excel(name = "科目名称") - private String subjectName; - - @Excel(name = "显示顺序") - private Integer showSeq; - - @Excel(name = "计费属性") - private String billingAttributes; - - @Excel(name = "所属模块") - private String belongModule; - - @Excel(name = "是否一级科目", readConverterExp = "1=是,2=否") - private Integer firstSubjectFlag; - - @Excel(name = "上级科目编码") - private String upperSubjectCode; - - @Excel(name = "上级科目名称") - private String upperSubject; - - @Excel(name = "科目类型", readConverterExp = "1=系统科目,2=非系统科目") - private Integer accountType; - - @Excel(name = "收支类型", readConverterExp = "1=收入,2=支出") - private Integer accountExpenseType; - - @Excel(name = "服务项编码") - private String serviceItemsCode; - - @Excel(name = "服务项名称") + /** 费用类别(关联服务项名称) */ + @Excel(name = "费用类别") private String serviceItemsName; - @Excel(name = "服务项税率") + @Excel(name = "上级科目") + private String upperSubject; + + @Excel(name = "费用代码") + private String subjectCode; + + @Excel(name = "费用名称") + private String subjectName; + + @Excel(name = "税率") private BigDecimal taxRate; - @Excel(name = "费率") - private BigDecimal rate; + @Excel(name = "创建订单固定显示", readConverterExp = "0=否,1=是") + private Integer fixedOrderDisplay; - @Excel(name = "启用状态", readConverterExp = "1=启用,2=停用") - private Integer status; + @Excel(name = "入库显示", readConverterExp = "0=否,1=是") + private Integer inOrderDisplay; + + @Excel(name = "出库显示", readConverterExp = "0=否,1=是") + private Integer outOrderDisplay; @Excel(name = "自有车辆固定显示", readConverterExp = "1=是,2=否") private Integer ownStatus; @@ -69,30 +46,24 @@ public class ExpenseAccountExportVO { @Excel(name = "社会车辆固定显示", readConverterExp = "1=是,2=否") private Integer societyStatus; - @Excel(name = "创建订单固定显示", readConverterExp = "0=否,1=是") - private Integer fixedOrderDisplay; - - @Excel(name = "入库单固定显示", readConverterExp = "0=否,1=是") - private Integer inOrderDisplay; - - @Excel(name = "出库单固定显示", readConverterExp = "0=否,1=是") - private Integer outOrderDisplay; - - @Excel(name = "计费单位") - private String billingUnit; - - @Excel(name = "计费单位2") - private String billingUnit2; - - @Excel(name = "NC科目编码") - private String ncSubjectCode; - - @Excel(name = "备注") + @Excel(name = "备注信息") private String remark; + @Excel(name = "状态", readConverterExp = "1=启用,2=停用") + private Integer status; + @Excel(name = "创建人") private String createByName; @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date createTime; + + @Excel(name = "更新人") + private String updateByName; + + @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + @Excel(name = "所属组织") + private String organizationName; } From 155f1866b46f764fe0c48538190f391151288c8b Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Fri, 28 Aug 2026 18:01:44 +0800 Subject: [PATCH 06/19] =?UTF-8?q?feat(system):=E5=BA=93=E5=8C=BA=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=96=B0=E5=A2=9E=E5=BA=93=E5=8C=BA=E9=9D=A2=E7=A7=AF?= =?UTF-8?q?=E5=90=88=E8=AE=A1=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20GET=20/storageSectionApi/sumStorageRegion?= =?UTF-8?q?=EF=BC=8C=E4=B8=8E=E5=BA=93=E5=8C=BA=E5=88=97=E8=A1=A8=20/list?= =?UTF-8?q?=20=E7=9B=B8=E5=90=8C=E7=AD=9B=E9=80=89=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E5=8F=8A=E7=BB=84=E7=BB=87=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90?= =?UTF-8?q?=EF=BC=8C=20SQL=20=E7=AB=AF=20IFNULL(SUM(storage=5Fregion),0)?= =?UTF-8?q?=20=E8=BF=94=E5=9B=9E=E9=9D=A2=E7=A7=AF=E5=90=88=E8=AE=A1(?= =?UTF-8?q?=E5=B9=B3=E6=96=B9=E7=B1=B3)=EF=BC=8C=E6=97=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=94=E5=9B=9E0=EF=BC=9B=20=E6=8A=BD=E5=8F=96=20qu?= =?UTF-8?q?eryList=20=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90=E4=B8=BA=20apply?= =?UTF-8?q?QueryDataPermission=20=E4=BE=9B=E5=88=97=E8=A1=A8=E4=B8=8E?= =?UTF-8?q?=E5=90=88=E8=AE=A1=E5=85=B1=E7=94=A8=EF=BC=8C/list=20=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E4=B8=8D=E5=8F=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StorageSectionApplicationService.java | 25 +++++++++++++++++-- .../facade/IStorageSectionService.java | 6 +++++ .../mapper/StorageSectionMapper.java | 6 +++++ .../persistence/StorageSectionImpl.java | 10 ++++++++ .../service/StorageSectionDomainService.java | 8 ++++++ .../storageSection/StorageSectionApi.java | 12 +++++++++ .../storageSection/StorageSectionMapper.xml | 8 ++++++ 7 files changed, 73 insertions(+), 2 deletions(-) diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageSection/StorageSectionApplicationService.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageSection/StorageSectionApplicationService.java index 0a17bb062..83cfb1af9 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageSection/StorageSectionApplicationService.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/application/service/storageSection/StorageSectionApplicationService.java @@ -21,6 +21,7 @@ import com.mhd.system.service.ISysDictTypeService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.List; /** * 库区ApplicationService @@ -42,11 +43,32 @@ public class StorageSectionApplicationService { */ public List queryList(StorageSectionDO storageSectionDO) { + //设置查询数据权限 + applyQueryDataPermission(storageSectionDO); + return storageSectionDomainService.queryList(storageSectionDO); + } + + /** + * 库区面积合计:与 /list 相同的数据权限和筛选条件,SQL 直接 SUM(库区面积), + * 保证合计数与列表中各行库区面积之和一致 + */ + public BigDecimal sumStorageRegion(StorageSectionDO storageSectionDO) { + //与 /list 共用同一数据权限,保证合计与列表口径一致 + applyQueryDataPermission(storageSectionDO); + return storageSectionDomainService.sumStorageRegion(storageSectionDO); + } + + /** + * @description 设置查询数据权限(queryList 与 sumStorageRegion 共用) + * @author ZhouGY + * @param storageSectionDO + */ + private void applyQueryDataPermission(StorageSectionDO storageSectionDO) { LoginUser loginUser = SecurityUtils.getLoginUser(); if (loginUser != null && loginUser.getUserPo() != null) { Long userOrganizationId = loginUser.getUserPo().getOrganizationId(); Long frontendOrganizationId = storageSectionDO.getOrganizationId(); // 前端传递的 organizationId - + // 数据权限:根据 organizationId 来设置查询权限 // 当前登录用户的 organizationId 为 2827 时,可查全部组织数据;其他组织只能查自己 if (userOrganizationId != null && userOrganizationId == 2827L) { @@ -91,7 +113,6 @@ public class StorageSectionApplicationService { } } } - return storageSectionDomainService.queryList(storageSectionDO); } diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/facade/IStorageSectionService.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/facade/IStorageSectionService.java index 925dfb501..2d26a4299 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/facade/IStorageSectionService.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/facade/IStorageSectionService.java @@ -5,6 +5,7 @@ import com.mhd.basic.domain.storageSection.entity.StorageSection; import com.mhd.basic.domain.storageSection.repository.po.StorageSectionPO; import com.mhd.basic.domain.storageSection.repository.todo.StorageSectionDO; +import java.math.BigDecimal; import java.util.List; /** @@ -20,6 +21,11 @@ public interface IStorageSectionService extends IService */ public List queryList(StorageSectionDO storageSectionDO); + /** + * 库区面积合计(与 queryList 相同筛选条件) + */ + public BigDecimal sumStorageRegion(StorageSectionDO storageSectionDO); + /** * 新增库区 */ diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/mapper/StorageSectionMapper.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/mapper/StorageSectionMapper.java index 46153810f..82196f40e 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/mapper/StorageSectionMapper.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/mapper/StorageSectionMapper.java @@ -1,5 +1,6 @@ package com.mhd.basic.domain.storageSection.repository.mapper; +import java.math.BigDecimal; import java.util.List; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mhd.basic.domain.storageSection.entity.StorageSection; @@ -19,4 +20,9 @@ public interface StorageSectionMapper extends BaseMapper */ public List queryList(StorageSectionDO storageSectionDO); + /** + * 库区面积合计(与 queryList 相同筛选条件) + */ + public BigDecimal sumStorageRegion(StorageSectionDO storageSectionDO); + } diff --git a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/persistence/StorageSectionImpl.java b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/persistence/StorageSectionImpl.java index 2f6e3fcfb..5f1f0baae 100644 --- a/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/persistence/StorageSectionImpl.java +++ b/mhd-modules/mhd-system/src/main/java/com/mhd/basic/domain/storageSection/repository/persistence/StorageSectionImpl.java @@ -10,6 +10,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -33,6 +34,15 @@ public class StorageSectionImpl extends ServiceImpl order by a.storage_name asc, a.create_time desc + + + \ No newline at end of file From 2781f0d8d9b2b09e07505b8e154dbe7393e0edb6 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Mon, 31 Aug 2026 10:17:53 +0800 Subject: [PATCH 07/19] =?UTF-8?q?feat(oms):=E5=BA=93=E5=AD=98=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=AF=BC=E5=87=BA=E6=96=B0=E5=A2=9E=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E4=B8=BA0=E6=98=AF=E5=90=A6=E5=AF=BC=E5=87=BA=E5=8F=82?= =?UTF-8?q?=E6=95=B0=20/reservationMaterialInventoryApi/list=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8F=AF=E9=80=89=E5=8F=82=E6=95=B0=20excludeZeroInve?= =?UTF-8?q?ntoryQuantity=EF=BC=8C=E5=89=8D=E7=AB=AF"=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E4=B8=BA0=E6=98=AF=E5=90=A6=E5=AF=BC=E5=87=BA"=E9=80=89"?= =?UTF-8?q?=E6=98=AF"=E6=97=B6=E4=BC=A0=20true=EF=BC=8CSQL=20=E4=BB=A5=20H?= =?UTF-8?q?AVING=20IFNULL(SUM(inventory=5Fquantity),0)>0=20=E6=8E=92?= =?UTF-8?q?=E9=99=A4=E6=B1=87=E6=80=BB=E5=BA=93=E5=AD=98=E4=B8=BA0?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E6=8D=AE=EF=BC=9B=E6=89=80=E6=9C=89=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E7=BB=B4=E5=BA=A6(all/hz/wl/kw/wlkw/wlExpiry)?= =?UTF-8?q?=E5=85=B16=E6=9D=A1=E6=9F=A5=E8=AF=A2=E5=9C=A8=20GROUP=20BY=20?= =?UTF-8?q?=E5=90=8E=E7=BB=9F=E4=B8=80=E6=8C=82=E8=BD=BD=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E7=89=87=E6=AE=B5=20excludeZeroInventoryHaving=EF=BC=8C?= =?UTF-8?q?=E4=B8=8E=20sort=5Forder=20=E5=AF=B90=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E7=9A=84=E5=AE=9A=E4=B9=89=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= =?UTF-8?q?=EF=BC=9B=E4=B8=8D=E4=BC=A0=E5=8F=82=E6=95=B0=E6=97=B6=20SQL=20?= =?UTF-8?q?=E4=B8=8D=E6=8B=BC=E6=8E=A5=E6=9D=A1=E4=BB=B6=EF=BC=8C=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E4=B8=8E=E5=8E=9F=E6=9D=A5=E5=AE=8C=E5=85=A8=E4=B8=80?= =?UTF-8?q?=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ReservationMaterialInventoryDO.java | 3 +++ .../ReservationMaterialInventoryApi.java | 6 +++++- .../MaterialInventoryMapper.xml | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java index c31872418..c3985e908 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java @@ -258,4 +258,7 @@ public class ReservationMaterialInventoryDO extends MaterialBaseDO { @ApiModelProperty("导出按到期日分组排序标识:仅 queryRule=wlExpiry 时生效,true-到期日倒序(日期近的在前、空值最后、0库存靠后);不传-保持原有排序") private Boolean orderByExpiryDate; + + @ApiModelProperty("导出排除库存为0标识:true-排除库存数量汇总为0的数据(所有查询维度 queryRule 均生效);不传-不过滤") + private Boolean excludeZeroInventoryQuantity; } \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java index 7d1368cf8..8e124d521 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java @@ -47,12 +47,13 @@ public class ReservationMaterialInventoryApi extends BaseController { /** * 分页查询物料库存列表 */ - @ApiOperation("查询物料库存列表") + @ApiOperation(value = "查询物料库存列表", notes = "导出复用本接口,任意查询维度(queryRule)下:前端\"库存为0是否导出\"选\"是\"时传 excludeZeroInventoryQuantity=true 排除库存为0的数据,不传或 false 导出全部(含库存为0);orderByExpiryDate=true 时按到期日分组排序") @GetMapping("/list") public TableDataInfo list(ReservationMaterialInventoryDTO materialInventoryDTO, @RequestParam(value = "customerName", required = false) String customerName, @RequestParam(value = "expiryDateStart", required = false) String expiryDateStart, @RequestParam(value = "expiryDateEnd", required = false) String expiryDateEnd, + @RequestParam(value = "excludeZeroInventoryQuantity", required = false) Boolean excludeZeroInventoryQuantity, @RequestParam(value = "orderByExpiryDate", required = false) Boolean orderByExpiryDate) { // 出库分配前端传的是客户名称 customerName,这里通过名称查询货主ID,使用精确匹配(不模糊匹配) @@ -86,6 +87,9 @@ public class ReservationMaterialInventoryApi extends BaseController { // 导出场景:queryRule=wlExpiry + orderByExpiryDate=true 时 SQL 按到期日倒序分组 // (日期近的在前、空值最后、0库存靠后、同到期日按更新时间倒序) materialInventoryDO.setOrderByExpiryDate(orderByExpiryDate); + // 导出场景:前端"库存为0是否导出"选"是"时传 excludeZeroInventoryQuantity=true, + // SQL 用 HAVING 排除汇总库存为0的数据(与 WMS 同名参数语义一致) + materialInventoryDO.setExcludeZeroInventoryQuantity(excludeZeroInventoryQuantity); startPage(); // 使用带批次属性的查询方法,批次属性值从库存表中获取 List list = materialInventoryApplicationService.queryListWithBatchAttributes(materialInventoryDO); diff --git a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml index 0297f8a29..10b846bf0 100644 --- a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml @@ -282,6 +282,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + HAVING IFNULL(SUM(a.inventory_quantity), 0) > 0 + + + @@ -369,6 +381,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY a.MATERIAL_BASE_INFO_ID, a.EXPIRY_DATE, a.shipper_id, a.shipper_code, a.shipper_name, a.organization_id, a.organization_name, a.top_organization_id, b.material_code, b.material_name + @@ -394,6 +407,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY a.SHIPPER_ID + From 2d1e4b5912355a42d51eca45f21be8648d7e08bf Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Mon, 31 Aug 2026 11:15:41 +0800 Subject: [PATCH 08/19] =?UTF-8?q?feat(oms):=E5=87=BA=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E6=8C=89=E7=BC=96=E5=8F=B7=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=90=8D=E7=A7=B0=E5=B9=B6=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=AE=A2=E6=88=B7/=E7=89=A9=E6=96=99=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E6=A8=A1=E6=9D=BF=E5=88=97=E4=BD=8D=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8A=A8=E6=80=81=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需求:①入库业务单导入模板只填物料编号+客户编号,物料名称/客户名称按编号自动匹配库中数据 ②③出库导入校验客户/物料是否已存在,不存在提示"进行维护" ④库中查询结果为空(确无数据)时报错 一、入库导入(ReservationStockInOrderImpl.parseStockInExcel): 1. 物料逐编号查库,缺失料号收集后一次性报错"物料[xx]不存在,请先维护物料信息后再导入" 2. 明细新增客户编号:去重后查库,为空报错"客户[编号]不存在,请先维护客户信息后再导入"; 查到则以该客户为单据货主并自动匹配名称;与物料归属货主不一致时拦截并提示两者编号 3. 明细回填库中物料名称;未填客户编号时维持原"货主取自物料"逻辑,兼容老模板 二、出库导入(parseExcelFile): 1. 物料/客户校验前移到落库前(原为先落库后查客户,报错会留半截数据) 2. 物料查不到收集后报错维护提示;客户查不到报错维护提示,客户ID/名称改取库中匹配值 (替换仅返回id且查不到时静默存null的queryShipperId) 3. 產品編號/客户編号漏填的行直接报错提示补全 三、模板解析改为按列头文字动态定位列(繁简体归一化),列/行顺序调整不影响解析, 识别不到时回落老模板固定列位,老模板行为不变: 1. StockInExcelListener(入库):以產品編號为锚点识别列头行,新增客戶編號列支持 2. DynamicDeliveryExcelListener(出库):左右分栏各自锚点识别;送货日期/线号/车次/ 发票张数/小计/纪录合共改按标签文字定位,行号列号不再写死 四、Mapper:ReservationStockOutOrderMapper新增queryCustomerByCode(按组织查询, USER_MEMBER_CODE或USER_SHIPPER.CUSTOMER_NC_CODE任一命中),删除queryShipperId --- .../DynamicDeliveryExcelListener.java | 517 ++++++++++++------ .../listener/StockInExcelListener.java | 246 +++++++-- .../ReservationStockInOrderImpl.java | 184 +++++-- .../repository/vo/StockInHeader.java | 20 + .../repository/vo/StockInProductDetail.java | 14 +- .../ReservationStockOutOrderMapper.java | 8 +- .../ReservationStockOutOrderMapper.xml | 12 +- 7 files changed, 751 insertions(+), 250 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/DynamicDeliveryExcelListener.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/DynamicDeliveryExcelListener.java index 9b898afb5..07ae79d33 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/DynamicDeliveryExcelListener.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/DynamicDeliveryExcelListener.java @@ -21,20 +21,47 @@ import java.util.Map; /** * 维他奶出库Excel解析监听器(新模板版) * 新模板特点:货品明细与客户配送明细左右分栏展示 - * 核心功能: - * 1. 左侧栏:第1-3行解析表头(送货日期/送货线号/车次),第5行解析纪录合共,第7行起解析货品明细 - * 2. 右侧栏:第2行解析发票张数/小计,第4行起解析客户配送明细 - * 3. 自动处理文本型数字转换(String→Integer/BigDecimal) - * 4. 完整的异常处理和日志记录 + * 核心功能(行列位置均不写死,模板调整行/列顺序不影响解析): + * 1. 表头标签(送货日期/送货线号/车次/发票张数/小计/纪录合共):按标签文字定位标签所在列,取右侧相邻列的值 + * 2. 货品明细:按列头文字(貨品編號/簡稱/箱/支等,繁简体归一化)定位各列,列头行之后的行按数据解析 + * 3. 客户配送明细:按列头文字(類别/發票編號/客户編號/名稱/箱/包/金額等)定位各列 + * 4. 列头行识别失败时兜底:按老模板固定布局(左栏A-D列、右栏F-L列、行号1-7)解析,保证老模板不受影响 + * 5. 自动处理文本型数字转换(String→Integer/BigDecimal) */ public class DynamicDeliveryExcelListener extends AnalysisEventListener> { // 日志实例 private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDeliveryExcelListener.class); - // 关键字定义(Excel标签匹配) - private static final String KEYWORD_TOTAL_RECORD = "紀錄合共"; - private static final String KEYWORD_INVOICE_COUNT = "發票張數"; - private static final String KEYWORD_SUBTOTAL = "小計"; + // ---------------------- 关键字/列头别名定义(归一化后匹配,兼容简繁体) ---------------------- + // 表头标签关键字 + private static final String[] KEYWORD_DELIVERY_DATE = {"送货日期"}; + private static final String[] KEYWORD_DELIVERY_LINE = {"送货线号", "送货线路"}; + private static final String[] KEYWORD_TRAIN = {"车次"}; + private static final String[] KEYWORD_TOTAL_RECORD = {"纪录合共"}; + private static final String[] KEYWORD_INVOICE_COUNT = {"发票张数"}; + private static final String[] KEYWORD_SUBTOTAL = {"小计"}; + + // 货品明细列头别名 + private static final String[] PRODUCT_CODE_ALIASES = {"货品编号", "产品编号", "物料编号", "货品编码", "产品编码", "物料编码", "料号"}; + private static final String[] PRODUCT_NAME_ALIASES = {"简称", "货品名称", "产品名称", "物料名称", "品名", "名称"}; + private static final String[] PRODUCT_BOX_ALIASES = {"箱数", "箱"}; + private static final String[] PRODUCT_PIECE_ALIASES = {"支数", "件数", "支"}; + + // 客户配送明细列头别名 + private static final String[] CATEGORY_ALIASES = {"类别"}; + private static final String[] INVOICE_NO_ALIASES = {"发票编号", "发票号", "发票"}; + private static final String[] CUSTOMER_CODE_ALIASES = {"客户编号", "客户编码", "客户代码"}; + private static final String[] CUSTOMER_NAME_ALIASES = {"客户名称", "名称"}; + private static final String[] CUSTOMER_BOX_ALIASES = {"箱数", "箱"}; + private static final String[] CUSTOMER_PACKAGE_ALIASES = {"包数", "包"}; + private static final String[] CUSTOMER_AMOUNT_ALIASES = {"金额", "总额"}; + + // 列头行认定门槛:锚点列(编号列)必须识别到,且识别列总数达标,防止数据行误判为列头行 + private static final int HEADER_MIN_MATCH_COUNT = 2; + // 老模板兜底:货品明细数据固定从第7行、客户配送明细数据固定从第4行开始(列头未识别到时启用固定列位) + private static final int PRODUCT_DATA_START_ROW = 7; + private static final int CUSTOMER_DATA_START_ROW = 4; + // 支持的日期格式(String转Date) private static final String[] SUPPORTED_DATE_FORMATS = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyyMMdd", "yyyy-MM-dd HH:mm:ss"}; @@ -43,6 +70,26 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener productDetailList = new ArrayList<>(); private final List customerDeliveryDetailList = new ArrayList<>(); + // 货品明细列索引(-1表示未识别该列) + private int productCodeCol = -1; + private int productNameCol = -1; + private int productBoxCol = -1; + private int productPieceCol = -1; + private boolean productColumnsResolved = false; + + // 客户配送明细列索引(-1表示未识别该列) + private int categoryCol = -1; + private int invoiceNoCol = -1; + private int customerCodeCol = -1; + private int customerNameCol = -1; + private int customerBoxCol = -1; + private int customerPackageCol = -1; + private int customerAmountCol = -1; + private boolean customerColumnsResolved = false; + + // 发票张数/小计行是否已解析 + private boolean invoiceRowParsed = false; + // 当前行号(1-based,与Excel行号一致) private int currentRowNum = 0; @@ -53,10 +100,10 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data) { - switch (currentRowNum) { - case 1: - // 第1行:送货日期(A列=标签,B列=值) - parseDeliveryDate(data); - break; - case 2: - // 第2行:送货线号(A列=标签,B列=值) - parseDeliveryLineNo(data); - break; - case 3: - // 第3行:车次(A列=标签,B列=值) - parseTrainNumber(data); - break; - case 4: - // 第4行:货品明细标题行,无需解析 - LOGGER.debug("第{}行:货品明细标题行,跳过", currentRowNum); - break; - case 5: - // 第5行:纪录合共(A列=标签,B列=总记录数,D列=总箱数,E列=总支数) - parseTotalRecord(data); - break; - case 6: - // 第6行:货品明细列标题(貨品編號/簡稱/箱/支),跳过 - LOGGER.debug("第{}行:货品明细列标题行,跳过", currentRowNum); - break; - default: - // 第7行起:货品明细数据行(遇到空行自动结束) - if (currentRowNum >= 7 && isValidProductDetailRow(data)) { - parseProductDetail(data, currentRowNum); - } - break; + // 1. 表头标签:送货日期/送货线号/车次(按标签文字定位,行位置不限) + parseHeaderLabels(data); + // 2. 纪录合共(按标签文字定位,行位置不限) + parseTotalRecord(data); + // 3. 货品明细:列布局已确定时按数据行解析 + if (productColumnsResolved) { + if (isValidProductDetailRow(data)) { + parseProductDetail(data, currentRowNum); + } + return; + } + // 4. 尝试识别货品明细列头行;识别成功则本行即列头行,跳过数据解析 + if (tryResolveProductColumns(data)) { + return; + } + // 5. 超过老模板数据起始行仍未识别到列头行:启用固定列位兜底,从当前行开始按数据行解析 + if (currentRowNum >= PRODUCT_DATA_START_ROW) { + applyProductFallbackColumns(); + if (isValidProductDetailRow(data)) { + parseProductDetail(data, currentRowNum); + } } } // ---------------------- 右侧区域解析(客户配送相关信息) ---------------------- private void parseRightSection(Map data) { - switch (currentRowNum) { - case 1: - // 第1行:客户配送明细标题行,无需解析 - LOGGER.debug("第{}行:客户配送明细标题行,跳过", currentRowNum); - break; - case 2: - // 第2行:发票张数/小计(F列=發票張數标签,G列=数值;J列=小計标签,K/L/M列=数值) - parseInvoiceCount(data); - break; - case 3: - // 第3行:客户配送明细列标题(類别/發票編號/客户編號/名稱/箱/包/金額),跳过 - LOGGER.debug("第{}行:客户配送明细列标题行,跳过", currentRowNum); - break; - default: - // 第4行起:客户配送明细数据行(遇到空行自动结束) - if (currentRowNum >= 4 && isValidCustomerDetailRow(data)) { - parseCustomerDeliveryDetail(data, currentRowNum); - } - break; + // 1. 发票张数/小计(按标签文字定位,行位置不限,解析成功后不再重复解析) + if (!invoiceRowParsed) { + parseInvoiceRow(data); + } + // 2. 客户配送明细:列布局已确定时按数据行解析 + if (customerColumnsResolved) { + if (isValidCustomerDetailRow(data)) { + parseCustomerDeliveryDetail(data, currentRowNum); + } + return; + } + // 3. 尝试识别客户配送明细列头行;识别成功则本行即列头行,跳过数据解析 + if (tryResolveCustomerColumns(data)) { + return; + } + // 4. 超过老模板数据起始行仍未识别到列头行:启用固定列位兜底,从当前行开始按数据行解析 + if (currentRowNum >= CUSTOMER_DATA_START_ROW) { + applyCustomerFallbackColumns(); + if (isValidCustomerDetailRow(data)) { + parseCustomerDeliveryDetail(data, currentRowNum); + } } } // ---------------------- 解析完成回调 ---------------------- @Override public void doAfterAllAnalysed(AnalysisContext context) { + // 小计标签始终未识别到时保持与原逻辑一致:小计相关字段置0 + if (deliveryHeader.getSubtotalAmount() == null) { + LOGGER.warn("未找到【小计】标签,小计信息置空"); + deliveryHeader.setSubtotalBoxCount(0); + deliveryHeader.setSubtotalPieceCount(0); + deliveryHeader.setSubtotalAmount(BigDecimal.ZERO); + } LOGGER.info("Excel解析全部完成!最终统计结果:"); LOGGER.info("1. 表头信息:送货日期={},送货线号={},车次={},发票张数={},总记录数={},小计金额={}", deliveryHeader.getDeliveryDate(), @@ -144,47 +190,52 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data) { - Object value = getValueSafely(data, 1); // B列 - if (value != null) { - Date deliveryDate = convertToDate(value); - deliveryHeader.setDeliveryDate(deliveryDate); - LOGGER.debug("第{}行:送货日期解析完成,值={}", currentRowNum, deliveryDate); + // ---------------------- 左侧:表头信息解析(标签定位,行位置不限) ---------------------- + private void parseHeaderLabels(Map data) { + // 送货日期 + if (deliveryHeader.getDeliveryDate() == null) { + Integer keywordIndex = findKeywordIndex(data, KEYWORD_DELIVERY_DATE); + if (keywordIndex != null) { + Date deliveryDate = convertToDate(getValueSafely(data, keywordIndex + 1)); + if (deliveryDate != null) { + deliveryHeader.setDeliveryDate(deliveryDate); + LOGGER.debug("第{}行:送货日期解析完成,值={}", currentRowNum, deliveryDate); + } + } + } + // 送货线号 + if (deliveryHeader.getDeliveryLineNo() == null || deliveryHeader.getDeliveryLineNo().trim().isEmpty()) { + Integer keywordIndex = findKeywordIndex(data, KEYWORD_DELIVERY_LINE); + if (keywordIndex != null) { + Object value = getValueSafely(data, keywordIndex + 1); + if (value != null && !value.toString().trim().isEmpty()) { + deliveryHeader.setDeliveryLineNo(value.toString().trim()); + LOGGER.debug("第{}行:送货线号解析完成,值={}", currentRowNum, deliveryHeader.getDeliveryLineNo()); + } + } + } + // 车次 + if (deliveryHeader.getTrainNumber() == null) { + Integer keywordIndex = findKeywordIndex(data, KEYWORD_TRAIN); + if (keywordIndex != null) { + deliveryHeader.setTrainNumber(convertToInteger(getValueSafely(data, keywordIndex + 1))); + LOGGER.debug("第{}行:车次解析完成,值={}", currentRowNum, deliveryHeader.getTrainNumber()); + } } } - private void parseDeliveryLineNo(Map data) { - Object value = getValueSafely(data, 1); // B列 - if (value != null) { - String deliveryLineNo = value.toString().trim(); - deliveryHeader.setDeliveryLineNo(deliveryLineNo); - LOGGER.debug("第{}行:送货线号解析完成,值={}", currentRowNum, deliveryLineNo); - } - } - - private void parseTrainNumber(Map data) { - Object value = getValueSafely(data, 1); // B列 - Integer trainNumber = convertToInteger(value); - deliveryHeader.setTrainNumber(trainNumber); - LOGGER.debug("第{}行:车次解析完成,值={}", currentRowNum, trainNumber); - } - - // ---------------------- 左侧:纪录合共解析 ---------------------- + // ---------------------- 左侧:纪录合共解析(标签定位,行位置不限) ---------------------- /** - * 第5行:纪录合共 - * 列对应关系:0=A列=紀錄合共标签,1=B列=总记录数,3=D列=总箱数,4=E列=总支数 + * 纪录合共:标签右侧第1列=总记录数,第3列=总箱数,第4列=总支数(与老模板B/D/E列偏移一致) */ private void parseTotalRecord(Map data) { - Object totalRecordLabel = getValueSafely(data, 0); - if (totalRecordLabel == null || !totalRecordLabel.toString().trim().contains(KEYWORD_TOTAL_RECORD)) { - LOGGER.warn("第{}行:不是纪录合共行,跳过解析,第一列内容:{}", currentRowNum, totalRecordLabel); + Integer keywordIndex = findKeywordIndex(data, KEYWORD_TOTAL_RECORD); + if (keywordIndex == null) { return; } - - Integer totalRecordCount = convertToInteger(getValueSafely(data, 1)); // B列 - Integer totalBoxCount = convertToInteger(getValueSafely(data, 3)); // D列 - Integer totalPieceCount = convertToInteger(getValueSafely(data, 4)); // E列 + Integer totalRecordCount = convertToInteger(getValueSafely(data, keywordIndex + 1)); + Integer totalBoxCount = convertToInteger(getValueSafely(data, keywordIndex + 3)); + Integer totalPieceCount = convertToInteger(getValueSafely(data, keywordIndex + 4)); deliveryHeader.setTotalRecordCount(totalRecordCount); deliveryHeader.setTotalBoxCount(totalBoxCount); @@ -194,27 +245,66 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data) { - Object productCode = getValueSafely(data, 0); // A列 - return productCode != null - && !productCode.toString().trim().isEmpty() - && !"貨品編號".equals(productCode.toString().trim()); + private boolean tryResolveProductColumns(Map data) { + int codeCol = findColumnByAliases(data, PRODUCT_CODE_ALIASES); + if (codeCol < 0) { + return false; + } + int nameCol = findColumnByAliases(data, PRODUCT_NAME_ALIASES); + int boxCol = findColumnByAliases(data, PRODUCT_BOX_ALIASES); + int pieceCol = findColumnByAliases(data, PRODUCT_PIECE_ALIASES); + int matched = 1 + (nameCol >= 0 ? 1 : 0) + (boxCol >= 0 ? 1 : 0) + (pieceCol >= 0 ? 1 : 0); + if (matched < HEADER_MIN_MATCH_COUNT) { + return false; + } + productCodeCol = codeCol; + productNameCol = nameCol; + productBoxCol = boxCol; + productPieceCol = pieceCol; + productColumnsResolved = true; + LOGGER.info("第{}行识别为货品明细列头行,列映射:货品编号={},名称={},箱={},支={}", + currentRowNum, productCodeCol, productNameCol, productBoxCol, productPieceCol); + return true; } /** - * 解析货品明细(第7行起) - * 列对应关系:0=A列=貨品編號,1=B列=簡稱,2=C列=箱,3=D列=支 + * 兜底列位:老模板货品明细固定布局(0=A列=貨品編號,1=B列=簡稱,2=C列=箱,3=D列=支) + */ + private void applyProductFallbackColumns() { + productCodeCol = 0; + productNameCol = 1; + productBoxCol = 2; + productPieceCol = 3; + productColumnsResolved = true; + LOGGER.warn("第{}行前未识别到货品明细列头行,启用老模板固定列位兜底解析", currentRowNum); + } + + /** + * 校验货品明细行是否有效(排除列头行和空行) + */ + private boolean isValidProductDetailRow(Map data) { + Object productCode = getValueSafely(data, productCodeCol); + if (productCode == null || productCode.toString().trim().isEmpty()) { + return false; + } + // 列头文字本身不算数据行(防止列头行被当作数据重复解析) + String text = normalizeHeaderText(productCode); + return !matchesAny(text, PRODUCT_CODE_ALIASES); + } + + /** + * 解析货品明细(列头行之后的行) */ private void parseProductDetail(Map data, int rowNum) { ProductDetail productDetail = new ProductDetail(); - productDetail.setProductCode(convertToString(getValueSafely(data, 0))); - productDetail.setProductName(convertToString(getValueSafely(data, 1))); - productDetail.setBoxCount(convertToInteger(getValueSafely(data, 2))); - productDetail.setPieceCount(convertToInteger(getValueSafely(data, 3))); + productDetail.setProductCode(convertToString(getValueSafely(data, productCodeCol))); + productDetail.setProductName(convertToString(getValueSafely(data, productNameCol))); + productDetail.setBoxCount(convertToInteger(getValueSafely(data, productBoxCol))); + productDetail.setPieceCount(convertToInteger(getValueSafely(data, productPieceCol))); productDetailList.add(productDetail); LOGGER.debug("第{}行:解析货品明细完成,货品编号={},名称={},箱数={},支数={}", @@ -222,67 +312,105 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data) { - // 1. 定位并解析发票张数 - Integer invoiceCount = findValueAfterKeyword(data, KEYWORD_INVOICE_COUNT); - if (invoiceCount != null) { - deliveryHeader.setInvoiceCount(invoiceCount); + // ---------------------- 右侧:发票张数与小计解析(标签定位,行位置不限) ---------------------- + private void parseInvoiceRow(Map data) { + // 1. 发票张数:标签右侧相邻列 + Integer invoiceIndex = findKeywordIndex(data, KEYWORD_INVOICE_COUNT); + if (invoiceIndex != null) { + deliveryHeader.setInvoiceCount(convertToInteger(getValueSafely(data, invoiceIndex + 1))); } - // 2. 定位并解析小计(标签右侧第1列=箱,第2列=支,第3列=金额) - Integer subtotalStartIndex = findKeywordIndex(data, KEYWORD_SUBTOTAL); - if (subtotalStartIndex != null && subtotalStartIndex >= 0) { - Integer subtotalBoxCount = convertToInteger(getValueSafely(data, subtotalStartIndex + 1)); - Integer subtotalPieceCount = convertToInteger(getValueSafely(data, subtotalStartIndex + 2)); - BigDecimal subtotalAmount = convertToBigDecimal(getValueSafely(data, subtotalStartIndex + 3)); + // 2. 小计:标签右侧第1列=箱,第2列=支,第3列=金额 + Integer subtotalIndex = findKeywordIndex(data, KEYWORD_SUBTOTAL); + if (subtotalIndex != null && subtotalIndex >= 0) { + Integer subtotalBoxCount = convertToInteger(getValueSafely(data, subtotalIndex + 1)); + Integer subtotalPieceCount = convertToInteger(getValueSafely(data, subtotalIndex + 2)); + BigDecimal subtotalAmount = convertToBigDecimal(getValueSafely(data, subtotalIndex + 3)); deliveryHeader.setSubtotalBoxCount(subtotalBoxCount); deliveryHeader.setSubtotalPieceCount(subtotalPieceCount); deliveryHeader.setSubtotalAmount(subtotalAmount); + invoiceRowParsed = true; - LOGGER.debug("第{}行:解析小计完成,箱数={},支数={},金额={}", - currentRowNum, subtotalBoxCount, subtotalPieceCount, subtotalAmount); - } else { - LOGGER.warn("第{}行:未找到小计标签,小计信息置空", currentRowNum); - deliveryHeader.setSubtotalBoxCount(0); - deliveryHeader.setSubtotalPieceCount(0); - deliveryHeader.setSubtotalAmount(BigDecimal.ZERO); + LOGGER.debug("第{}行:解析发票张数行完成,发票张数={},小计箱数={},支数={},金额={}", + currentRowNum, deliveryHeader.getInvoiceCount(), subtotalBoxCount, subtotalPieceCount, subtotalAmount); } - - LOGGER.debug("第{}行:解析发票张数行完成,发票张数={},小计金额={}", - currentRowNum, deliveryHeader.getInvoiceCount(), deliveryHeader.getSubtotalAmount()); } - // ---------------------- 右侧:客户配送明细解析 ---------------------- + // ---------------------- 右侧:客户配送明细列头识别与数据解析 ---------------------- /** - * 校验客户配送明细行是否有效(排除表头和空行) + * 尝试将当前行识别为客户配送明细列头行:以客户编号列为锚点,按列头文字(简繁归一化)匹配各列 + */ + private boolean tryResolveCustomerColumns(Map data) { + int codeCol = findColumnByAliases(data, CUSTOMER_CODE_ALIASES); + if (codeCol < 0) { + return false; + } + int categoryColTmp = findColumnByAliases(data, CATEGORY_ALIASES); + int invoiceNoColTmp = findColumnByAliases(data, INVOICE_NO_ALIASES); + int nameColTmp = findColumnByAliases(data, CUSTOMER_NAME_ALIASES); + int boxColTmp = findColumnByAliases(data, CUSTOMER_BOX_ALIASES); + int packageColTmp = findColumnByAliases(data, CUSTOMER_PACKAGE_ALIASES); + int amountColTmp = findColumnByAliases(data, CUSTOMER_AMOUNT_ALIASES); + int matched = 1 + (categoryColTmp >= 0 ? 1 : 0) + (invoiceNoColTmp >= 0 ? 1 : 0) + (nameColTmp >= 0 ? 1 : 0) + + (boxColTmp >= 0 ? 1 : 0) + (packageColTmp >= 0 ? 1 : 0) + (amountColTmp >= 0 ? 1 : 0); + if (matched < HEADER_MIN_MATCH_COUNT) { + return false; + } + categoryCol = categoryColTmp; + invoiceNoCol = invoiceNoColTmp; + customerCodeCol = codeCol; + customerNameCol = nameColTmp; + customerBoxCol = boxColTmp; + customerPackageCol = packageColTmp; + customerAmountCol = amountColTmp; + customerColumnsResolved = true; + LOGGER.info("第{}行识别为客户配送明细列头行,列映射:类别={},发票编号={},客户编号={},名称={},箱={},包={},金额={}", + currentRowNum, categoryCol, invoiceNoCol, customerCodeCol, customerNameCol, customerBoxCol, customerPackageCol, customerAmountCol); + return true; + } + + /** + * 兜底列位:老模板客户配送明细固定布局(5=F列=類别,6=G列=發票編號,7=H列=客户編號,8=I列=名稱, + * 9=J列=箱,10=K列=包,11=L列=金額) + */ + private void applyCustomerFallbackColumns() { + categoryCol = 5; + invoiceNoCol = 6; + customerCodeCol = 7; + customerNameCol = 8; + customerBoxCol = 9; + customerPackageCol = 10; + customerAmountCol = 11; + customerColumnsResolved = true; + LOGGER.warn("第{}行前未识别到客户配送明细列头行,启用老模板固定列位兜底解析", currentRowNum); + } + + /** + * 校验客户配送明细行是否有效(排除列头行和空行) */ private boolean isValidCustomerDetailRow(Map data) { - Object category = getValueSafely(data, 5); // F列 - return category != null - && !category.toString().trim().isEmpty() - && !"類别".equals(category.toString().trim()); + Object category = getValueSafely(data, categoryCol); + if (category == null || category.toString().trim().isEmpty()) { + return false; + } + // 列头文字本身不算数据行(防止列头行被当作数据重复解析) + String text = normalizeHeaderText(category); + return !matchesAny(text, CATEGORY_ALIASES); } /** - * 解析客户配送明细(第4行起) - * 列对应关系:5=F列=類别,6=G列=發票編號,7=H列=客户編號,8=I列=名稱, - * 9=J列=箱,10=K列=包,11=L列=金額 + * 解析客户配送明细(列头行之后的行) */ private void parseCustomerDeliveryDetail(Map data, int rowNum) { CustomerDeliveryDetail customerDetail = new CustomerDeliveryDetail(); - customerDetail.setCategory(convertToString(getValueSafely(data, 5))); - customerDetail.setInvoiceNo(convertToString(getValueSafely(data, 6))); - customerDetail.setCustomerCode(convertToString(getValueSafely(data, 7))); - customerDetail.setCustomerName(convertToString(getValueSafely(data, 8))); - customerDetail.setBoxCount(convertToInteger(getValueSafely(data, 9))); - customerDetail.setPackageCount(convertToInteger(getValueSafely(data, 10))); - customerDetail.setAmount(convertToBigDecimal(getValueSafely(data, 11))); + customerDetail.setCategory(convertToString(getValueSafely(data, categoryCol))); + customerDetail.setInvoiceNo(convertToString(getValueSafely(data, invoiceNoCol))); + customerDetail.setCustomerCode(convertToString(getValueSafely(data, customerCodeCol))); + customerDetail.setCustomerName(convertToString(getValueSafely(data, customerNameCol))); + customerDetail.setBoxCount(convertToInteger(getValueSafely(data, customerBoxCol))); + customerDetail.setPackageCount(convertToInteger(getValueSafely(data, customerPackageCol))); + customerDetail.setAmount(convertToBigDecimal(getValueSafely(data, customerAmountCol))); customerDeliveryDetailList.add(customerDetail); LOGGER.debug("第{}行:解析客户配送明细完成,类别={},发票编号={},客户编号={},名称={},箱数={},包数={},金额={}", @@ -299,7 +427,7 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data, int columnIndex) { - if (data == null || data.isEmpty()) { + if (columnIndex < 0 || data == null || data.isEmpty()) { return null; } @@ -333,9 +461,9 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data, String keyword) { - if (data == null || data.isEmpty() || keyword == null) { + private Integer findKeywordIndex(Map data, String[] keywords) { + if (data == null || data.isEmpty() || keywords == null) { return null; } for (Object keyObj : data.keySet()) { Object value = data.get(keyObj); - if (value != null && value.toString().trim().contains(keyword)) { + if (value == null) { + continue; + } + String text = normalizeHeaderText(value); + if (text.isEmpty()) { + continue; + } + if (matchesAny(text, keywords)) { try { return Integer.parseInt(keyObj.toString()); } catch (NumberFormatException e) { - // key不是数字(可能是表头文本如"客户配送明细"),跳过,不打印警告避免日志刷屏 - LOGGER.debug("第{}行:关键字'{}'所在列key不是数字,key={},跳过", - currentRowNum, keyword, keyObj); + // key不是数字(可能是表头文本),跳过,不打印警告避免日志刷屏 + LOGGER.debug("第{}行:关键字所在列key不是数字,key={},跳过", currentRowNum, keyObj); } } } @@ -366,15 +500,64 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener data, String keyword) { - Integer keywordIndex = findKeywordIndex(data, keyword); - if (keywordIndex == null) { - LOGGER.warn("第{}行:未找到关键字'{}'", currentRowNum, keyword); - return null; + private int findColumnByAliases(Map data, String[] aliases) { + if (data == null || data.isEmpty() || aliases == null) { + return -1; } - return convertToInteger(getValueSafely(data, keywordIndex + 1)); + for (Object keyObj : data.keySet()) { + Object value = data.get(keyObj); + if (value == null) { + continue; + } + String text = normalizeHeaderText(value); + if (text.isEmpty()) { + continue; + } + if (matchesAny(text, aliases)) { + try { + return Integer.parseInt(keyObj.toString()); + } catch (NumberFormatException e) { + // key不是数字(可能是表头文本),跳过 + LOGGER.debug("第{}行:列头所在列key不是数字,key={},跳过", currentRowNum, keyObj); + } + } + } + return -1; + } + + /** + * 列头/标签文字归一化:去空格/冒号/括号等符号、转小写、繁体转简体(覆盖模板常见用字) + */ + private String normalizeHeaderText(Object value) { + if (value == null) { + return ""; + } + String s = value.toString().trim().toLowerCase(); + s = s.replace(":", "").replace(":", "") + .replace(" ", "").replace(" ", "") + .replace("(", "").replace(")", "") + .replace("(", "").replace(")", "") + .replace("/", "").replace("\\", ""); + s = s.replace('貨', '货').replace('編', '编').replace('號', '号').replace('稱', '称') + .replace('數', '数').replace('飲', '饮').replace('規', '规').replace('戶', '户') + .replace('紀', '纪').replace('錄', '录').replace('張', '张').replace('計', '计') + .replace('車', '车').replace('線', '线').replace('類', '类').replace('別', '别') + .replace('簡', '简').replace('產', '产').replace('單', '单'); + return s; + } + + /** + * 判断归一化后的文字是否命中任一别名 + */ + private boolean matchesAny(String text, String[] aliases) { + for (String alias : aliases) { + if (text.contains(alias)) { + return true; + } + } + return false; } /** diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/StockInExcelListener.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/StockInExcelListener.java index c7df783b9..f8493da8c 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/StockInExcelListener.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/listener/StockInExcelListener.java @@ -17,7 +17,11 @@ import java.util.Map; /** * 入库单Excel解析监听器 - * 功能:1. 解析第一行表头数据 2. 跳过第二行合计行、第三行列名行 3. 解析第四行开始的产品明细 4. 支持文本型数字转换 + * 功能:1. 解析第一行表头数据(入库日期、OrderNO,按标签定位,位置不敏感) + * 2. 动态定位列头行:扫描前几行,识别出"產品編號"等已知列头文字的行即认定为列头行(行位置不敏感) + * 3. 列按列头文字动态定位(兼容繁体列头:產品編號/客戶編號/件數/飲用日期等),列顺序调整不影响解析 + * 4. 列头无法识别时兜底:按老模板固定列位(0=编号,1=名称,2=规格,3=件数,4=饮用日期) + * 5. 支持文本型数字转换 */ public class StockInExcelListener extends AnalysisEventListener> { private static final Logger LOGGER = LoggerFactory.getLogger(StockInExcelListener.class); @@ -35,6 +39,29 @@ public class StockInExcelListener extends AnalysisEventListener= 4) { - if (isValidProductRow(data)) { - StockInProductDetail detail = parseProductDetail(data); - productDetailList.add(detail); + // 2. 列布局未确定:尝试识别列头行;识别成功则本行即为列头行,跳过数据解析 + else if (!columnResolved) { + if (!tryResolveHeaderRow(data)) { + // 3. 超过老模板列头行位置仍未识别到列头行:启用固定列位兜底,从当前行开始按数据行解析 + if (currentRowNum > FALLBACK_AFTER_ROW_NUM) { + applyFallbackColumns(); + parseDataRow(data); + } + // 否则(合计行Total等):跳过 } } + // 4. 列布局已确定:按数据行解析 + else { + parseDataRow(data); + } } catch (ExcelDataConvertException e) { // Excel内置转换异常(如日期格式错误) @@ -79,6 +106,16 @@ public class StockInExcelListener extends AnalysisEventListener data) { + if (isValidProductRow(data)) { + StockInProductDetail detail = parseProductDetail(data); + productDetailList.add(detail); + } + } + /** * 解析第一行表头数据 * 模板中标签与值相邻,分别搜索"入库日期"和"OrderNO"标签后一列的值,兼容位置调整 @@ -86,15 +123,17 @@ public class StockInExcelListener extends AnalysisEventListener data) { // 入库日期:查找包含"入库日期"标签的下一个值 Object stockInDateObj = getValueAfterLabel(data, "入庫日期:"); + if (stockInDateObj == null) { + stockInDateObj = getValueAfterLabel(data, "入库日期"); + } if (stockInDateObj != null) { Date stockInDate = convertToDate(stockInDateObj); stockInHeader.setStockInDate(stockInDate); LOGGER.debug("第1行:入库日期解析完成,值:{}", stockInDate); } - // OrderNO:优先查找"OrderNO"标签后的值(老模板布局,标签与值相邻); - // 新模板第1行无"OrderNO"标签,单号值直接放在单元格中(如E1), - // 此时取第1行中除标签、入库日期以外的第一个非空字符串值 + // OrderNO:优先查找"OrderNO"标签后的值(标签与值相邻); + // 新模板第1行无"OrderNO"标签时,取第1行中除标签、入库日期以外的第一个非空字符串值 Object orderNoObj = getValueAfterLabel(data, "OrderNO"); if (orderNoObj == null) { for (Object value : data.values()) { @@ -103,7 +142,7 @@ public class StockInExcelListener extends AnalysisEventListener data) { + if (data == null || data.isEmpty()) { + return false; + } + int tmpProductCodeCol = -1; + int tmpProductNameCol = -1; + int tmpCustomerCodeCol = -1; + int tmpDrinkDateCol = -1; + int tmpPieceCountCol = -1; + int tmpSpecCol = -1; + int matched = 0; + + for (Object keyObj : data.keySet()) { + Object value = data.get(keyObj); + if (value == null) { + continue; + } + String text = normalizeHeaderText(value); + if (text.isEmpty()) { + continue; + } + int colIndex; + try { + colIndex = Integer.parseInt(keyObj.toString()); + } catch (NumberFormatException e) { + // key不是数字(如表头文本),跳过 + continue; + } + // 按组依次匹配(先具体后一般,避免同一列头命中多组别名时归属错乱) + if (tmpProductCodeCol < 0 && matchesAny(text, PRODUCT_CODE_ALIASES)) { + tmpProductCodeCol = colIndex; + matched++; + } else if (tmpCustomerCodeCol < 0 && matchesAny(text, CUSTOMER_CODE_ALIASES)) { + tmpCustomerCodeCol = colIndex; + matched++; + } else if (tmpProductNameCol < 0 && matchesAny(text, PRODUCT_NAME_ALIASES)) { + tmpProductNameCol = colIndex; + matched++; + } else if (tmpDrinkDateCol < 0 && matchesAny(text, DRINK_DATE_ALIASES)) { + tmpDrinkDateCol = colIndex; + matched++; + } else if (tmpPieceCountCol < 0 && matchesAny(text, PIECE_COUNT_ALIASES)) { + tmpPieceCountCol = colIndex; + matched++; + } else if (tmpSpecCol < 0 && matchesAny(text, SPEC_ALIASES)) { + tmpSpecCol = colIndex; + matched++; + } + } + + // 产品编号列是数据解析的锚点,必须识别到;且识别总数达标才认定为列头行 + if (tmpProductCodeCol < 0 || matched < HEADER_MIN_MATCH_COUNT) { + return false; + } + + productCodeCol = tmpProductCodeCol; + productNameCol = tmpProductNameCol; + customerCodeCol = tmpCustomerCodeCol; + drinkDateCol = tmpDrinkDateCol; + pieceCountCol = tmpPieceCountCol; + specCol = tmpSpecCol; + columnResolved = true; + LOGGER.info("第{}行识别为列头行,列映射:产品编号={},产品名称={},客户编号={},规格={},件数={},饮用日期={}", + currentRowNum, productCodeCol, productNameCol, customerCodeCol, specCol, pieceCountCol, drinkDateCol); + return true; + } + + /** + * 兜底列位:老模板固定布局(0=编号,1=名称,2=规格,3=件数,4=饮用日期),无客户编号列 + */ + private void applyFallbackColumns() { + productCodeCol = 0; + productNameCol = 1; + customerCodeCol = -1; + specCol = 2; + pieceCountCol = 3; + drinkDateCol = 4; + columnResolved = true; + LOGGER.warn("第{}行前未识别到列头行,启用老模板固定列位兜底解析", currentRowNum); + } + /** * 解析产品明细数据(一行对应一个产品) - * 新模板列顺序:0=产品编号,1=产品名称,2=规格(箱含量),3=件数,4=饮用日期 + * 各列按列头行动态定位的索引取值,未识别的列取null/默认值 */ private StockInProductDetail parseProductDetail(Map data) { StockInProductDetail detail = new StockInProductDetail(); - // 产品编号(第1列,索引0)- 支持文本型数字转换 - detail.setProductCode(convertToString(getValueSafely(data, 0))); - // 产品名称(第2列,索引1) - detail.setProductName(convertToString(getValueSafely(data, 1))); - // 箱含量/规格(第3列,索引2) - detail.setBoxCapacity(convertToInteger(getValueSafely(data, 2))); - // 件数(第4列,索引3) - detail.setPieceCount(convertToInteger(getValueSafely(data, 3))); - // 饮用日期(第5列,索引4) - detail.setDrinkDate(convertToDate(getValueSafely(data, 4))); + // 产品编号(可不填名称,导入时按编号自动匹配库中物料名称) + detail.setProductCode(convertToString(getValueSafely(data, productCodeCol))); + // 产品名称(列头未识别或单元格未填时为空串,由服务端按编号自动匹配覆盖) + detail.setProductName(convertToString(getValueSafely(data, productNameCol))); + // 客户编号(新模板新增列,可不填;填写后由服务端校验客户存在性并自动匹配客户名称) + detail.setCustomerCode(convertToString(getValueSafely(data, customerCodeCol))); + // 箱含量/规格 + detail.setBoxCapacity(convertToInteger(getValueSafely(data, specCol))); + // 件数 + detail.setPieceCount(convertToInteger(getValueSafely(data, pieceCountCol))); + // 饮用日期 + detail.setDrinkDate(convertToDate(getValueSafely(data, drinkDateCol))); - LOGGER.debug("第{}行产品明细解析完成,产品编号:{},产品名称:{}", - currentRowNum, detail.getProductCode(), detail.getProductName()); + LOGGER.debug("第{}行产品明细解析完成,产品编号:{},产品名称:{},客户编号:{}", + currentRowNum, detail.getProductCode(), detail.getProductName(), detail.getCustomerCode()); return detail; } @@ -152,9 +278,6 @@ public class StockInExcelListener extends AnalysisEventListener data) { - // 有效条件:产品编号不为空 + 不是合计行 - Object productCodeObj = getValueSafely(data, 0); - // 排除合计行(整行包含"Total"字样) if (containsAnyValue(data, "Total")) { LOGGER.debug("第{}行是合计行,跳过解析", currentRowNum); @@ -162,6 +285,7 @@ public class StockInExcelListener extends AnalysisEventListener data, int columnIndex) { - if (data == null || data.isEmpty()) { + if (columnIndex < 0 || data == null || data.isEmpty()) { return null; } + String strIndex = String.valueOf(columnIndex); + // 1. 直接按key查找(key为String或Integer,跳过非数字key) + for (Object keyObj : data.keySet()) { + if (keyObj != null && strIndex.equals(keyObj.toString())) { + return data.get(keyObj); + } + } + + // 2. 兜底:按顺序位置查找 int index = 0; for (Object value : data.values()) { if (index == columnIndex) { @@ -193,7 +328,7 @@ public class StockInExcelListener extends AnalysisEventListener getProductDetailList() { return productDetailList; } -} \ No newline at end of file +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java index 5116ef9b0..2503b839f 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java @@ -172,10 +172,10 @@ public class ReservationStockInOrderImpl extends ServiceImpl> shipperIds = new ArrayList<>(); Set processedShipperIds = new HashSet<>(); + // ====== 物料存在性校验(落库前):查询结果为空说明库中没有该物料,收集全部缺失料号后一次性提示维护 ====== + Map materialCache = new LinkedHashMap<>(); + List missingMaterials = new ArrayList<>(); for (ProductDetail productDetail : productDetailList) { - MaterialBaseInfoPO materialBaseInfoPO = stockInOrderMapper.getByCode(productDetail.getProductCode(),loginUser.getUserPo().getOrganizationId()); - if (materialBaseInfoPO != null && materialBaseInfoPO.getShipperId() != null) { - Long shipperId = materialBaseInfoPO.getShipperId(); - if (processedShipperIds.add(shipperId)) { - Map shipper = new HashMap<>(); - shipper.put("shipperId", shipperId); - shipper.put("shipperName", materialBaseInfoPO.getShipperName()); - shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); - shipperIds.add(shipper); - } + String productCode = productDetail.getProductCode() == null ? "" : productDetail.getProductCode().trim(); + if (productCode.isEmpty()) { + throw new Exception("出库明细存在未填写產品編號的行,请补全后重新导入"); } + if (materialCache.containsKey(productCode)) { + continue; + } + MaterialBaseInfoPO materialBaseInfoPO = stockInOrderMapper.getByCode(productCode, loginUser.getUserPo().getOrganizationId()); + if (materialBaseInfoPO == null) { + missingMaterials.add(productCode); + } else { + materialCache.put(productCode, materialBaseInfoPO); + } + } + if (!missingMaterials.isEmpty()) { + throw new Exception("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息后再导入"); + } + for (MaterialBaseInfoPO materialBaseInfoPO : materialCache.values()) { + if (materialBaseInfoPO.getShipperId() != null && processedShipperIds.add(materialBaseInfoPO.getShipperId())) { + Map shipper = new HashMap<>(); + shipper.put("shipperId", materialBaseInfoPO.getShipperId()); + shipper.put("shipperName", materialBaseInfoPO.getShipperName()); + shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); + shipperIds.add(shipper); + } + } + + // ====== 客户存在性校验(落库前):客户编号在库中查询结果为空说明客户不存在,提示维护后再导入 ====== + Map customerCache = new HashMap<>(); + for (CustomerDeliveryDetail customerDeliveryDetail : customerDeliveryDetailList) { + String customerCode = customerDeliveryDetail.getCustomerCode() == null ? "" : customerDeliveryDetail.getCustomerCode().trim(); + if (customerCode.isEmpty()) { + throw new Exception("客户配送明细存在未填写客户编号的行,请补全后重新导入"); + } + if (customerCache.containsKey(customerCode)) { + continue; + } + UserPo customer = stockOutOrderMapper.queryCustomerByCode(customerCode, loginUser.getUserPo().getOrganizationId()); + if (customer == null || customer.getUserId() == null) { + throw new Exception("客户[" + customerCode + "]不存在,请先维护客户信息后再导入"); + } + customerCache.put(customerCode, customer); } // 货品明细不为空时,校验货主信息 @@ -330,7 +364,8 @@ public class ReservationStockInOrderImpl extends ServiceImpl productDetailList = resultDTO.getProductDetailList(); + if (productDetailList == null || productDetailList.isEmpty()) { + throw new ServiceException("未解析到产品明细数据,请检查模板中是否填写了產品編號"); + } + StockInHeader stockInHeader = resultDTO.getStockInHeader(); + // ====== 物料存在性校验:模板只填物料编号即可,物料名称按编号自动匹配库中数据 ====== + // 查询结果为空说明库中没有该物料,收集全部缺失料号后一次性提示维护 + Map materialCache = new LinkedHashMap<>(); + List missingMaterials = new ArrayList<>(); + for (StockInProductDetail productDetail : productDetailList) { + String productCode = productDetail.getProductCode(); + if (materialCache.containsKey(productCode)) { + continue; + } + MaterialBaseInfoPO materialBaseInfoPO = stockInOrderMapper.getByCode(productCode, loginUser.getUserPo().getOrganizationId()); + if (materialBaseInfoPO == null) { + missingMaterials.add(productCode); + } else { + materialCache.put(productCode, materialBaseInfoPO); + } + } + if (!missingMaterials.isEmpty()) { + throw new ServiceException("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息后再导入"); + } + // 明细回填库中匹配到的物料名称(自动匹配,模板中的名称列可不填/以库中为准;库中名称为空时保留模板值) + for (StockInProductDetail productDetail : productDetailList) { + String materialName = materialCache.get(productDetail.getProductCode()).getMaterialName(); + if (materialName != null && !materialName.trim().isEmpty()) { + productDetail.setProductName(materialName); + } + } + // ====== 物料归属货主集合(用于客户编号一致性校验,及未填客户编号时的兜底取值) ====== List> shipperIds = new ArrayList<>(); Set processedShipperIds = new HashSet<>(); - for (StockInProductDetail productDetail : productDetailList) { - MaterialBaseInfoPO materialBaseInfoPO = stockInOrderMapper.getByCode(productDetail.getProductCode(),loginUser.getUserPo().getOrganizationId()); - // 物料在当前登录组织下不存在时给出明确提示,避免后续货主取值越界导致500 - if (materialBaseInfoPO == null) { - throw new ServiceException("产品编号[" + productDetail.getProductCode() + "]在当前组织下不存在,请核对产品编号或使用物料所属组织的账号导入"); - } - if (materialBaseInfoPO.getShipperId() != null) { - Long shipperId = materialBaseInfoPO.getShipperId(); - if (processedShipperIds.add(shipperId)) { - Map shipper = new HashMap<>(); - shipper.put("shipperId", shipperId); - shipper.put("shipperName", materialBaseInfoPO.getShipperName()); - shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); - shipperIds.add(shipper); - } + for (MaterialBaseInfoPO materialBaseInfoPO : materialCache.values()) { + if (materialBaseInfoPO.getShipperId() != null && processedShipperIds.add(materialBaseInfoPO.getShipperId())) { + Map shipper = new HashMap<>(); + shipper.put("shipperId", materialBaseInfoPO.getShipperId()); + shipper.put("shipperName", materialBaseInfoPO.getShipperName()); + shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); + shipperIds.add(shipper); } } - if (shipperIds.isEmpty()) { - throw new ServiceException("导入的产品均未配置货主,请先在物料档案中维护货主信息"); + if (shipperIds.size() > 1) { + StringBuilder shipperCodeStr = new StringBuilder(); + for (Map shipper : shipperIds) { + if (shipperCodeStr.length() > 0) { + shipperCodeStr.append("、"); + } + shipperCodeStr.append(shipper.get("shipperCode")); + } + throw new ServiceException("导入的物料不属于同一货主(货主编号:" + shipperCodeStr + "),一个入库单只能属于一个货主"); + } + // ====== 客户编号校验与名称自动匹配:模板只填客户编号,客户名称以库中数据为准 ====== + UserPo customer = null; + Set customerCodes = new LinkedHashSet<>(); + for (StockInProductDetail productDetail : productDetailList) { + if (productDetail.getCustomerCode() != null && !productDetail.getCustomerCode().trim().isEmpty()) { + customerCodes.add(productDetail.getCustomerCode().trim()); + } + } + if (customerCodes.size() > 1) { + throw new ServiceException("导入明细中存在多个客户编号(" + String.join("、", customerCodes) + "),一个入库单只能属于一个客户"); + } + if (!customerCodes.isEmpty()) { + String customerCode = customerCodes.iterator().next(); + // 查询结果为空说明库中没有该客户,提示维护后再导入 + customer = stockOutOrderMapper.queryCustomerByCode(customerCode, loginUser.getUserPo().getOrganizationId()); + if (customer == null || customer.getUserId() == null) { + throw new ServiceException("客户[" + customerCode + "]不存在,请先维护客户信息后再导入"); + } + // 客户编号与物料归属货主一致性校验(不一致拦截,提示两者编号;货主编码未维护时提示货主名称) + if (!shipperIds.isEmpty() && !customer.getUserId().equals(shipperIds.get(0).get("shipperId"))) { + Object materialShipperCode = shipperIds.get(0).get("shipperCode"); + String materialShipperDisplay = (materialShipperCode != null && !materialShipperCode.toString().trim().isEmpty()) + ? "货主编号[" + materialShipperCode + "]" : "货主[" + shipperIds.get(0).get("shipperName") + "]"; + throw new ServiceException("客户编号[" + customerCode + "]与物料归属" + materialShipperDisplay + "]不一致,请核对后再导入"); + } + stockInHeader.setCustomerCode(customerCode); + stockInHeader.setCustomerName(customer.getUserName()); } ReservationStockInOrderDO reservationStockInOrderDO = new ReservationStockInOrderDO(); - StockInHeader stockInHeader = resultDTO.getStockInHeader(); // 业务单号:优先使用模板自带OrderNO(不做格式规则校验),与系统已有业务单号重复时提示不允许导入; // 入库业务单号(in_order_number)不使用模板单号,统一由系统原本逻辑生成(见insertDomain); // 模板未填单号时保持系统逻辑:业务单号默认=入库业务单号 @@ -515,9 +612,19 @@ public class ReservationStockInOrderImpl extends ServiceImpl queryOutStockNoticePrintDetail(ReservationStockOutOrderDO stockOutOrderDO); - Long queryShipperId(@Param("userMemberCode") String userMemberCode,@Param("organizationId") Long organizationId); + /** + * 按客户编号+组织查询客户(用户表),返回客户ID/编号/名称 + * 客户编号兼容两种编码:USER.USER_MEMBER_CODE(客户编码)或 USER_SHIPPER.CUSTOMER_NC_CODE(NC客户代码/khdm),任一命中即可 + * 用于出入库导入时校验客户是否存在并自动匹配客户名称;结果为空表示库中无该客户 + */ + UserPo queryCustomerByCode(@Param("userMemberCode") String userMemberCode, @Param("organizationId") Long organizationId); int countByOrderNumberPrefix(@Param("prefix") String prefix); diff --git a/mhd_oms/src/main/resources/mapper/reservationStockOutOrder/ReservationStockOutOrderMapper.xml b/mhd_oms/src/main/resources/mapper/reservationStockOutOrder/ReservationStockOutOrderMapper.xml index 68fd934af..a83d8f6e9 100644 --- a/mhd_oms/src/main/resources/mapper/reservationStockOutOrder/ReservationStockOutOrderMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reservationStockOutOrder/ReservationStockOutOrderMapper.xml @@ -470,8 +470,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + select max(u.user_id) as user_id, max(u.user_name) as user_name, max(u.user_member_code) as user_member_code + from NGWL_TEST_USER."USER" u + left join NGWL_TEST_USER.USER_SHIPPER us + on us.USER_ID = u.USER_ID and us.DEL_FLAG = 1 + where u.ORGANIZATION_ID = #{organizationId} and u.DEL_FLAG = 1 + and (u.USER_MEMBER_CODE = #{userMemberCode} or us.CUSTOMER_NC_CODE = #{userMemberCode}) + + + \ No newline at end of file From a983ae639ecf4079dd09b33a277ff152c701bad7 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Mon, 31 Aug 2026 15:59:54 +0800 Subject: [PATCH 13/19] =?UTF-8?q?fix(wms,oms)=EF=BC=9A=E7=89=A9=E6=96=99/?= =?UTF-8?q?=E8=BF=87=E6=9C=9F=E6=97=A5=E6=9C=9F=E6=9F=A5=E8=AF=A2=E8=A1=A5?= =?UTF-8?q?=E6=9F=A5=E5=85=A5=E5=BA=93=E6=97=A5=E6=9C=9F(create=5Ftime)?= =?UTF-8?q?=E4=B8=8E=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=EF=BC=8C=E5=88=86?= =?UTF-8?q?=E7=BB=84=E9=A1=B5=E7=AD=BE=E5=85=A5=E5=BA=93=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MaterialInventoryMapper.xml | 4 ++++ .../MaterialInventoryMapper.xml | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml index 1bea8b465..200226335 100644 --- a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml @@ -379,6 +379,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" IFNULL(SUM(a.VOLUME), 0) AS VOLUME, MAX(a.unit) AS unit, MAX(a.unit_name) AS unit_name, + MAX(a.create_time) AS create_time, + MAX(a.update_time) AS update_time, MAX(a.production_date) AS production_date, MAX(a.inventory_date) AS inventory_date, MAX(a.purchase_date) AS purchase_date, @@ -386,6 +388,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" MAX(a.bar_code) AS inv_bar_code, MAX(a.in_order_number) AS in_order_number, MAX(a.lot_number) AS lot_number, b.material_code, b.material_name, + MAX(b.material_classify_code) AS material_classify_code, + MAX(b.material_classify_name) AS material_classify_name, CASE WHEN IFNULL(SUM(a.inventory_quantity), 0) = 0 THEN 1 ELSE 0 END AS sort_order from reservation_material_inventory a left join NGWL_TEST_WMS.material_base_info b on a.material_base_info_id = b.material_base_info_id diff --git a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml index 8df558332..3b55cabce 100644 --- a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml +++ b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml @@ -416,6 +416,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" group by a.MATERIAL_INVENTORY_ID order by sort_order, a.update_time desc + + + + + a.del_flag = 1 + and a.level = 1 + and b.del_flag = 1 + and b.status = 5 + + and b.top_organization_id = #{topOrganizationId} + + + and b.warehouse_name like concat('%', #{warehouseName}, '%') + + + and b.in_order_number like concat('%', #{inOrderNumber}, '%') + + + and b.shipper_code like concat('%', #{shipperCode}, '%') + + + and b.shipper_name like concat('%', #{shipperName}, '%') + + + and a.material_code like concat('%', #{materialCode}, '%') + + + and a.material_name like concat('%', #{materialName}, '%') + + + and date_format(b.warehouse_date,'%Y-%m-%d') >= #{warehouseDateStart} + + + and date_format(b.warehouse_date,'%Y-%m-%d') <= #{warehouseDateEnd} + + + + + from execution_in_material_detail a + join execution_stock_in_order b on a.in_order_number = b.in_order_number and b.del_flag = 1 + left join NGWL_TEST_WMS.material_base_info c on a.material_base_info_id = c.material_base_info_id + + + + + + + + diff --git a/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml b/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml new file mode 100644 index 000000000..77fccdc12 --- /dev/null +++ b/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml @@ -0,0 +1,124 @@ + + + + + + + a.del_flag = 1 + and a.level = 1 + and b.del_flag = 1 + and b.status = 9 + + and b.top_organization_id = #{topOrganizationId} + + + and b.warehouse_name like concat('%', #{warehouseName}, '%') + + + and b.customer_name like concat('%', #{customerName}, '%') + + + and b.shipper_name like concat('%', #{shipperName}, '%') + + + and b.line_code like concat('%', #{lineCode}, '%') + + + and b.line_name like concat('%', #{lineName}, '%') + + + and date_format(b.create_time,'%Y-%m-%d') >= #{createTimeStart} + + + and date_format(b.create_time,'%Y-%m-%d') <= #{createTimeEnd} + + + + + from execution_out_material_detail a + join execution_stock_out_order b on a.out_order_number = b.out_order_number and b.del_flag = 1 + + + + + + + + + + + + + + diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/inboundRank/InboundRankApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/inboundRank/InboundRankApplicationService.java new file mode 100644 index 000000000..c5a18f4ff --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/inboundRank/InboundRankApplicationService.java @@ -0,0 +1,76 @@ +package com.mhd.wms.application.service.inboundRank; + +import com.mhd.common.core.exception.ServiceException; +import com.mhd.common.core.utils.StringUtils; +import com.mhd.common.security.utils.SecurityUtils; +import com.mhd.system.api.domain.cache.AssociationWarehouseCacheDO; +import com.mhd.system.api.domain.cache.SystemServiceCacheUtil; +import com.mhd.system.api.model.LoginUser; +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; +import com.mhd.wms.domain.inboundRank.service.InboundRankDomainService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 入库排名统计应用服务 + * + *

负责登录用户组织隔离注入、日期区间校验,查询逻辑在 InboundRankDomainService → InboundRankMapper.xml

+ * + * @author rcx + * @date 2026-08-31 + */ +@Service +@Slf4j +public class InboundRankApplicationService { + + @Autowired + private InboundRankDomainService inboundRankDomainService; + + /** + * 分页查询商品入库排名列表(仓单+货品汇总,按件数降序) + */ + public List queryMaterialRankList(InboundRankDO inboundRankDO) { + fillLoginContext(inboundRankDO); + return inboundRankDomainService.queryMaterialRankList(inboundRankDO); + } + + /** + * 商品入库排名合计(当前筛选条件全量,不分页) + */ + public InboundRankTotalPO queryMaterialRankTotal(InboundRankDO inboundRankDO) { + return inboundRankDomainService.queryMaterialRankTotal(inboundRankDO); + } + + /** + * 导出商品入库排名(不分页全量) + */ + public List exportMaterialRank(InboundRankDO inboundRankDO) { + fillLoginContext(inboundRankDO); + return inboundRankDomainService.queryMaterialRankList(inboundRankDO); + } + + /** + * 注入登录用户组织隔离与关联仓库,并校验入仓日期区间必填 + */ + private void fillLoginContext(InboundRankDO inboundRankDO) { + if (StringUtils.isEmpty(inboundRankDO.getWarehouseDateStart()) || StringUtils.isEmpty(inboundRankDO.getWarehouseDateEnd())) { + throw new ServiceException("请选择统计日期区间"); + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser != null && loginUser.getUserPo() != null) { + Long topOrganizationId = loginUser.getUserPo().getTopOrganizationId(); + if (topOrganizationId != null && topOrganizationId != 1) { + inboundRankDO.setTopOrganizationId(topOrganizationId); + } + AssociationWarehouseCacheDO associationWarehouseCacheDO = SystemServiceCacheUtil.getAssociationWarehouseCache(loginUser.getUserid()); + if (associationWarehouseCacheDO != null && associationWarehouseCacheDO.getWarehouseId() != null) { + inboundRankDO.setWarehouseId(associationWarehouseCacheDO.getWarehouseId()); + } + } + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/outboundRank/OutboundRankApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/outboundRank/OutboundRankApplicationService.java new file mode 100644 index 000000000..3f6d4d267 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/outboundRank/OutboundRankApplicationService.java @@ -0,0 +1,100 @@ +package com.mhd.wms.application.service.outboundRank; + +import com.mhd.common.core.utils.StringUtils; +import com.mhd.common.core.exception.ServiceException; +import com.mhd.common.security.utils.SecurityUtils; +import com.mhd.system.api.domain.cache.AssociationWarehouseCacheDO; +import com.mhd.system.api.domain.cache.SystemServiceCacheUtil; +import com.mhd.system.api.model.LoginUser; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; +import com.mhd.wms.domain.outboundRank.service.OutboundRankDomainService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 出库排名统计应用服务 + * + *

负责登录用户组织隔离注入、日期区间校验,统计逻辑在 OutboundRankDomainService → OutboundRankMapper.xml

+ * + * @author rcx + * @date 2026-08-31 + */ +@Service +@Slf4j +public class OutboundRankApplicationService { + + @Autowired + private OutboundRankDomainService outboundRankDomainService; + + /** + * 分页查询商品出库排名列表 + */ + public List queryMaterialRankList(OutboundRankDO outboundRankDO) { + fillLoginContext(outboundRankDO); + return outboundRankDomainService.queryMaterialRankList(outboundRankDO); + } + + /** + * 分页查询线路出库排名列表 + */ + public List queryLineRankList(OutboundRankDO outboundRankDO) { + fillLoginContext(outboundRankDO); + return outboundRankDomainService.queryLineRankList(outboundRankDO); + } + + /** + * 商品出库排名合计(当前筛选条件全量,不分页) + */ + public OutboundRankTotalPO queryMaterialRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankDomainService.queryMaterialRankTotal(outboundRankDO); + } + + /** + * 线路出库排名合计(当前筛选条件全量,不分页) + */ + public OutboundRankTotalPO queryLineRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankDomainService.queryLineRankTotal(outboundRankDO); + } + + /** + * 导出商品出库排名(不分页全量) + */ + public List exportMaterialRankList(OutboundRankDO outboundRankDO) { + fillLoginContext(outboundRankDO); + return outboundRankDomainService.queryMaterialRankList(outboundRankDO); + } + + /** + * 导出线路出库排名(不分页全量) + */ + public List exportLineRankList(OutboundRankDO outboundRankDO) { + fillLoginContext(outboundRankDO); + return outboundRankDomainService.queryLineRankList(outboundRankDO); + } + + /** + * 注入登录用户组织隔离与关联仓库,并校验日期区间必填 + */ + private void fillLoginContext(OutboundRankDO outboundRankDO) { + if (StringUtils.isEmpty(outboundRankDO.getCreateTimeStart()) || StringUtils.isEmpty(outboundRankDO.getCreateTimeEnd())) { + throw new ServiceException("请选择统计日期区间"); + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser != null && loginUser.getUserPo() != null) { + Long topOrganizationId = loginUser.getUserPo().getTopOrganizationId(); + if (topOrganizationId != null && topOrganizationId != 1) { + outboundRankDO.setTopOrganizationId(topOrganizationId); + } + AssociationWarehouseCacheDO associationWarehouseCacheDO = SystemServiceCacheUtil.getAssociationWarehouseCache(loginUser.getUserid()); + if (associationWarehouseCacheDO != null && associationWarehouseCacheDO.getWarehouseId() != null) { + outboundRankDO.setWarehouseId(associationWarehouseCacheDO.getWarehouseId()); + } + } + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/facade/IInboundRankService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/facade/IInboundRankService.java new file mode 100644 index 000000000..9559e2d5f --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/facade/IInboundRankService.java @@ -0,0 +1,26 @@ +package com.mhd.wms.domain.inboundRank.repository.facade; + +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; + +import java.util.List; + +/** + * 入库排名统计Service接口 + * + * @author rcx + * @date 2026-08-31 + */ +public interface IInboundRankService { + + /** + * 商品入库排名列表(仓单+货品汇总,按件数降序) + */ + List queryMaterialRankList(InboundRankDO inboundRankDO); + + /** + * 商品入库排名合计(不分页) + */ + InboundRankTotalPO queryMaterialRankTotal(InboundRankDO inboundRankDO); +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/mapper/InboundRankMapper.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/mapper/InboundRankMapper.java new file mode 100644 index 000000000..5c95d09fa --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/mapper/InboundRankMapper.java @@ -0,0 +1,26 @@ +package com.mhd.wms.domain.inboundRank.repository.mapper; + +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; + +import java.util.List; + +/** + * 入库排名统计Mapper接口 + * + * @author rcx + * @date 2026-08-31 + */ +public interface InboundRankMapper { + + /** + * 商品入库排名列表(仓单+货品汇总,按件数降序) + */ + List queryMaterialRankList(InboundRankDO inboundRankDO); + + /** + * 商品入库排名合计(不分页,当前筛选条件全量合计) + */ + InboundRankTotalPO queryMaterialRankTotal(InboundRankDO inboundRankDO); +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/persistence/InboundRankImpl.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/persistence/InboundRankImpl.java new file mode 100644 index 000000000..519b7cc13 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/persistence/InboundRankImpl.java @@ -0,0 +1,34 @@ +package com.mhd.wms.domain.inboundRank.repository.persistence; + +import com.mhd.wms.domain.inboundRank.repository.facade.IInboundRankService; +import com.mhd.wms.domain.inboundRank.repository.mapper.InboundRankMapper; +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 入库排名统计Service业务层处理 + * + * @author rcx + * @date 2026-08-31 + */ +@Service +public class InboundRankImpl implements IInboundRankService { + + @Autowired + private InboundRankMapper inboundRankMapper; + + @Override + public List queryMaterialRankList(InboundRankDO inboundRankDO) { + return inboundRankMapper.queryMaterialRankList(inboundRankDO); + } + + @Override + public InboundRankTotalPO queryMaterialRankTotal(InboundRankDO inboundRankDO) { + return inboundRankMapper.queryMaterialRankTotal(inboundRankDO); + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundMaterialRankPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundMaterialRankPO.java new file mode 100644 index 000000000..03567787a --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundMaterialRankPO.java @@ -0,0 +1,79 @@ +package com.mhd.wms.domain.inboundRank.repository.po; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.mhd.common.core.annotation.Excel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 入库排名统计PO(一行 = 某仓单内某货品的合计,按件数降序排名) + * + *

字段口径:入仓日期=入仓单入仓日期;仓单编号=入库单号;客户编号/存货人=入仓单货主; + * 件数/毛重/体积=仓单内该货品明细合计;毛重(吨)=明细总毛重KG/1000; + * 投保总额=货品档案保额(material_base_info.insure_amount,组内取MAX防多批次明细行重复累加)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +@ApiModel(value = "入库排名统计对象", description = "入库排名统计响应对象") +public class InboundMaterialRankPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("入仓日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @Excel(name = "入仓日期", width = 30, dateFormat = "yyyy-MM-dd") + private Date warehouseDate; + + @ApiModelProperty("仓单编号(入库单号)") + @Excel(name = "仓单编号") + private String inOrderNumber; + + @ApiModelProperty("客户编号") + @Excel(name = "客户编号") + private String shipperCode; + + @ApiModelProperty("存货人(客户名称)") + @Excel(name = "存货人") + private String shipperName; + + @ApiModelProperty("商品货号") + @Excel(name = "商品货号") + private String materialCode; + + @ApiModelProperty("货名/商品名称") + @Excel(name = "货名") + private String materialName; + + @ApiModelProperty("规格型号") + @Excel(name = "规格型号") + private String specificationModel; + + @ApiModelProperty("单位") + @Excel(name = "单位") + private String unitName; + + @ApiModelProperty("件数(仓单内该货品合计,排名依据)") + @Excel(name = "件数") + private BigDecimal pieceCount; + + @ApiModelProperty("毛重(吨)(仓单内该货品合计,KG/1000)") + @Excel(name = "毛重(吨)") + private BigDecimal grossWeightTon; + + @ApiModelProperty("投保总额(货品档案保额)") + @Excel(name = "投保总额") + private BigDecimal insureAmount; + + @ApiModelProperty("体积(CBM)(仓单内该货品合计)") + @Excel(name = "体积(CBM)") + private BigDecimal totalVolume; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundRankTotalPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundRankTotalPO.java new file mode 100644 index 000000000..551bf25d4 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/po/InboundRankTotalPO.java @@ -0,0 +1,39 @@ +package com.mhd.wms.domain.inboundRank.repository.po; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 入库排名统计合计PO + * + *

当前筛选条件下不分页的全量合计,随分页列表一起返回 + * (totalPieceCount/totalGrossWeightTon/totalInsureAmount/totalVolume/totalOrderCount)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +@ApiModel(value = "入库排名统计合计对象", description = "入库排名统计合计响应对象") +public class InboundRankTotalPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("件数合计") + private BigDecimal totalPieceCount; + + @ApiModelProperty("毛重(吨)合计") + private BigDecimal totalGrossWeightTon; + + @ApiModelProperty("投保总额合计(各仓单+货品行保额之和)") + private BigDecimal totalInsureAmount; + + @ApiModelProperty("体积(CBM)合计") + private BigDecimal totalVolume; + + @ApiModelProperty("仓单数合计") + private Long totalOrderCount; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/todo/InboundRankDO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/todo/InboundRankDO.java new file mode 100644 index 000000000..1cc725242 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/repository/todo/InboundRankDO.java @@ -0,0 +1,50 @@ +package com.mhd.wms.domain.inboundRank.repository.todo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 入库排名统计查询DO + * + *

topOrganizationId / warehouseId 由服务端按登录用户注入,前端不传

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +public class InboundRankDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("入仓日期-开始 yyyy-MM-dd(必填)") + private String warehouseDateStart; + + @ApiModelProperty("入仓日期-结束 yyyy-MM-dd(必填)") + private String warehouseDateEnd; + + @ApiModelProperty("仓单编号/入库单号(模糊)") + private String inOrderNumber; + + @ApiModelProperty("客户编号(模糊)") + private String shipperCode; + + @ApiModelProperty("存货人/客户名称(模糊)") + private String shipperName; + + @ApiModelProperty("商品货号(模糊)") + private String materialCode; + + @ApiModelProperty("货名/商品名称(模糊)") + private String materialName; + + @ApiModelProperty("仓库名称(模糊)") + private String warehouseName; + + @ApiModelProperty("一级组织表ID(登录用户隔离,非1时注入)") + private Long topOrganizationId; + + @ApiModelProperty("仓库id(按用户关联仓库注入)") + private Long warehouseId; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/service/InboundRankDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/service/InboundRankDomainService.java new file mode 100644 index 000000000..53e948b61 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/inboundRank/service/InboundRankDomainService.java @@ -0,0 +1,39 @@ +package com.mhd.wms.domain.inboundRank.service; + +import com.mhd.wms.domain.inboundRank.repository.facade.IInboundRankService; +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 入库排名统计领域服务 + * + * @author rcx + * @date 2026-08-31 + */ +@Service +@Slf4j +public class InboundRankDomainService { + + @Autowired + private IInboundRankService inboundRankService; + + /** + * 商品入库排名列表(仓单+货品汇总,按件数降序) + */ + public List queryMaterialRankList(InboundRankDO inboundRankDO) { + return inboundRankService.queryMaterialRankList(inboundRankDO); + } + + /** + * 商品入库排名合计(不分页) + */ + public InboundRankTotalPO queryMaterialRankTotal(InboundRankDO inboundRankDO) { + return inboundRankService.queryMaterialRankTotal(inboundRankDO); + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/facade/IOutboundRankService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/facade/IOutboundRankService.java new file mode 100644 index 000000000..81981ab4b --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/facade/IOutboundRankService.java @@ -0,0 +1,37 @@ +package com.mhd.wms.domain.outboundRank.repository.facade; + +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; + +import java.util.List; + +/** + * 出库排名统计Service接口 + * + * @author rcx + * @date 2026-08-31 + */ +public interface IOutboundRankService { + + /** + * 商品出库排名列表 + */ + List queryMaterialRankList(OutboundRankDO outboundRankDO); + + /** + * 线路出库排名列表 + */ + List queryLineRankList(OutboundRankDO outboundRankDO); + + /** + * 商品出库排名合计(不分页) + */ + OutboundRankTotalPO queryMaterialRankTotal(OutboundRankDO outboundRankDO); + + /** + * 线路出库排名合计(不分页) + */ + OutboundRankTotalPO queryLineRankTotal(OutboundRankDO outboundRankDO); +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/mapper/OutboundRankMapper.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/mapper/OutboundRankMapper.java new file mode 100644 index 000000000..40f17acc2 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/mapper/OutboundRankMapper.java @@ -0,0 +1,37 @@ +package com.mhd.wms.domain.outboundRank.repository.mapper; + +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; + +import java.util.List; + +/** + * 出库排名统计Mapper接口 + * + * @author rcx + * @date 2026-08-31 + */ +public interface OutboundRankMapper { + + /** + * 商品出库排名列表(按销售量倒序) + */ + List queryMaterialRankList(OutboundRankDO outboundRankDO); + + /** + * 线路出库排名列表(按销售量倒序) + */ + List queryLineRankList(OutboundRankDO outboundRankDO); + + /** + * 商品出库排名合计(不分页,当前筛选条件全量合计) + */ + OutboundRankTotalPO queryMaterialRankTotal(OutboundRankDO outboundRankDO); + + /** + * 线路出库排名合计(不分页,当前筛选条件全量合计) + */ + OutboundRankTotalPO queryLineRankTotal(OutboundRankDO outboundRankDO); +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/persistence/OutboundRankImpl.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/persistence/OutboundRankImpl.java new file mode 100644 index 000000000..0acf88572 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/persistence/OutboundRankImpl.java @@ -0,0 +1,45 @@ +package com.mhd.wms.domain.outboundRank.repository.persistence; + +import com.mhd.wms.domain.outboundRank.repository.facade.IOutboundRankService; +import com.mhd.wms.domain.outboundRank.repository.mapper.OutboundRankMapper; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 出库排名统计Service业务层处理 + * + * @author rcx + * @date 2026-08-31 + */ +@Service +public class OutboundRankImpl implements IOutboundRankService { + + @Autowired + private OutboundRankMapper outboundRankMapper; + + @Override + public List queryMaterialRankList(OutboundRankDO outboundRankDO) { + return outboundRankMapper.queryMaterialRankList(outboundRankDO); + } + + @Override + public List queryLineRankList(OutboundRankDO outboundRankDO) { + return outboundRankMapper.queryLineRankList(outboundRankDO); + } + + @Override + public OutboundRankTotalPO queryMaterialRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankMapper.queryMaterialRankTotal(outboundRankDO); + } + + @Override + public OutboundRankTotalPO queryLineRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankMapper.queryLineRankTotal(outboundRankDO); + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundLineRankPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundLineRankPO.java new file mode 100644 index 000000000..7f5c05c3c --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundLineRankPO.java @@ -0,0 +1,45 @@ +package com.mhd.wms.domain.outboundRank.repository.po; + +import com.mhd.common.core.annotation.Excel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 线路出库排名PO + * + *

按日期区间对出库单聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价); + * 仅统计带线路的出库单(line_code 为 2026-08-20 新增冗余字段,历史单为空)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +@ApiModel(value = "线路出库排名对象", description = "线路出库排名响应对象") +public class OutboundLineRankPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("线路编码") + @Excel(name = "线路编码") + private String lineCode; + + @ApiModelProperty("线路名称") + @Excel(name = "线路名称") + private String lineName; + + @ApiModelProperty("销售量(出库数量合计)") + @Excel(name = "销售量") + private BigDecimal salesQuantity; + + @ApiModelProperty("交易数(出库单据数)") + @Excel(name = "交易数") + private Long tradeCount; + + @ApiModelProperty("金额") + @Excel(name = "金额") + private BigDecimal amount; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundMaterialRankPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundMaterialRankPO.java new file mode 100644 index 000000000..9725d50ab --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundMaterialRankPO.java @@ -0,0 +1,52 @@ +package com.mhd.wms.domain.outboundRank.repository.po; + +import com.mhd.common.core.annotation.Excel; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 商品出库排名PO + * + *

按日期区间对出库明细聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +@ApiModel(value = "商品出库排名对象", description = "商品出库排名响应对象") +public class OutboundMaterialRankPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("商品货号") + @Excel(name = "商品货号") + private String materialCode; + + @ApiModelProperty("商品名称") + @Excel(name = "商品名称") + private String materialName; + + @ApiModelProperty("规格") + @Excel(name = "规格") + private String specificationModel; + + @ApiModelProperty("单位") + @Excel(name = "单位") + private String unitName; + + @ApiModelProperty("销售量(出库数量合计)") + @Excel(name = "销售量") + private BigDecimal salesQuantity; + + @ApiModelProperty("交易数(出库单据数)") + @Excel(name = "交易数") + private Long tradeCount; + + @ApiModelProperty("金额") + @Excel(name = "金额") + private BigDecimal amount; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundRankTotalPO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundRankTotalPO.java new file mode 100644 index 000000000..ea0e9ace9 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/po/OutboundRankTotalPO.java @@ -0,0 +1,32 @@ +package com.mhd.wms.domain.outboundRank.repository.po; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 出库排名合计PO + * + *

当前筛选条件下不分页的全量合计,随分页列表一起返回(totalSalesQuantity/totalTradeCount/totalAmount)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +@ApiModel(value = "出库排名合计对象", description = "出库排名合计响应对象") +public class OutboundRankTotalPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("销售量合计") + private BigDecimal totalSalesQuantity; + + @ApiModelProperty("交易数合计(去重出库单数)") + private Long totalTradeCount; + + @ApiModelProperty("金额合计") + private BigDecimal totalAmount; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/todo/OutboundRankDO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/todo/OutboundRankDO.java new file mode 100644 index 000000000..c38c77230 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/repository/todo/OutboundRankDO.java @@ -0,0 +1,53 @@ +package com.mhd.wms.domain.outboundRank.repository.todo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 出库排名统计查询DO + * + *

topOrganizationId / warehouseId 由服务端按登录用户注入,前端不传

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +public class OutboundRankDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("出库日期-开始 yyyy-MM-dd(必填)") + private String createTimeStart; + + @ApiModelProperty("出库日期-结束 yyyy-MM-dd(必填)") + private String createTimeEnd; + + @ApiModelProperty("商品货号(模糊)") + private String materialCode; + + @ApiModelProperty("商品名称(模糊)") + private String materialName; + + @ApiModelProperty("线路编码(模糊)") + private String lineCode; + + @ApiModelProperty("线路名称(模糊)") + private String lineName; + + @ApiModelProperty("仓库名称(模糊)") + private String warehouseName; + + @ApiModelProperty("客户名称(模糊)") + private String customerName; + + @ApiModelProperty("货主名称(模糊)") + private String shipperName; + + @ApiModelProperty("一级组织表ID(登录用户隔离,非1时注入)") + private Long topOrganizationId; + + @ApiModelProperty("仓库id(按用户关联仓库注入)") + private Long warehouseId; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/service/OutboundRankDomainService.java b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/service/OutboundRankDomainService.java new file mode 100644 index 000000000..1d2fe84c2 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/outboundRank/service/OutboundRankDomainService.java @@ -0,0 +1,54 @@ +package com.mhd.wms.domain.outboundRank.service; + +import com.mhd.wms.domain.outboundRank.repository.facade.IOutboundRankService; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 出库排名统计领域服务 + * + * @author rcx + * @date 2026-08-31 + */ +@Service +@Slf4j +public class OutboundRankDomainService { + + @Autowired + private IOutboundRankService outboundRankService; + + /** + * 商品出库排名列表 + */ + public List queryMaterialRankList(OutboundRankDO outboundRankDO) { + return outboundRankService.queryMaterialRankList(outboundRankDO); + } + + /** + * 线路出库排名列表 + */ + public List queryLineRankList(OutboundRankDO outboundRankDO) { + return outboundRankService.queryLineRankList(outboundRankDO); + } + + /** + * 商品出库排名合计(不分页) + */ + public OutboundRankTotalPO queryMaterialRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankService.queryMaterialRankTotal(outboundRankDO); + } + + /** + * 线路出库排名合计(不分页) + */ + public OutboundRankTotalPO queryLineRankTotal(OutboundRankDO outboundRankDO) { + return outboundRankService.queryLineRankTotal(outboundRankDO); + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/inboundRank/InboundRankAssembler.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/inboundRank/InboundRankAssembler.java new file mode 100644 index 000000000..f884e0715 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/inboundRank/InboundRankAssembler.java @@ -0,0 +1,36 @@ +package com.mhd.wms.interfaces.assember.inboundRank; + +import cn.hutool.core.util.ObjectUtil; +import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException; +import com.mhd.common.core.exception.digitalLogisticsException.UserError; +import com.mhd.common.core.utils.IgnoreNullUtil; +import com.mhd.common.core.utils.bean.BeanUtils; +import com.mhd.common.security.utils.SecurityUtils; +import com.mhd.system.api.model.LoginUser; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; +import com.mhd.wms.interfaces.dto.inboundRank.InboundRankDTO; +import org.springframework.stereotype.Component; + +/** + * 入库排名统计Assembler + * + * @author rcx + * @date 2026-08-31 + */ +@Component +public class InboundRankAssembler { + + /** + * 转换实体 + */ + public InboundRankDO toDO(InboundRankDTO inboundRankDTO) { + InboundRankDO inboundRankDO = new InboundRankDO(); + // 拷贝 + BeanUtils.copyProperties(inboundRankDTO, inboundRankDO, IgnoreNullUtil.getNullPropertyNames(inboundRankDTO)); + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (ObjectUtil.isNull(loginUser)) { + throw new DigitalLogisticsException(UserError.TIMEOUT); + } + return inboundRankDO; + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/outboundRank/OutboundRankAssembler.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/outboundRank/OutboundRankAssembler.java new file mode 100644 index 000000000..8388a919f --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/assember/outboundRank/OutboundRankAssembler.java @@ -0,0 +1,36 @@ +package com.mhd.wms.interfaces.assember.outboundRank; + +import cn.hutool.core.util.ObjectUtil; +import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException; +import com.mhd.common.core.exception.digitalLogisticsException.UserError; +import com.mhd.common.core.utils.IgnoreNullUtil; +import com.mhd.common.core.utils.bean.BeanUtils; +import com.mhd.common.security.utils.SecurityUtils; +import com.mhd.system.api.model.LoginUser; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; +import com.mhd.wms.interfaces.dto.outboundRank.OutboundRankDTO; +import org.springframework.stereotype.Component; + +/** + * 出库排名统计Assembler + * + * @author rcx + * @date 2026-08-31 + */ +@Component +public class OutboundRankAssembler { + + /** + * 转换实体 + */ + public OutboundRankDO toDO(OutboundRankDTO outboundRankDTO) { + OutboundRankDO outboundRankDO = new OutboundRankDO(); + // 拷贝 + BeanUtils.copyProperties(outboundRankDTO, outboundRankDO, IgnoreNullUtil.getNullPropertyNames(outboundRankDTO)); + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (ObjectUtil.isNull(loginUser)) { + throw new DigitalLogisticsException(UserError.TIMEOUT); + } + return outboundRankDO; + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/inboundRank/InboundRankDTO.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/inboundRank/InboundRankDTO.java new file mode 100644 index 000000000..e7c812636 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/inboundRank/InboundRankDTO.java @@ -0,0 +1,45 @@ +package com.mhd.wms.interfaces.dto.inboundRank; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 入库排名统计入参DTO + * + *

按入仓日期区间筛选商品入库量统计排名,统计口径:入仓日期 + 仅已入库状态(5) + 明细仅取 level=1 主行; + * 一行 = 某仓单内某货品的合计,按件数降序排名

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +public class InboundRankDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("入仓日期-开始 yyyy-MM-dd(必填,按入仓单入仓日期筛选)") + private String warehouseDateStart; + + @ApiModelProperty("入仓日期-结束 yyyy-MM-dd(必填,按入仓单入仓日期筛选)") + private String warehouseDateEnd; + + @ApiModelProperty("仓单编号/入库单号(模糊)") + private String inOrderNumber; + + @ApiModelProperty("客户编号(模糊)") + private String shipperCode; + + @ApiModelProperty("存货人/客户名称(模糊)") + private String shipperName; + + @ApiModelProperty("商品货号(模糊)") + private String materialCode; + + @ApiModelProperty("货名/商品名称(模糊)") + private String materialName; + + @ApiModelProperty("仓库名称(模糊)") + private String warehouseName; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/outboundRank/OutboundRankDTO.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/outboundRank/OutboundRankDTO.java new file mode 100644 index 000000000..eac2c07b5 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/dto/outboundRank/OutboundRankDTO.java @@ -0,0 +1,43 @@ +package com.mhd.wms.interfaces.dto.outboundRank; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 出库排名统计入参DTO + * + *

按日期区间统计商品/线路出库量排名,统计口径:出库单创建时间 + 已出库及之后状态(9,12,13)

+ * + * @author rcx + * @date 2026-08-31 + */ +@Data +public class OutboundRankDTO { + + @ApiModelProperty("出库日期-开始 yyyy-MM-dd(必填,按出库单创建时间统计)") + private String createTimeStart; + + @ApiModelProperty("出库日期-结束 yyyy-MM-dd(必填,按出库单创建时间统计)") + private String createTimeEnd; + + @ApiModelProperty("商品货号(模糊)") + private String materialCode; + + @ApiModelProperty("商品名称(模糊)") + private String materialName; + + @ApiModelProperty("线路编码(模糊)") + private String lineCode; + + @ApiModelProperty("线路名称(模糊)") + private String lineName; + + @ApiModelProperty("仓库名称(模糊)") + private String warehouseName; + + @ApiModelProperty("客户名称(模糊)") + private String customerName; + + @ApiModelProperty("货主名称(模糊)") + private String shipperName; +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/inboundRank/InboundRankApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/inboundRank/InboundRankApi.java new file mode 100644 index 000000000..b979e988b --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/inboundRank/InboundRankApi.java @@ -0,0 +1,105 @@ +package com.mhd.wms.interfaces.facadeApi.inboundRank; + +import com.github.pagehelper.PageInfo; +import com.mhd.common.core.constant.HttpStatus; +import com.mhd.common.core.utils.PageUtils; +import com.mhd.common.core.utils.poi.ExcelUtil; +import com.mhd.wms.application.service.inboundRank.InboundRankApplicationService; +import com.mhd.wms.domain.inboundRank.repository.po.InboundMaterialRankPO; +import com.mhd.wms.domain.inboundRank.repository.po.InboundRankTotalPO; +import com.mhd.wms.domain.inboundRank.repository.todo.InboundRankDO; +import com.mhd.wms.interfaces.assember.inboundRank.InboundRankAssembler; +import com.mhd.wms.interfaces.dto.inboundRank.InboundRankDTO; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; + +/** + * 入库排名统计Api + * + *

按入仓日期区间筛选商品入库量统计排名。统计口径:入仓日期 + 仅已入库状态(5), + * 明细仅取 level=1 主行;一行 = 某仓单内某货品的合计,按件数降序排名。 + * 字段:入仓日期/仓单编号/客户编号/存货人/货名/件数/毛重(吨)/投保总额/体积(CBM)。 + * 与出库排名(/outboundRankApi)配对,前端用 tab 页区分

+ * + * @author rcx + * @date 2026-08-31 + */ +@RestController +@RequestMapping("/inboundRankApi") +public class InboundRankApi { + + @Resource + private InboundRankAssembler inboundRankAssembler; + + @Autowired + private InboundRankApplicationService inboundRankApplicationService; + + /** + * 分页查询商品入库排名列表(含合计行) + */ + @ApiOperation("分页查询商品入库排名列表") + @GetMapping("/queryMaterialRankList") + public HashMap queryMaterialRankList(InboundRankDTO inboundRankDTO) { + //转换实体 + InboundRankDO inboundRankDO = inboundRankAssembler.toDO(inboundRankDTO); + startPage(); + List list = inboundRankApplicationService.queryMaterialRankList(inboundRankDO); + InboundRankTotalPO total = inboundRankApplicationService.queryMaterialRankTotal(inboundRankDO); + return getDataTableWithTotal(list, total); + } + + /** + * 导出商品入库排名Excel + */ + @ApiOperation("导出商品入库排名Excel") + @GetMapping("/exportMaterialRank") + public void exportMaterialRank(HttpServletResponse response, InboundRankDTO inboundRankDTO) throws Exception { + //转换实体(不分页,导出全量) + InboundRankDO inboundRankDO = inboundRankAssembler.toDO(inboundRankDTO); + List list = inboundRankApplicationService.exportMaterialRank(inboundRankDO); + + String fileName = URLEncoder.encode("入库排名统计", "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelUtil util = new ExcelUtil<>(InboundMaterialRankPO.class); + util.exportExcel(response, list, "入库排名统计"); + } + + /** + * 分页结果 + 合计行:data/total/code/msg 之外追加 + * totalPieceCount/totalGrossWeightTon/totalInsureAmount/totalVolume/totalOrderCount + */ + private HashMap getDataTableWithTotal(List list, InboundRankTotalPO total) { + HashMap result = new HashMap<>(); + result.put("data", list); + result.put("total", new PageInfo<>(list).getTotal()); + result.put("code", HttpStatus.SUCCESS); + result.put("msg", "查询成功"); + if (total != null) { + result.put("totalPieceCount", total.getTotalPieceCount()); + result.put("totalGrossWeightTon", total.getTotalGrossWeightTon()); + result.put("totalInsureAmount", total.getTotalInsureAmount()); + result.put("totalVolume", total.getTotalVolume()); + result.put("totalOrderCount", total.getTotalOrderCount()); + } + return result; + } + + /** + * 设置请求分页数据 + */ + protected void startPage() { + PageUtils.startPage(); + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/outboundRank/OutboundRankApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/outboundRank/OutboundRankApi.java new file mode 100644 index 000000000..439dbba78 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/outboundRank/OutboundRankApi.java @@ -0,0 +1,134 @@ +package com.mhd.wms.interfaces.facadeApi.outboundRank; + +import com.github.pagehelper.PageInfo; +import com.mhd.common.core.constant.HttpStatus; +import com.mhd.common.core.utils.PageUtils; +import com.mhd.common.core.utils.poi.ExcelUtil; +import com.mhd.wms.application.service.outboundRank.OutboundRankApplicationService; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundLineRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundMaterialRankPO; +import com.mhd.wms.domain.outboundRank.repository.po.OutboundRankTotalPO; +import com.mhd.wms.domain.outboundRank.repository.todo.OutboundRankDO; +import com.mhd.wms.interfaces.assember.outboundRank.OutboundRankAssembler; +import com.mhd.wms.interfaces.dto.outboundRank.OutboundRankDTO; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; + +/** + * 出库排名统计Api + * + *

按日期区间筛选,统计商品/线路出库量排名。统计口径:出库单创建时间 + 已出库及之后状态(9,12,13), + * 明细仅取 level=1 主行;销售量=SUM(出库数量)、交易数=COUNT(DISTINCT 出库单号)、金额=SUM(明细总价)

+ * + * @author rcx + * @date 2026-08-31 + */ +@RestController +@RequestMapping("/outboundRankApi") +public class OutboundRankApi { + + @Resource + private OutboundRankAssembler outboundRankAssembler; + + @Autowired + private OutboundRankApplicationService outboundRankApplicationService; + + /** + * 分页查询商品出库排名列表(含合计行) + */ + @ApiOperation("分页查询商品出库排名列表") + @GetMapping("/queryMaterialRankList") + public HashMap queryMaterialRankList(OutboundRankDTO outboundRankDTO) { + //转换实体 + OutboundRankDO outboundRankDO = outboundRankAssembler.toDO(outboundRankDTO); + startPage(); + List list = outboundRankApplicationService.queryMaterialRankList(outboundRankDO); + OutboundRankTotalPO total = outboundRankApplicationService.queryMaterialRankTotal(outboundRankDO); + return getDataTableWithTotal(list, total); + } + + /** + * 分页查询线路出库排名列表(含合计行) + */ + @ApiOperation("分页查询线路出库排名列表") + @GetMapping("/queryLineRankList") + public HashMap queryLineRankList(OutboundRankDTO outboundRankDTO) { + //转换实体 + OutboundRankDO outboundRankDO = outboundRankAssembler.toDO(outboundRankDTO); + startPage(); + List list = outboundRankApplicationService.queryLineRankList(outboundRankDO); + OutboundRankTotalPO total = outboundRankApplicationService.queryLineRankTotal(outboundRankDO); + return getDataTableWithTotal(list, total); + } + + /** + * 导出商品出库排名Excel + */ + @ApiOperation("导出商品出库排名Excel") + @GetMapping("/exportMaterialRank") + public void exportMaterialRank(HttpServletResponse response, OutboundRankDTO outboundRankDTO) throws Exception { + //转换实体(不分页,导出全量) + OutboundRankDO outboundRankDO = outboundRankAssembler.toDO(outboundRankDTO); + List list = outboundRankApplicationService.exportMaterialRankList(outboundRankDO); + + String fileName = URLEncoder.encode("商品出库排名", "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelUtil util = new ExcelUtil<>(OutboundMaterialRankPO.class); + util.exportExcel(response, list, "商品出库排名"); + } + + /** + * 导出线路出库排名Excel + */ + @ApiOperation("导出线路出库排名Excel") + @GetMapping("/exportLineRank") + public void exportLineRank(HttpServletResponse response, OutboundRankDTO outboundRankDTO) throws Exception { + //转换实体(不分页,导出全量) + OutboundRankDO outboundRankDO = outboundRankAssembler.toDO(outboundRankDTO); + List list = outboundRankApplicationService.exportLineRankList(outboundRankDO); + + String fileName = URLEncoder.encode("线路出库排名", "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelUtil util = new ExcelUtil<>(OutboundLineRankPO.class); + util.exportExcel(response, list, "线路出库排名"); + } + + /** + * 分页结果 + 合计行:data/total/code/msg 之外追加 totalSalesQuantity/totalTradeCount/totalAmount + */ + private HashMap getDataTableWithTotal(List list, OutboundRankTotalPO total) { + HashMap result = new HashMap<>(); + result.put("data", list); + result.put("total", new PageInfo<>(list).getTotal()); + result.put("code", HttpStatus.SUCCESS); + result.put("msg", "查询成功"); + if (total != null) { + result.put("totalSalesQuantity", total.getTotalSalesQuantity()); + result.put("totalTradeCount", total.getTotalTradeCount()); + result.put("totalAmount", total.getTotalAmount()); + } + return result; + } + + /** + * 设置请求分页数据 + */ + protected void startPage() { + PageUtils.startPage(); + } +} diff --git a/mhd_wms/src/main/resources/mapper/inboundRank/InboundRankMapper.xml b/mhd_wms/src/main/resources/mapper/inboundRank/InboundRankMapper.xml new file mode 100644 index 000000000..fc1e6f238 --- /dev/null +++ b/mhd_wms/src/main/resources/mapper/inboundRank/InboundRankMapper.xml @@ -0,0 +1,104 @@ + + + + + + + a.del_flag = 1 + and a.level = 1 + and b.status = 5 + + and b.top_organization_id = #{topOrganizationId} + + + and b.warehouse_id = #{warehouseId} + + + and b.warehouse_name like concat('%', #{warehouseName}, '%') + + + and b.in_order_number like concat('%', #{inOrderNumber}, '%') + + + and b.shipper_code like concat('%', #{shipperCode}, '%') + + + and b.shipper_name like concat('%', #{shipperName}, '%') + + + and a.material_code like concat('%', #{materialCode}, '%') + + + and a.material_name like concat('%', #{materialName}, '%') + + + and date_format(b.warehouse_date,'%Y-%m-%d') >= #{warehouseDateStart} + + + and date_format(b.warehouse_date,'%Y-%m-%d') <= #{warehouseDateEnd} + + + + + from in_material_detail a + join stock_in_order b on a.in_order_number = b.in_order_number and b.del_flag = 1 + left join material_base_info c on a.material_base_info_id = c.material_base_info_id + + + + + + + + diff --git a/mhd_wms/src/main/resources/mapper/outboundRank/OutboundRankMapper.xml b/mhd_wms/src/main/resources/mapper/outboundRank/OutboundRankMapper.xml new file mode 100644 index 000000000..54aa7458b --- /dev/null +++ b/mhd_wms/src/main/resources/mapper/outboundRank/OutboundRankMapper.xml @@ -0,0 +1,126 @@ + + + + + + + a.del_flag = 1 + and a.level = 1 + and b.status in (9, 12, 13) + + and b.top_organization_id = #{topOrganizationId} + + + and b.warehouse_id = #{warehouseId} + + + and b.warehouse_name like concat('%', #{warehouseName}, '%') + + + and b.customer_name like concat('%', #{customerName}, '%') + + + and b.shipper_name like concat('%', #{shipperName}, '%') + + + and b.line_code like concat('%', #{lineCode}, '%') + + + and b.line_name like concat('%', #{lineName}, '%') + + + and date_format(b.create_time,'%Y-%m-%d') >= #{createTimeStart} + + + and date_format(b.create_time,'%Y-%m-%d') <= #{createTimeEnd} + + + + + from out_material_detail a + join stock_out_order b on a.out_order_number = b.out_order_number and b.del_flag = 1 + + + + + + + + + + + + + + From 1300a4e1f8e93cbc2e6c01104091d0d6a24a7547 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Tue, 1 Sep 2026 09:10:59 +0800 Subject: [PATCH 15/19] =?UTF-8?q?fix(oms)=EF=BC=9A=E5=87=BA=E5=BA=93?= =?UTF-8?q?=E6=8E=92=E5=90=8DOMS=E7=AB=AF=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=B7=A8=E5=BA=93=E6=9F=A5WMS=E5=87=BA?= =?UTF-8?q?=E5=BA=93=E8=A1=A8=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E6=81=92=E4=B8=BA=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OMS库出库单状态从不流转(预约/执行单全部status=1,无"已出库"数据),原SQL按执行出库单status=9过滤导致查不出任何数据;改为跨库取WMS出库表NGWL_TEST_WMS.stock_out_order/out_material_detail(与OMS库存查询跨库join material_base_info同一模式),口径与WMS端/outboundRankApi完全一致:已出库及之后状态(9,12,13)+明细level=1主行+按出库单创建时间筛选,两边页面数字一致;接口路径/参数/返回结构均不变,其余文件仅更新注释中的数据源说明 --- .../po/ExecutionOutboundLineRankPO.java | 2 +- .../po/ExecutionOutboundMaterialRankPO.java | 2 +- .../todo/ExecutionOutboundRankDTO.java | 7 ++-- ...ecutionOutboundRankApplicationService.java | 2 +- .../ExecutionOutboundRankApi.java | 7 +++- .../ExecutionOutboundRankMapper.xml | 37 +++++++++---------- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundLineRankPO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundLineRankPO.java index 607e789c6..974944f90 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundLineRankPO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundLineRankPO.java @@ -11,7 +11,7 @@ import java.math.BigDecimal; /** * 线路出库排名PO * - *

按日期区间对执行出库单聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价); + *

按日期区间对出库单聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价); * 仅统计带线路的出库单(line_code 为 2026-08-20 新增冗余字段,历史单为空)

* * @author rcx diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundMaterialRankPO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundMaterialRankPO.java index ce1cb867e..c32046d9f 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundMaterialRankPO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/po/ExecutionOutboundMaterialRankPO.java @@ -11,7 +11,7 @@ import java.math.BigDecimal; /** * 商品出库排名PO * - *

按日期区间对执行出库明细聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价)

+ *

按日期区间对出库明细聚合:销售量=SUM(出库数量),交易数=COUNT(DISTINCT 出库单号),金额=SUM(明细总价)

* * @author rcx * @date 2026-08-31 diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/todo/ExecutionOutboundRankDTO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/todo/ExecutionOutboundRankDTO.java index 9b055286f..0637173b7 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/todo/ExecutionOutboundRankDTO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/repository/todo/ExecutionOutboundRankDTO.java @@ -8,7 +8,8 @@ import java.io.Serializable; /** * 出库排名统计入参DTO * - *

按日期区间统计商品/线路出库量排名,数据源为 OMS 执行出库链路(execution_out_material_detail / execution_stock_out_order)

+ *

按日期区间统计商品/线路出库量排名。OMS 库出库单状态不流转(无"已出库"数据), + * 数据源跨库取 WMS 库出库表(NGWL_TEST_WMS.stock_out_order / out_material_detail),与 WMS 端 /outboundRankApi 同口径

* * @author rcx * @date 2026-08-31 @@ -16,10 +17,10 @@ import java.io.Serializable; @Data public class ExecutionOutboundRankDTO { - @ApiModelProperty("出库日期-开始 yyyy-MM-dd(必填,按执行出库单创建时间统计)") + @ApiModelProperty("出库日期-开始 yyyy-MM-dd(必填,按出库单创建时间统计)") private String createTimeStart; - @ApiModelProperty("出库日期-结束 yyyy-MM-dd(必填,按执行出库单创建时间统计)") + @ApiModelProperty("出库日期-结束 yyyy-MM-dd(必填,按出库单创建时间统计)") private String createTimeEnd; @ApiModelProperty("商品货号(模糊)") diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/service/ExecutionOutboundRankApplicationService.java b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/service/ExecutionOutboundRankApplicationService.java index ef14d18bc..158f2b8c2 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/service/ExecutionOutboundRankApplicationService.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/outboundRank/service/ExecutionOutboundRankApplicationService.java @@ -17,7 +17,7 @@ import java.util.List; /** * 出库排名统计应用服务 * - *

负责登录用户组织隔离注入、日期区间校验,统计逻辑在 ExecutionOutboundRankDomainService → ExecutionOutboundRankMapper.xml

+ *

负责登录用户组织隔离注入、日期区间校验,统计逻辑在 ExecutionOutboundRankDomainService → ExecutionOutboundRankMapper.xml(跨库取 WMS 出库数据)

* * @author rcx * @date 2026-08-31 diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/outboundRank/ExecutionOutboundRankApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/outboundRank/ExecutionOutboundRankApi.java index 07cb22725..f1222e413 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/outboundRank/ExecutionOutboundRankApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/outboundRank/ExecutionOutboundRankApi.java @@ -27,8 +27,11 @@ import java.util.List; /** * 出库排名统计Api * - *

按日期区间筛选,统计商品/线路出库量排名。统计口径:执行出库单创建时间 + 已出库状态(9), - * 明细仅取 level=1 主行;销售量=SUM(出库数量)、交易数=COUNT(DISTINCT 出库单号)、金额=SUM(明细总价)

+ *

按日期区间筛选,统计商品/线路出库量排名。OMS 库出库单状态不流转(无"已出库"数据), + * 数据源跨库取 WMS 库出库表(NGWL_TEST_WMS.stock_out_order / out_material_detail), + * 口径与 WMS 端 /outboundRankApi 完全一致,两边数字一致; + * 统计条件:出库单创建时间 + 已出库及之后状态(9,12,13),明细仅取 level=1 主行; + * 销售量=SUM(出库数量)、交易数=COUNT(DISTINCT 出库单号)、金额=SUM(明细总价)

* * @author rcx * @date 2026-08-31 diff --git a/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml b/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml index 77fccdc12..f35689979 100644 --- a/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml +++ b/mhd_oms/src/main/resources/mapper/outboundRank/ExecutionOutboundRankMapper.xml @@ -5,16 +5,19 @@ a.del_flag = 1 and a.level = 1 - and b.del_flag = 1 - and b.status = 9 + and b.status in (9, 12, 13) and b.top_organization_id = #{topOrganizationId} @@ -33,6 +36,12 @@ and b.line_name like concat('%', #{lineName}, '%') + + and a.material_code like concat('%', #{materialCode}, '%') + + + and a.material_name like concat('%', #{materialName}, '%') + and date_format(b.create_time,'%Y-%m-%d') >= #{createTimeStart} @@ -42,8 +51,8 @@ - from execution_out_material_detail a - join execution_stock_out_order b on a.out_order_number = b.out_order_number and b.del_flag = 1 + from NGWL_TEST_WMS.out_material_detail a + join NGWL_TEST_WMS.stock_out_order b on a.out_order_number = b.out_order_number and b.del_flag = 1 @@ -60,12 +69,6 @@ - - and a.material_code like concat('%', #{materialCode}, '%') - - - and a.material_name like concat('%', #{materialName}, '%') - GROUP BY a.material_code order by salesQuantity desc @@ -99,12 +102,6 @@ - - and a.material_code like concat('%', #{materialCode}, '%') - - - and a.material_name like concat('%', #{materialName}, '%') - From a588264849a049eb1620f264ae957d4bb6e0c0bd Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Tue, 1 Sep 2026 09:24:20 +0800 Subject: [PATCH 16/19] =?UTF-8?q?fix(wms,oms)=EF=BC=9A=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E6=8E=92=E5=90=8D=E7=BB=9F=E8=AE=A1=E6=9F=A5=E8=AF=A2=E6=81=92?= =?UTF-8?q?=E4=B8=BA=E7=A9=BA=E4=BF=AE=E5=A4=8D=EF=BC=9AOMS=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=85=A5=E5=BA=93=E5=8D=95=E7=8A=B6=E6=80=81=E4=B8=8D?= =?UTF-8?q?=E5=9B=9E=E4=BC=A0(=E5=85=A8=E5=81=9Cstatus=3D1)=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=8C=89=E5=B7=B2=E5=85=A5=E5=BA=93=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E4=B8=BA=E4=BB=85=E6=8E=92=E9=99=A4=E5=B7=B2?= =?UTF-8?q?=E5=8F=96=E6=B6=88(6)/=E5=B7=B2=E5=85=B3=E9=97=AD(7)(=E4=B8=8E?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E6=98=8E=E7=BB=86=E6=9F=A5=E8=AF=A2=E9=A1=B5?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E4=B8=80=E8=87=B4)=EF=BC=9B=E5=85=A5?= =?UTF-8?q?=E4=BB=93=E6=97=A5=E6=9C=9F=E4=B8=BA=E7=A9=BA=E7=9A=84=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E5=8D=95=E7=AD=9B=E9=80=89=E4=B8=8E=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E5=9D=87=E5=85=9C=E5=BA=95=E5=BB=BA=E6=A1=A3=E6=97=B6=E9=97=B4?= =?UTF-8?q?create=5Ftime(=E4=B8=8E=E5=87=BA=E5=BA=93=E6=98=8E=E7=BB=86?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=8F=A3=E5=BE=84=E4=B8=80=E8=87=B4)?= =?UTF-8?q?=EF=BC=9BWMS=E4=BB=8D=E4=BF=9D=E6=8C=81=E4=BB=85=E5=B7=B2?= =?UTF-8?q?=E5=85=A5=E5=BA=93(5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facade/inboundRank/ExecutionInboundRankApi.java | 5 +++-- .../inboundRank/ExecutionInboundRankMapper.xml | 13 +++++++------ .../facadeApi/inboundRank/InboundRankApi.java | 2 +- .../mapper/inboundRank/InboundRankMapper.xml | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java index 9ff29be10..b7cfd6391 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java @@ -26,8 +26,9 @@ import java.util.List; /** * 入库排名统计Api * - *

按入仓日期区间筛选商品入库量统计排名。统计口径:入仓日期 + 仅已入库状态(5), - * 明细仅取 level=1 主行;一行 = 某仓单内某货品的合计,按件数降序排名。 + *

按入仓日期区间筛选商品入库量统计排名。统计口径:入仓日期(为空的历史单按建档时间兜底); + * OMS 执行入库单状态不回传(长期停留已创建),不按"已入库"过滤,仅排除已取消(6)/已关闭(7), + * 与入库明细查询页面口径一致;明细仅取 level=1 主行;一行 = 某仓单内某货品的合计,按件数降序排名。 * 字段:入仓日期/仓单编号/客户编号/存货人/货名/件数/毛重(吨)/投保总额/体积(CBM)。 * 与出库排名(/executionOutboundRankApi)配对,前端用 tab 页区分

* diff --git a/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml b/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml index 46c335671..0401cc603 100644 --- a/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml +++ b/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml @@ -8,15 +8,16 @@ 入库排名统计公共查询条件(OMS 执行入库链路) 数据源:执行入库明细 execution_in_material_detail(a,仅 level=1 主行防重复) join 执行入仓单 execution_stock_in_order(b) left join NGWL_TEST_WMS.material_base_info(c,跨库取货品档案保额作投保总额) - 口径:b.status = 5已入库(OMS 入库状态枚举 1~7,排除已取消(6)/已关闭(7)及未完成单据); - 日期筛选按执行入仓单入仓日期 b.warehouse_date(与列表"入仓日期"列同源); + 口径:b.status not in (6已取消,7已关闭)(OMS 执行入库单状态不回传,实际数据长期停留 status=1已创建, + 无法按"已入库5"过滤,与入库明细查询页面口径一致不按状态筛选,仅排除已取消/已关闭); + 日期筛选按执行入仓单入仓日期 b.warehouse_date(与列表"入仓日期"列同源,为空的历史单按建档时间兜底); 一行 = 某仓单(b.in_order_number)内某货品(a.material_code)的合计,按件数降序排名 --> a.del_flag = 1 and a.level = 1 and b.del_flag = 1 - and b.status = 5 + and b.status not in (6, 7) and b.top_organization_id = #{topOrganizationId} @@ -39,10 +40,10 @@ and a.material_name like concat('%', #{materialName}, '%') - and date_format(b.warehouse_date,'%Y-%m-%d') >= #{warehouseDateStart} + and date_format(IFNULL(b.warehouse_date, b.create_time),'%Y-%m-%d') >= #{warehouseDateStart} - and date_format(b.warehouse_date,'%Y-%m-%d') <= #{warehouseDateEnd} + and date_format(IFNULL(b.warehouse_date, b.create_time),'%Y-%m-%d') <= #{warehouseDateEnd} @@ -56,7 +57,7 @@ select - MAX(b.warehouse_date) AS warehouseDate, + IFNULL(MAX(b.warehouse_date), MAX(b.create_time)) AS warehouseDate, b.in_order_number AS inOrderNumber, MAX(b.shipper_code) AS shipperCode, MAX(b.shipper_name) AS shipperName, From bb3ca932d25147177f6abcf46a44de5c96f64fae Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Tue, 1 Sep 2026 09:53:51 +0800 Subject: [PATCH 17/19] =?UTF-8?q?feat(oms):=E5=87=BA=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=AD=98=E5=9C=A8=E6=80=A7=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=94=B6=E9=9B=86=E5=90=8E=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=8F=90=E7=A4=BA(=E7=89=A9=E6=96=99+=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E7=9A=84=E7=BC=96=E5=8F=B7=E4=B8=80?= =?UTF-8?q?=E6=9D=A1=E6=B6=88=E6=81=AF=E5=B8=A6=E5=85=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整2d1e4b59引入的校验流程(入库parseStockInExcel/出库parseExcelFile统一): 1. 第一步:物料编号存在性校验——逐编号查库,全部不存在的收集到missingMaterials,不立即报错 2. 第二步:客户编号存在性校验——逐编号查库,全部不存在的收集到missingCustomers,不立即报错 3. 第三步:两步全部完成后统一抛出提示,避免修完物料问题重导一次才暴露客户问题: 只缺物料:"物料[A、B]不存在,请先维护物料信息,维护完成后重新导入" 只缺客户:"客户[C]不存在,请先维护客户信息,维护完成后重新导入" 都缺:"物料[A、B]不存在,请先维护物料信息;客户[C、D]不存在,请先维护客户信息,维护完成后重新导入" 4. 存在性全部通过后才继续多货主/多客户编号/客户与货主一致性等校验及建单; 入库"多个客户编号"校验相应移到存在性之后,多个编号中缺哪个报哪个 5. 入库客户查询结果存入customerCache复用,一致性校验不再二次查库 漏填编号(空白行)仍即时报错,不属于存在性问题,行为不变 --- .../ReservationStockInOrderImpl.java | 95 ++++++++++++------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java index 2503b839f..20176fe04 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationStockInOrder/repository/persistence/ReservationStockInOrderImpl.java @@ -259,7 +259,7 @@ public class ReservationStockInOrderImpl extends ServiceImpl> shipperIds = new ArrayList<>(); Set processedShipperIds = new HashSet<>(); - // ====== 物料存在性校验(落库前):查询结果为空说明库中没有该物料,收集全部缺失料号后一次性提示维护 ====== + // ====== 第一步:物料编号存在性校验(落库前,先收集全部缺失编号,不立即报错) ====== Map materialCache = new LinkedHashMap<>(); List missingMaterials = new ArrayList<>(); for (ProductDetail productDetail : productDetailList) { @@ -277,21 +277,10 @@ public class ReservationStockInOrderImpl extends ServiceImpl shipper = new HashMap<>(); - shipper.put("shipperId", materialBaseInfoPO.getShipperId()); - shipper.put("shipperName", materialBaseInfoPO.getShipperName()); - shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); - shipperIds.add(shipper); - } - } - // ====== 客户存在性校验(落库前):客户编号在库中查询结果为空说明客户不存在,提示维护后再导入 ====== + // ====== 第二步:客户编号存在性校验(落库前,先收集全部缺失编号,不立即报错) ====== Map customerCache = new HashMap<>(); + List missingCustomers = new ArrayList<>(); for (CustomerDeliveryDetail customerDeliveryDetail : customerDeliveryDetailList) { String customerCode = customerDeliveryDetail.getCustomerCode() == null ? "" : customerDeliveryDetail.getCustomerCode().trim(); if (customerCode.isEmpty()) { @@ -302,9 +291,33 @@ public class ReservationStockInOrderImpl extends ServiceImpl problems = new ArrayList<>(); + if (!missingMaterials.isEmpty()) { + problems.add("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息"); + } + if (!missingCustomers.isEmpty()) { + problems.add("客户[" + String.join("、", missingCustomers) + "]不存在,请先维护客户信息"); + } + throw new Exception(String.join(";", problems) + ",维护完成后重新导入"); + } + + // ====== 后续处理(编号均已存在):货主归属校验、建单落库 ====== + for (MaterialBaseInfoPO materialBaseInfoPO : materialCache.values()) { + if (materialBaseInfoPO.getShipperId() != null && processedShipperIds.add(materialBaseInfoPO.getShipperId())) { + Map shipper = new HashMap<>(); + shipper.put("shipperId", materialBaseInfoPO.getShipperId()); + shipper.put("shipperName", materialBaseInfoPO.getShipperName()); + shipper.put("shipperCode", materialBaseInfoPO.getShipperCode()); + shipperIds.add(shipper); } - customerCache.put(customerCode, customer); } // 货品明细不为空时,校验货主信息 @@ -506,8 +519,8 @@ public class ReservationStockInOrderImpl extends ServiceImpl materialCache = new LinkedHashMap<>(); List missingMaterials = new ArrayList<>(); for (StockInProductDetail productDetail : productDetailList) { @@ -522,9 +535,35 @@ public class ReservationStockInOrderImpl extends ServiceImpl customerCodes = new LinkedHashSet<>(); + for (StockInProductDetail productDetail : productDetailList) { + if (productDetail.getCustomerCode() != null && !productDetail.getCustomerCode().trim().isEmpty()) { + customerCodes.add(productDetail.getCustomerCode().trim()); + } } + Map customerCache = new HashMap<>(); + List missingCustomers = new ArrayList<>(); + for (String customerCode : customerCodes) { + UserPo customer = stockOutOrderMapper.queryCustomerByCode(customerCode, loginUser.getUserPo().getOrganizationId()); + if (customer == null || customer.getUserId() == null) { + missingCustomers.add(customerCode); + } else { + customerCache.put(customerCode, customer); + } + } + // ====== 第三步:物料/客户存在性校验全部完成后,统一提示两类编号各自有哪些不存在 ====== + if (!missingMaterials.isEmpty() || !missingCustomers.isEmpty()) { + List problems = new ArrayList<>(); + if (!missingMaterials.isEmpty()) { + problems.add("物料[" + String.join("、", missingMaterials) + "]不存在,请先维护物料信息"); + } + if (!missingCustomers.isEmpty()) { + problems.add("客户[" + String.join("、", missingCustomers) + "]不存在,请先维护客户信息"); + } + throw new ServiceException(String.join(";", problems) + ",维护完成后重新导入"); + } + // ====== 后续处理(编号均已存在):名称自动匹配、货主归属与一致性校验、建单 ====== // 明细回填库中匹配到的物料名称(自动匹配,模板中的名称列可不填/以库中为准;库中名称为空时保留模板值) for (StockInProductDetail productDetail : productDetailList) { String materialName = materialCache.get(productDetail.getProductCode()).getMaterialName(); @@ -532,7 +571,7 @@ public class ReservationStockInOrderImpl extends ServiceImpl> shipperIds = new ArrayList<>(); Set processedShipperIds = new HashSet<>(); for (MaterialBaseInfoPO materialBaseInfoPO : materialCache.values()) { @@ -554,24 +593,14 @@ public class ReservationStockInOrderImpl extends ServiceImpl customerCodes = new LinkedHashSet<>(); - for (StockInProductDetail productDetail : productDetailList) { - if (productDetail.getCustomerCode() != null && !productDetail.getCustomerCode().trim().isEmpty()) { - customerCodes.add(productDetail.getCustomerCode().trim()); - } - } if (customerCodes.size() > 1) { throw new ServiceException("导入明细中存在多个客户编号(" + String.join("、", customerCodes) + "),一个入库单只能属于一个客户"); } if (!customerCodes.isEmpty()) { String customerCode = customerCodes.iterator().next(); - // 查询结果为空说明库中没有该客户,提示维护后再导入 - customer = stockOutOrderMapper.queryCustomerByCode(customerCode, loginUser.getUserPo().getOrganizationId()); - if (customer == null || customer.getUserId() == null) { - throw new ServiceException("客户[" + customerCode + "]不存在,请先维护客户信息后再导入"); - } + customer = customerCache.get(customerCode); // 客户编号与物料归属货主一致性校验(不一致拦截,提示两者编号;货主编码未维护时提示货主名称) if (!shipperIds.isEmpty() && !customer.getUserId().equals(shipperIds.get(0).get("shipperId"))) { Object materialShipperCode = shipperIds.get(0).get("shipperCode"); From a4f27ab6432237284ad4754b9668971be5e64645 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Tue, 1 Sep 2026 16:29:41 +0800 Subject: [PATCH 18/19] =?UTF-8?q?feat(oms,wms):=E5=BA=93=E5=AD=98=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=AF=BC=E5=87=BA=E6=96=B0=E5=A2=9EisExport=E6=A0=87?= =?UTF-8?q?=E8=AF=86=E4=B8=8E=E8=9E=8D=E5=90=88=E6=8E=92=E5=BA=8F=20/list?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=E5=8F=AF=E9=80=89=E5=8F=82=E6=95=B0=20isE?= =?UTF-8?q?xport=20=E6=A0=87=E8=AF=86=E5=AF=BC=E5=87=BA=E8=AF=B7=E6=B1=82(?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=98=BE=E7=A4=BA=E5=95=86=E5=93=81=E9=A5=AE?= =?UTF-8?q?=E7=94=A8=E6=9C=89=E6=95=88=E6=9C=9F/=E7=89=A9=E6=96=99?= =?UTF-8?q?=E5=88=86=E7=B1=BB/=E8=BF=9B=E8=B4=A7=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E7=94=B1=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E8=BF=94=E5=9B=9E)=EF=BC=9BisExport=3Dtrue=20=E6=97=B6?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=9F=A5=E8=AF=A2=E7=BB=B4=E5=BA=A6(OMS=206?= =?UTF-8?q?=E6=9D=A1/WMS=208=E6=9D=A1)=E7=BB=9F=E4=B8=80=E5=90=AF=E7=94=A8?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E8=9E=8D=E5=90=88=E6=8E=92=E5=BA=8F=EF=BC=9A?= =?UTF-8?q?=E5=88=B0=E6=9C=9F=E6=97=A5=E5=80=92=E5=BA=8F=E7=A9=BA=E5=80=BC?= =?UTF-8?q?=E6=9C=80=E5=90=8E(=E4=BF=9D=E7=95=99=E5=8E=9F=20orderByExpiryD?= =?UTF-8?q?ate=20=E7=AC=AC=E4=B8=80=E4=BC=98=E5=85=88=E7=BA=A7,=E8=9E=8D?= =?UTF-8?q?=E5=90=88=E4=B8=8D=E5=8F=96=E4=BB=A3)=E2=86=92=E5=90=8C?= =?UTF-8?q?=E5=88=B0=E6=9C=9F=E6=97=A5=E5=86=85=E6=8C=89=E7=89=A9=E6=96=99?= =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=8D=87=E5=BA=8F=E2=86=920=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E9=9D=A0=E5=90=8E=E2=86=92=E6=9B=B4=E6=96=B0=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=80=92=E5=BA=8F=E2=86=92MAX(=E5=BA=93=E5=AD=98ID)?= =?UTF-8?q?=E5=85=9C=E5=BA=95=E4=BF=9D=E8=AF=81=E5=88=86=E9=A1=B5=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=EF=BC=9B=E5=90=84=E8=AF=AD=E5=8F=A5=E6=8C=82=E8=BD=BD?= =?UTF-8?q?=E5=85=AC=E5=85=B1=E7=89=87=E6=AE=B5=20exportOrderByMaterialCod?= =?UTF-8?q?e=EF=BC=8C=E4=B8=8D=E4=BC=A0=20isExport=20=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E6=8E=92=E5=BA=8F=EF=BC=9BOMS=20Controller?= =?UTF-8?q?=20=E5=9C=A8=20isExport=3Dtrue=20=E6=97=B6=E8=B7=B3=E8=BF=87=20?= =?UTF-8?q?Java=20=E6=8C=89=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E9=81=BF=E5=85=8D=E8=A6=86=E7=9B=96=20SQL=20=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo/ReservationMaterialInventoryDO.java | 3 + .../ReservationMaterialInventoryApi.java | 12 ++- .../MaterialInventoryMapper.xml | 54 +++++++++-- .../repository/todo/MaterialInventoryDO.java | 3 + .../MaterialInventoryApi.java | 11 ++- .../MaterialInventoryMapper.xml | 89 ++++++++++++++++--- 6 files changed, 143 insertions(+), 29 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java index 61fd270a5..13958e2f2 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/reservationMaterialInventory/repository/todo/ReservationMaterialInventoryDO.java @@ -270,4 +270,7 @@ public class ReservationMaterialInventoryDO extends MaterialBaseDO { @ApiModelProperty("导出排除库存为0标识:true-排除库存数量汇总为0的数据(所有查询维度 queryRule 均生效);不传-不过滤") private Boolean excludeZeroInventoryQuantity; + + @ApiModelProperty("导出标识:导出与查询共用 /list,true-本次为导出请求,启用导出融合排序(到期日倒序优先→同到期日按物料编码升序→0库存靠后→更新时间倒序);不传-保持原有排序") + private Boolean isExport; } \ No newline at end of file diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java index bae725a5c..0fcb31b52 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/reservationMaterialInventory/ReservationMaterialInventoryApi.java @@ -47,7 +47,7 @@ public class ReservationMaterialInventoryApi extends BaseController { /** * 分页查询物料库存列表 */ - @ApiOperation(value = "查询物料库存列表", notes = "导出复用本接口,任意查询维度(queryRule)下:前端\"库存为0是否导出\"选\"是\"时传 excludeZeroInventoryQuantity=true 排除库存为0的数据,不传或 false 导出全部(含库存为0);orderByExpiryDate=true 时按到期日分组排序") + @ApiOperation(value = "查询物料库存列表", notes = "导出复用本接口:传 isExport=true 标识导出请求(导出显示:商品饮用有效期expiryDate/物料分类materialClassifyName/进货日期purchaseDate,所有查询维度均返回);excludeZeroInventoryQuantity=true 排除库存为0的数据(所有维度生效);orderByExpiryDate=true 按到期日分组排序") @GetMapping("/list") public TableDataInfo list(ReservationMaterialInventoryDTO materialInventoryDTO, @RequestParam(value = "customerName", required = false) String customerName, @@ -56,7 +56,9 @@ public class ReservationMaterialInventoryApi extends BaseController { @RequestParam(value = "purchaseDateStart", required = false) String purchaseDateStart, @RequestParam(value = "purchaseDateEnd", required = false) String purchaseDateEnd, @RequestParam(value = "excludeZeroInventoryQuantity", required = false) Boolean excludeZeroInventoryQuantity, - @RequestParam(value = "orderByExpiryDate", required = false) Boolean orderByExpiryDate) + @RequestParam(value = "orderByExpiryDate", required = false) Boolean orderByExpiryDate, + // 导出标识:导出与查询共用本接口,isExport=true 表示本次请求为导出(导出专属排序等逻辑以此开关) + @RequestParam(value = "isExport", required = false) Boolean isExport) { // 出库分配前端传的是客户名称 customerName,这里通过名称查询货主ID,使用精确匹配(不模糊匹配) String shipperName = null; // 保存查询到的货主名称 @@ -95,12 +97,14 @@ public class ReservationMaterialInventoryApi extends BaseController { // 导出场景:前端"库存为0是否导出"选"是"时传 excludeZeroInventoryQuantity=true, // SQL 用 HAVING 排除汇总库存为0的数据(与 WMS 同名参数语义一致) materialInventoryDO.setExcludeZeroInventoryQuantity(excludeZeroInventoryQuantity); + // 导出标识:isExport=true 启用导出融合排序(到期日倒序优先 → 同到期日按物料编码升序,所有维度生效) + materialInventoryDO.setIsExport(isExport); startPage(); // 使用带批次属性的查询方法,批次属性值从库存表中获取 List list = materialInventoryApplicationService.queryListWithBatchAttributes(materialInventoryDO); // 按更新时间倒序排序;update_time 为空的数据放最后 - // (导出 orderByExpiryDate=true 时跳过该重排,避免覆盖 SQL 的到期日分组排序) - if (!Boolean.TRUE.equals(orderByExpiryDate)) { + // (导出 orderByExpiryDate=true 或 isExport=true 时跳过该重排,避免覆盖 SQL 的导出排序) + if (!Boolean.TRUE.equals(orderByExpiryDate) && !Boolean.TRUE.equals(isExport)) { list.sort((o1, o2) -> { Date t1 = o1.getUpdateTime(); Date t2 = o2.getUpdateTime(); diff --git a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml index 200226335..e9cd17b19 100644 --- a/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml +++ b/mhd_oms/src/main/resources/mapper/reservationMaterialInventory/MaterialInventoryMapper.xml @@ -305,6 +305,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + order by CASE WHEN MAX(a.expiry_date) IS NULL THEN 1 ELSE 0 END ASC, MAX(a.expiry_date) DESC, + MAX(b.material_code) ASC, + CASE WHEN IFNULL(SUM(a.inventory_quantity), 0) = 0 THEN 1 ELSE 0 END ASC, + MAX(a.update_time) DESC, + MAX(a.material_inventory_id) DESC + + @@ -401,16 +427,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" a.organization_id, a.organization_name, a.top_organization_id, b.material_code, b.material_name - + - - order by CASE WHEN a.expiry_date IS NULL THEN 1 ELSE 0 END ASC, a.expiry_date DESC, - sort_order, MAX(a.update_time) DESC + + - order by sort_order, MAX(a.update_time) desc + + + + order by CASE WHEN a.expiry_date IS NULL THEN 1 ELSE 0 END ASC, a.expiry_date DESC, + sort_order, MAX(a.update_time) DESC + + + order by sort_order, MAX(a.update_time) desc + + @@ -427,6 +462,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY a.SHIPPER_ID + + + diff --git a/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/todo/MaterialInventoryDO.java b/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/todo/MaterialInventoryDO.java index c9b3af8aa..312a5f074 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/todo/MaterialInventoryDO.java +++ b/mhd_wms/src/main/java/com/mhd/wms/domain/materialInventory/repository/todo/MaterialInventoryDO.java @@ -285,4 +285,7 @@ public class MaterialInventoryDO extends MaterialBaseDO { @ApiModelProperty("库存分配匹配:Batch Ref NO's token 列表(默认不启用,仅显式赋值时参与匹配)") private List batchRefNoList; + + @ApiModelProperty("导出标识:导出与查询共用 /list,true-本次为导出请求,启用导出融合排序(到期日倒序优先→同到期日按物料编码升序→0库存靠后→更新时间倒序);不传-保持原有排序") + private Boolean isExport; } \ No newline at end of file diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialInventory/MaterialInventoryApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialInventory/MaterialInventoryApi.java index 93b329ea3..519994447 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialInventory/MaterialInventoryApi.java +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialInventory/MaterialInventoryApi.java @@ -53,7 +53,7 @@ public class MaterialInventoryApi extends BaseController { /** * 分页查询物料库存列表 */ - @ApiOperation(value = "查询物料库存列表", notes = "库存查询页不传 excludeZeroInventoryQuantity 可查出数量为0;出库分配页传 excludeZeroInventoryQuantity=true 排除库存数量为0") + @ApiOperation(value = "查询物料库存列表", notes = "库存查询页不传 excludeZeroInventoryQuantity 可查出数量为0;出库分配页传 excludeZeroInventoryQuantity=true 排除库存数量为0;导出复用本接口:传 isExport=true 标识导出请求(导出显示:商品饮用有效期expiryDate/物料分类materialClassifyName/进货日期purchaseDate,所有查询维度均返回)") @GetMapping("/list") public TableDataInfo list(MaterialInventoryDTO materialInventoryDTO, @RequestParam(value = "customerName", required = false) String customerName, @@ -62,7 +62,9 @@ public class MaterialInventoryApi extends BaseController { @RequestParam(value = "expiryDateEnd", required = false) String expiryDateEnd, @RequestParam(value = "purchaseDateStart", required = false) String purchaseDateStart, @RequestParam(value = "purchaseDateEnd", required = false) String purchaseDateEnd, - @RequestParam(value = "orderByExpiryDate", required = false) Boolean orderByExpiryDate) + @RequestParam(value = "orderByExpiryDate", required = false) Boolean orderByExpiryDate, + // 导出标识:导出与查询共用本接口,isExport=true 表示本次请求为导出(导出专属排序等逻辑以此开关) + @RequestParam(value = "isExport", required = false) Boolean isExport) { if (Boolean.TRUE.equals(excludeZeroInventoryQuantity)) { materialInventoryDTO.setExcludeZeroInventoryQuantity(true); @@ -100,6 +102,8 @@ public class MaterialInventoryApi extends BaseController { materialInventoryDO.setPurchaseDateEnd(purchaseDateEnd); // 导出场景:queryRule=wlExpiry + orderByExpiryDate=true 时 SQL 按到期日倒序分组(与 OMS 库存查询导出一致) materialInventoryDO.setOrderByExpiryDate(orderByExpiryDate); + // 导出标识:isExport=true 启用导出融合排序(到期日倒序优先 → 同到期日按物料编码升序,所有维度生效) + materialInventoryDO.setIsExport(isExport); Date updateTime = materialInventoryDTO.getUpdateTime(); if (updateTime != null) { LocalDateTime localDateTime = updateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); @@ -172,7 +176,8 @@ public class MaterialInventoryApi extends BaseController { // materialDetailId 是新接口专用参数(出库单明细ID),库存 DTO 的同名字段语义是「上架单明细ID」, // 泄入原查询会生成错误的 a.material_detail_id 条件导致查空,进原查询前显式清除 materialInventoryDTO.setMaterialDetailId(null); - return list(materialInventoryDTO, customerName, excludeZeroInventoryQuantity, expiryDateStart, expiryDateEnd, null, null, orderByExpiryDate); + // assignableList 非导出场景,isExport 传 null + return list(materialInventoryDTO, customerName, excludeZeroInventoryQuantity, expiryDateStart, expiryDateEnd, null, null, orderByExpiryDate, null); } @ApiOperation("查询物料库存列表不分页;queryRule=hz 时按货主+物料分组(同一物料在不同货主各占一行)") diff --git a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml index 3b55cabce..783b033bb 100644 --- a/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml +++ b/mhd_wms/src/main/resources/mapper/materialInventory/MaterialInventoryMapper.xml @@ -403,6 +403,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + order by CASE WHEN MAX(a.expiry_date) IS NULL THEN 1 ELSE 0 END ASC, MAX(a.expiry_date) DESC, + MAX(b.material_code) ASC, + CASE WHEN IFNULL(SUM(a.inventory_quantity), 0) = 0 THEN 1 ELSE 0 END ASC, + MAX(a.update_time) DESC, + MAX(a.material_inventory_id) DESC + + @@ -586,16 +621,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY a.MATERIAL_BASE_INFO_ID, a.EXPIRY_DATE, a.shipper_id, a.organization_id, a.top_organization_id - + - - order by CASE WHEN a.expiry_date IS NULL THEN 1 ELSE 0 END ASC, a.expiry_date DESC, - sort_order, MAX(a.update_time) DESC + + - order by sort_order, MAX(a.update_time) desc + + + + order by CASE WHEN a.expiry_date IS NULL THEN 1 ELSE 0 END ASC, a.expiry_date DESC, + sort_order, MAX(a.update_time) DESC + + + order by sort_order, MAX(a.update_time) desc + + @@ -631,7 +675,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY a.SHIPPER_ID, a.MATERIAL_BASE_INFO_ID, loc.warehouse_id, loc.warehouse_code, loc.warehouse_name, loc.storage_section_id, loc.storage_code, loc.storage_name, - loc.storage_location_id, loc.storage_location_code, loc.storage_location_name order by sort_order, a.update_time desc + loc.storage_location_id, loc.storage_location_code, loc.storage_location_name + + + + + order by sort_order, a.update_time desc + @@ -691,7 +741,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" b.material_code, a.warehouse_id, a.organization_id, a.top_organization_id - ORDER BY sort_order, MAX(a.update_time) DESC + + + + + ORDER BY sort_order, MAX(a.update_time) DESC + From da3c58a366952bef7a8ad9654d1c41c8a4cdaf79 Mon Sep 17 00:00:00 2001 From: rcx <3084692334@qq.com> Date: Tue, 1 Sep 2026 21:36:33 +0800 Subject: [PATCH 19/19] =?UTF-8?q?fix(wms,oms)=EF=BC=9A=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E6=8E=92=E5=90=8D=E7=BB=9F=E8=AE=A1=E5=8F=A3=E5=BE=84=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=9A=E4=BB=B6=E6=95=B0=E6=94=B9=E4=B8=BA=E5=AE=9E?= =?UTF-8?q?=E9=99=85=E4=B8=8A=E6=9E=B6=E9=87=8FSUM(SHELVES=5FQUANTITY)(?= =?UTF-8?q?=E5=8E=9FPIECE=5FCOUNT=E4=B8=BA=E5=BB=BA=E6=A1=A3=E7=94=B3?= =?UTF-8?q?=E6=8A=A5=E5=80=BC=E4=B8=8D=E9=9A=8F=E6=94=B6=E8=B4=A7=E4=B8=8A?= =?UTF-8?q?=E6=9E=B6=E5=8F=98=E5=8C=96)=EF=BC=9BOMS=E7=AB=AF=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=E8=B7=A8=E5=BA=93=E6=94=B9=E6=9F=A5WMS?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E8=A1=A8NGWL=5FTEST=5FWMS.stock=5Fin=5Forder?= =?UTF-8?q?/in=5Fmaterial=5Fdetail(OMS=E6=89=A7=E8=A1=8C=E5=8D=95=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=B8=8D=E5=9B=9E=E4=BC=A0=E4=B8=94=E4=B8=8A=E6=9E=B6?= =?UTF-8?q?=E9=87=8F=E5=9B=9E=E5=A1=AB=E4=BB=A3=E7=A0=81=E6=9C=AA=E5=90=AF?= =?UTF-8?q?=E7=94=A8=EF=BC=8C=E4=B8=8E=E5=87=BA=E5=BA=93=E6=8E=92=E5=90=8D?= =?UTF-8?q?1300a4e1=E8=B7=A8=E5=BA=93=E4=BF=AE=E6=B3=95=E4=B8=80=E8=87=B4)?= =?UTF-8?q?=EF=BC=8C=E7=8A=B6=E6=80=81=E6=81=A2=E5=A4=8D=E4=B8=BA=E5=B7=B2?= =?UTF-8?q?=E5=85=A5=E5=BA=93(5)=EF=BC=9B=E5=85=A5=E4=BB=93=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E4=B8=BA=E7=A9=BA=E5=85=9C=E5=BA=95=E5=BB=BA=E6=A1=A3?= =?UTF-8?q?=E6=97=B6=E9=97=B4=EF=BC=9BOMS=E4=B8=8EWMS=E4=B8=A4=E7=AB=AF?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E4=B8=80=E8=87=B4=EF=BC=9B=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=B7=AF=E5=BE=84/=E5=8F=82=E6=95=B0/=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=B8=8D=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../po/ExecutionInboundMaterialRankPO.java | 10 +++--- .../po/ExecutionInboundRankTotalPO.java | 2 +- .../todo/ExecutionInboundRankDTO.java | 2 +- .../inboundRank/ExecutionInboundRankApi.java | 8 +++-- .../ExecutionInboundRankMapper.xml | 32 +++++++++++-------- .../repository/po/InboundMaterialRankPO.java | 6 ++-- .../repository/po/InboundRankTotalPO.java | 2 +- .../facadeApi/inboundRank/InboundRankApi.java | 2 +- .../mapper/inboundRank/InboundRankMapper.xml | 8 ++--- 9 files changed, 40 insertions(+), 32 deletions(-) diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundMaterialRankPO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundMaterialRankPO.java index abcf12539..6532af87f 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundMaterialRankPO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundMaterialRankPO.java @@ -14,8 +14,10 @@ import java.util.Date; /** * 入库排名统计PO(一行 = 某仓单内某货品的合计,按件数降序排名) * - *

字段口径:入仓日期=执行入仓单入仓日期;仓单编号=执行入库单号;客户编号/存货人=入仓单货主; - * 件数/毛重/体积=仓单内该货品明细合计;毛重(吨)=明细总毛重KG/1000; + *

数据源跨库取 WMS 入库表(NGWL_TEST_WMS.stock_in_order / in_material_detail,OMS 执行单状态/上架量不回传), + * 口径与 WMS 端 /inboundRankApi 完全一致,两边数字一致。 + * 字段口径:入仓日期=WMS入仓单入仓日期(为空兜底建档时间);仓单编号=WMS入库单号;客户编号/存货人=入仓单货主; + * 件数/毛重/体积=仓单内该货品明细合计(件数取实际上架量 SHELVES_QUANTITY);毛重(吨)=明细总毛重KG/1000; * 投保总额=货品档案保额(NGWL_TEST_WMS.material_base_info.insure_amount,组内取MAX防多批次明细行重复累加)

* * @author rcx @@ -33,7 +35,7 @@ public class ExecutionInboundMaterialRankPO implements Serializable { @Excel(name = "入仓日期", width = 30, dateFormat = "yyyy-MM-dd") private Date warehouseDate; - @ApiModelProperty("仓单编号(执行入库单号)") + @ApiModelProperty("仓单编号(WMS入库单号)") @Excel(name = "仓单编号") private String inOrderNumber; @@ -61,7 +63,7 @@ public class ExecutionInboundMaterialRankPO implements Serializable { @Excel(name = "单位") private String unitName; - @ApiModelProperty("件数(仓单内该货品合计,排名依据)") + @ApiModelProperty("件数(仓单内该货品实际上架量合计 SHELVES_QUANTITY,排名依据)") @Excel(name = "件数") private BigDecimal pieceCount; diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundRankTotalPO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundRankTotalPO.java index d3d1bd88d..c8a51892e 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundRankTotalPO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/po/ExecutionInboundRankTotalPO.java @@ -22,7 +22,7 @@ public class ExecutionInboundRankTotalPO implements Serializable { private static final long serialVersionUID = 1L; - @ApiModelProperty("件数合计") + @ApiModelProperty("件数合计(实际上架量)") private BigDecimal totalPieceCount; @ApiModelProperty("毛重(吨)合计") diff --git a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/todo/ExecutionInboundRankDTO.java b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/todo/ExecutionInboundRankDTO.java index 3e557dd6d..968c289d5 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/todo/ExecutionInboundRankDTO.java +++ b/mhd_oms/src/main/java/com/mhd/oms/domain/inboundRank/repository/todo/ExecutionInboundRankDTO.java @@ -8,7 +8,7 @@ import java.io.Serializable; /** * 入库排名统计入参DTO * - *

数据源为 OMS 执行入库链路(execution_in_material_detail / execution_stock_in_order)

+ *

数据源跨库取 WMS 入库链路(NGWL_TEST_WMS.stock_in_order / in_material_detail,OMS 执行单状态/上架量不回传)

* * @author rcx * @date 2026-08-31 diff --git a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java index b7cfd6391..f97d23e57 100644 --- a/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java +++ b/mhd_oms/src/main/java/com/mhd/oms/interfaces/facade/inboundRank/ExecutionInboundRankApi.java @@ -26,9 +26,11 @@ import java.util.List; /** * 入库排名统计Api * - *

按入仓日期区间筛选商品入库量统计排名。统计口径:入仓日期(为空的历史单按建档时间兜底); - * OMS 执行入库单状态不回传(长期停留已创建),不按"已入库"过滤,仅排除已取消(6)/已关闭(7), - * 与入库明细查询页面口径一致;明细仅取 level=1 主行;一行 = 某仓单内某货品的合计,按件数降序排名。 + *

按入仓日期区间筛选商品入库量统计排名。OMS 库执行入库单状态/上架数量均不回传,真实数据在 WMS 库, + * 故数据源跨库取 WMS 入库表(NGWL_TEST_WMS.stock_in_order/in_material_detail,与 OMS 出库排名跨库修法一致); + * 口径与 WMS 端 /inboundRankApi 完全一致,两边数字一致:仅已入库(5) + 明细 level=1 主行 + + * 入仓日期(为空的历史单按建档时间兜底) + 件数=实际上架量(SHELVES_QUANTITY); + * 一行 = 某仓单内某货品的合计,按件数降序排名。 * 字段:入仓日期/仓单编号/客户编号/存货人/货名/件数/毛重(吨)/投保总额/体积(CBM)。 * 与出库排名(/executionOutboundRankApi)配对,前端用 tab 页区分

* diff --git a/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml b/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml index 0401cc603..e6b76eb0d 100644 --- a/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml +++ b/mhd_oms/src/main/resources/mapper/inboundRank/ExecutionInboundRankMapper.xml @@ -5,19 +5,23 @@ a.del_flag = 1 and a.level = 1 - and b.del_flag = 1 - and b.status not in (6, 7) + and b.status = 5 and b.top_organization_id = #{topOrganizationId} @@ -48,12 +52,12 @@ - from execution_in_material_detail a - join execution_stock_in_order b on a.in_order_number = b.in_order_number and b.del_flag = 1 + from NGWL_TEST_WMS.in_material_detail a + join NGWL_TEST_WMS.stock_in_order b on a.in_order_number = b.in_order_number and b.del_flag = 1 left join NGWL_TEST_WMS.material_base_info c on a.material_base_info_id = c.material_base_info_id - + select @@ -66,7 +66,7 @@ MAX(a.material_name) AS materialName, MAX(a.SPECIFICATION_MODEL) AS specificationModel, MAX(a.unit_name) AS unitName, - IFNULL(SUM(a.PIECE_COUNT), 0) AS pieceCount, + IFNULL(SUM(a.SHELVES_QUANTITY), 0) AS pieceCount, ROUND(IFNULL(SUM(a.TOTAL_GROSS_WEIGHT), 0) / 1000, 3) AS grossWeightTon, MAX(IFNULL(c.insure_amount, 0)) AS insureAmount, IFNULL(SUM(a.TOTAL_VOLUME), 0) AS totalVolume @@ -90,7 +90,7 @@ from ( select b.in_order_number AS inOrderNumber, - IFNULL(SUM(a.PIECE_COUNT), 0) AS pieceCount, + IFNULL(SUM(a.SHELVES_QUANTITY), 0) AS pieceCount, IFNULL(SUM(a.TOTAL_GROSS_WEIGHT), 0) AS grossWeightKG, MAX(IFNULL(c.insure_amount, 0)) AS insureAmount, IFNULL(SUM(a.TOTAL_VOLUME), 0) AS totalVolume