跳转到主要内容

发货-发货详情

发货详情模块功能解析文档发货详情报表模块功能解析文档

1. 系统架构

1.1 整体架构

发货详情模块采用前后端分离架构,发货详情报表模块采用前后端分离架构,主要包含以下组件:

  • 前端组件:Vue 3 + Element Plus 构建的单页应用
  • 后端服务:Spring Boot 微服务,提供 RESTful API
  • 数据库:MySQL 数据库存储发货相关数据
  • 外部依赖:亚马逊API用于获取和同步FBA货件信息

1.2 模块依赖关系

flowchart TD
    A[前端daily.前端detail/index.vue] --> B[shipmentPlacementApi.reportApi.js]
    B --> C[InventoryReportController]ShipInboundReportController]
    C --> D[FBAInventoryServiceImpl]ShipInboundPlanService]
    D --> E[InventoryReportHisMapper]ShipInboundPlanMapper]
    E --> F[MySQL数据库]
    D --> F[ProductInfoService]G[ShipInboundItemService]
    E --> G[MySQL数据库]
    FG --> H[产品信息数据库]

2. 前端实现

2.1 核心文件结构

└── wimoor-ui/src/
    └── views/
        └── erp/
            └── shipv2/
                └── shipment_handing/
                    └── shipstep/amazon/report/ship/detail/
├── index.vue                    # 发货详情主组件主页面组件
├── components/                  # 子组件目录
└── components/...

wimoor-ui/src/api/amazon/inbound/
├── shipment_info.vuereportApi.js                 # 货件信息组件
                            ├── two_box.vue            # 货件详情标签页
                            ├── three_deliver.vue      # 物流出库标签页
                            ├── customs_picking.vue    # 海关申报标签页
                            ├── four_receive.vue       # 接收详情标签页
                            ├── ship_box.vue           # 装箱组件报表API接口
└── ship_box_case.vue      # 箱式装箱组件
    └── api/
        └── erp/
            └── shipv2/
                └── shipmentPlacementApi.reportV2Api.js               # API接口定义报表V2 API接口

2.2 前端核心代码分析核心组件分析

2.2.1 主组件结构(主页面组件(index.vue)

文件路径wimoor-ui/src/views/amazon/report/ship/detail/index.vue

主要功能

  • 提供两种数据视图:按货件汇总和按SKU汇总
  • 支持多维度筛选和数据导出
  • 实时显示数据统计信息

核心代码结构

<template>
  <el-dialog v-model="detailVisible" title="货件详情" class="ship-detail-dialog" top="3vh" width="85%"  >
        <template #header="{ close, titleId,titleClass  }">
          <div class="my-header"main-sty">
    <div!-- :id="titleId"标签页切换 :class="titleClass"-->
    <el-tabs v-model="tabActive"
                    type="card"
                    class="demo-tabs"selecttype" @tab-change="tabchange"
                  handleQuery">
      <el-tab-pane label="货件详情"按货件汇总" :name="0"shipment" key="shipment"></el-tab-pane>
      <el-tab-pane label="物流出库"按SKU汇总" :name="1"></el-tab-pane>sku" <el-tab-pane label=key="海关申报" :name="2"></el-tab-pane>
                    <el-tab-pane label="接收详情" :name="3"sku"></el-tab-pane>
    </el-tabs>

    <!-- 筛选条件区 -->
    <el-row>
      <div class="con-header">
        <el-space>
          <Group @change="changeGroup" /div>
          <el-select v-model="queryParam.datetype">
            <el-option value="createdate" label="创建日期"></el-option>
            <el-option value="deliverydate" label="发货日期"></el-option>
          </el-select>
          <Datepicker longtime="ok" @changedate="changedate" />
          <Warehouse @changeware="getWarehouse" defaultValue="all" />
          <el-input v-model="queryParam.search" placeholder="请输入货件编码" />
          <el-popover v-model:visible="moreSearchVisible">
            <!-- 高级筛选 -->
          </el-popover>
        </el-space>
      </div>
    </template>
    <div   v-loading="loading">
          <TwoBox @stepdata="stepChange" ref="twoRef"  @change="stepChange(0)" v-if="tabActive===0" />
          <ThreeDeliver @stepdata="stepChange" ref="threeRef" @change="stepChange(1)" v-if="tabActive===1" />
          <CustomsPicking ref="customsPickingRef" v-if="tabActive===2" ></CustomsPicking>
          <FourReceive ref="fourRef" v-if="tabActive===3"/> 
       <ShipmentInfo ref="shipmentRef" @change="handleShipmentInfo" />
</div>
     <template #footer>
           <el-button @click="detailVisible = false">关闭</el-button>
       </template>
    </el-dialog>
</template>

2.2.2 核心逻辑实现

// 数据初始化
let state =reactive({
    tabActive:0,
    shipmentData:{},
})

// 显示货件详情
function show(row,mstep){
    localRow.value=row;
    state.tabActive=mstep;
    shipmentid.value=row.shipmentid;
    detailVisible.value=true;
    loading.value=true;
    nextTick(()=>{
        var timer=setTimeout(()=>{
            shipmentRef.value.getBaseInfo(row.shipmentid);
        },200);
    });
}

// 处理货件信息返回
function handleShipmentInfo(data){
    loading.value=false;
    state.shipmentData = data;
    nextTick(()=>{
         handleStep(data);
    });
}

// 切换标签页处理
function tabchange(val){
    handleStep(state.shipmentData);
}

// 根据当前标签页加载对应数据
function handleStep(data){
    nextTick(()=>{
        if(state.tabActive===0){
                twoRef.value.loadOptData(data);
        }else if(state.tabActive===1){
                threeRef.value.loadOptData(data);
        }else if(state.tabActive===2){
                customsPickingRef.value.loadOptData(data);
        }else if(state.tabActive===3){
                fourRef.value.loadOptData(data);
        }
    })
}

2.2.3 货件信息组件(shipment_info.vue)

<template>
    <div class="shipment-info-wrap">
        <el-row class="ship-sty" :gutter="16">
            <el-col :span="8">
                <el-card shadow="never">
                    <div class="ship-title ">
                        <h4>单据信息</h4>
                    </div>

    <!-- 单据信息内容按货件汇总表格 -->
    <el-row v-if="selecttype=='shipment'">
      <GlobalTable ref="globalTable" :tableData="tableData" @loadTable="loadTableData">
        <!-- 表格列定义 -->
      </el-cardGlobalTable>
    </el-col>
            <el-col :span="8">
                <el-card shadow="never">
                    <div class="ship-title ">
                        <h4 >运输信息</h4>
                    </divrow>

    <!-- 运输信息内容按SKU汇总表格 -->
    <el-row v-else>
      <GlobalTable ref="skuglobalTable" :tableData="skutableData" @loadTable="skuloadTableData">
        <!-- 表格列定义 -->
      </el-card>
            </el-col>
            <el-col :span="8">
                <el-card shadow="never">
                    <div class="ship-title flex-between">
                        <h4 >地址信息</h4>
                        <el-button size="small" @click="handleShowDest" link type="info">添加箱标地址</el-button>
                    </div>
                    <!-- 地址信息内容 -->
                </el-card>
            </el-colGlobalTable>
    </el-row>
  </div>
</template>

状态管理

let state = reactive({
  queryParam: {
    search: "",
    marketplaceid: "",
    datetype: "createdate",
    companyid: "",
    hasexceptionnum: "all"
  },
  skusummary: null,
  isload: true,
  tableData: { records: [], total: 0 },
  skutableData: { records: [], total: 0 },
  companylist: [],
  channellist: [],
  moreSearchVisible: false,
  selecttype: "shipment"
});

核心方法

  1. handleQuery() - 处理查询请求
function handleQuery() {
  nextTick(() => {
    if (state.selecttype == "shipment") {
      var timer = setTimeout(function() {
        globalTable.value.loadTable(state.queryParam);
        clearTimeout(timer);
      }, 500);
    } else {
      var timer = setTimeout(function() {
        skuglobalTable.value.loadTable(state.queryParam);
        clearTimeout(timer);
      }, 500);
    }
  });
}
  1. loadTableData() - 加载货件汇总数据
function loadTableData(params) {
  reportApi.getShipmentReport(params).then((res) => {
    state.isload = false;
    state.tableData.records = res.data.records;
    state.tableData.total = res.data.total;
  });
}
  1. skuloadTableData() - 加载SKU汇总数据
function skuloadTableData(params) {
  reportApi.getShipmentDetailReport(params).then((res) => {
    state.isload = false;
    state.skutableData.records = res.data.records;
    if (params.currentpage == 1) {
      state.skusummary = res.data && res.data.records && res.data.records.length > 0 
        ? res.data.records[0].summary 
        : null;
    }
    state.skutableData.total = res.data.total;
  });
}
  1. downloadList() - 导出数据
function downloadList(ftype) {
  if (ftype == "shiptask") {
    findProcessHandle({
      "fromdate": state.queryParam.fromdate,
      "enddate": state.queryParam.enddate
    });
  } else if (ftype == "shipqty") {
    inventoryRptApi.downloadOutstockformOut({
      "fromdate": state.queryParam.fromdate,
      "enddate": state.queryParam.enddate
    });
  } else {
    state.queryParam.downloadType = ftype;
    reportApi.downShipmentExcel(state.queryParam, () => {
      state.downLoading = false;
    });
  }
}

2.3 API接口层

文件路径wimoor-ui/src/api/amazon/inbound/reportApi.js

核心接口

// 获取货件汇总报表
function getShipmentReport(data) {
  return request.post('/amazon/api/v1/ship/report/getShipmentReport', data);
}

// 获取SKU明细报表
function getShipmentDetailReport(data) {
  return request.post('/amazon/api/v1/ship/report/getShipmentDetailReport', data);
}

// 导出货件报表
function downShipmentExcel(data, callback) {
  return request({
    url: "/amazon/api/v1/ship/report/downShipmentExcel",
    responseType: "blob",
    data: data,
    method: 'post'
  }).then(res => {
    downloadhandler.downloadSuccess(res, "shipmentReport.xlsx");
    if (callback) {
      callback();
    }
  }).catch(e => {
    downloadhandler.downloadFail(e);
    if (callback) {
      callback();
    }
  });
}

3. 后端实现

3.1 核心文件结构

└── src/
    └── main/
        └── java/
            └── com/
                └── wimoor/
                    └── erp/
                        └── ship/
                            ├── controller/
                            │   └── ShipAmazonFormController.java   # 控制器
                            ├── service/
                            │   ├── IShipFormService.java          # 服务接口
                            │   └── impl/
                            │       └── ShipFormServiceImpl.java   # 服务实现
                            └── mapper/
                                └── ShipPlanItemMapper.java         # 数据访问层

3.2 后端核心代码分析控制器层

3.2.1.1 控制器层(ShipAmazonFormController.java)ShipInboundReportController

文件路径wimoor-amazon/amazon-boot/src/main/java/com/wimoor/amazon/inbound/controller/ShipInboundReportController.java

主要功能:提供发货报表相关的RESTful API接口

核心接口

  1. getShipmentReport() - 获取货件汇总报表
//@PostMapping(value 获取配货单= @GetMapping("/quotainfo/{shipmentid}"getShipmentReport")
public Result<ShipInboundShipmenSummarytVoIPage<Map<String, Object>>> quotainfoAction(@PathVariable("shipmentid")getShipmentReport(@RequestBody StringShipInboundShipmenSummaryDTO shipmentid)dto) {
  ResultMap<ShipInboundShipmenSummarytVoString, Object> resultparam = amazonClientOneFeign.infoAction(shipmentid)new HashMap<String, Object>();
  ShipInboundShipmenSummarytVoUserInfo itemsum=result.getData(user = UserInfoContext.get();
  param.put("shopid", user.getCompanyid());
  
  // 处理产品图片等信息处理marketplaceid
  //String ...
    ShipInboundShipmenSummarytVo datamarketplaceid = iWarehouseShelfInventoryService.formInvAssemblyShelf(itemsum)dto.getMarketplaceid();
  if (StrUtil.isEmpty(marketplaceid)) {
    marketplaceid = null;
  }
  param.put("marketplaceid", marketplaceid);
  
  // ...处理groupid
  returnString Result.success(data)groupid = dto.getGroupid();
  if (StrUtil.isEmpty(groupid)) {
    groupid = null;
  }
  param.put("groupid", groupid);
  
  // 处理搜索条件
  String search = dto.getSearch();
  if (StrUtil.isNotEmpty(search)) {
    param.put("search", search.trim() + "%");
  } else {
    param.put("search", null);
  }
  
  // 下载配货单处理日期类型
  String datetype = dto.getDatetype();
  param.put("datetype", datetype);
  
  // 处理日期范围
  String fromDate = dto.getFromdate();
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  if (StrUtil.isNotEmpty(fromDate)) {
    param.put("fromDate", fromDate.trim());
  } else {
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.DAY_OF_MONTH, -7);
    fromDate = GeneralUtil.formatDate(cal.getTime(), sdf);
    param.put("fromDate", fromDate);
  }
  
  String toDate = dto.getEnddate();
  if (StrUtil.isNotEmpty(toDate)) {
    param.put("endDate", toDate.trim().substring(0, 10) + " 23:59:59");
  } else {
    toDate = GeneralUtil.formatDate(new Date(), sdf);
    param.put("endDate", toDate + " 23:59:59");
  }
  
  // 处理仓库
  String warehouseid = dto.getWarehouseid();
  if (StrUtil.isEmpty(warehouseid) || "all".equals(warehouseid)) {
    warehouseid = null;
  }
  param.put("warehouseid", warehouseid);
  
  // 处理承运商
  String company = dto.getCompany();
  if (StrUtil.isEmpty(company)) {
    param.put("company", null);
  } else {
    param.put("company", company + "%");
  }
  
  String companyid = dto.getCompanyid();
  if (StrUtil.isEmpty(companyid)) {
    param.put("companyid", null);
  } else {
    param.put("companyid", companyid);
  }
  
  // 处理接收异常
  String iserror = dto.getHasexceptionnum();
  if (StrUtil.isEmpty(iserror) || "all".equals(iserror)) {
    iserror = null;
  }
  param.put("iserror", iserror);
  
  // 调用服务层获取数据
  IPage<Map<String, Object>> pagelist = shipInboundPlanService.getShipmentReport(dto.getPage(), param);
  return Result.success(pagelist);
}
  1. getShipmentDetailReport() - 获取SKU明细报表
@PostMapping(value = "/downPDFShipForm/{ftype}"getShipmentDetailReport")
public voidResult<IPage<Map<String, downPDFShipFormAction(@PathVariable("ftype")Object>>> String ftype,
        getShipmentDetailReport(@RequestBody ShipPrintLabelDTOShipInboundShipmenSummaryDTO dto, HttpServletResponse response) {
  //Map<String, 处理配货单下载逻辑Object> //param ...= new HashMap<String, Object>();
  UserInfo user = UserInfoContext.get();
  String shopid = user.getCompanyid();
  param.put("shopid", shopid);
  
  String marketplaceid = dto.getMarketplaceid();
  if (StrUtil.isEmpty(marketplaceid)) {
    marketplaceid = null;
  }
  //param.put("marketplaceid", 确认货件marketplaceid);
  
  @GetMapping(String groupid = dto.getGroupid();
  if (StrUtil.isEmpty(groupid)) {
    groupid = null;
  }
  param.put("groupid", groupid);
  
  String search = dto.getSearch();
  if (StrUtil.isNotEmpty(search)) {
    param.put("search", search.trim() + "%");
  } else {
    param.put("search", null);
  }
  
  String datetype = dto.getDatetype();
  param.put("datetype", datetype);
  
  String fromDate = dto.getFromdate();
  SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  if (StrUtil.isNotEmpty(fromDate)) {
    param.put("fromDate", fromDate.trim());
  } else {
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.DAY_OF_MONTH, -7);
    fromDate = GeneralUtil.formatDate(cal.getTime(), sdf);
    param.put("fromDate", fromDate);
  }
  
  String toDate = dto.getEnddate();
  if (StrUtil.isNotEmpty(toDate)) {
    param.put("endDate", toDate.trim().substring(0, 10) + " 23:59:59");
  } else {
    toDate = GeneralUtil.formatDate(new Date(), sdf);
    param.put("endDate", toDate + " 23:59:59");
  }
  
  String warehouseid = dto.getWarehouseid();
  if (StrUtil.isEmpty(warehouseid) || "all".equals(warehouseid)) {
    warehouseid = null;
  }
  param.put("warehouseid", warehouseid);
  
  String company = dto.getCompany();
  if (StrUtil.isEmpty(company)) {
    param.put("company", null);
  } else {
    param.put("company", company + "%");
  }
  
  String iserror = dto.getHasexceptionnum();
  if (StrUtil.isEmpty(iserror) || "all".equals(iserror)) {
    iserror = null;
  }
  param.put("iserror", iserror);
  
  IPage<Map<String, Object>> pagelist = shipInboundPlanService.getShipmentDetailReport(dto.getPage(), param);
  return Result.success(pagelist);
}
  1. downShipmentExcel() - 导出货件报表
@PostMapping(value = "/createShipment"downShipmentExcel")
@Transactional
public Result<Boolean>void createShipmentAction(StringdownShipmentExcelAction(@RequestBody shipmentid)ShipInboundShipmenSummaryDTO dto, HttpServletResponse response) {
  try {
    // 处理货件确认逻辑创建新的Excel工作薄
    SXSSFWorkbook workbook = new SXSSFWorkbook();
    response.setContentType("application/force-download");
    response.addHeader("Content-Disposition", "attachment;fileName=Shipmenttemplate.xlsx");
    ServletOutputStream fOut = response.getOutputStream();
    
    UserInfo user = UserInfoContext.get();
    String shopid = user.getCompanyid();
    
    // 构建查询参数
    Map<String, Object> params = new HashMap<String, Object>();
    String marketplaceid = dto.getMarketplaceid();
    String datetype = dto.getDatetype();
    params.put("datetype", datetype);
    
    if (StrUtil.isEmpty(marketplaceid)) {
      marketplaceid = null;
    }
    params.put("marketplaceid", marketplaceid);
    
    String groupid = dto.getGroupid();
    if (StrUtil.isEmpty(groupid)) {
      groupid = null;
    }
    params.put("groupid", groupid);
    
    String search = dto.getSearch();
    if (StrUtil.isNotEmpty(search)) {
      params.put("search", search.trim() + "%");
    } else {
      params.put("search", null);
    }
    
    String fromDate = dto.getFromdate();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    if (StrUtil.isNotEmpty(fromDate)) {
      params.put("fromDate", fromDate.trim());
    } else {
      Calendar cal = Calendar.getInstance();
      cal.add(Calendar.DAY_OF_MONTH, -7);
      fromDate = GeneralUtil.formatDate(cal.getTime(), sdf);
      params.put("fromDate", fromDate);
    }
    
    String toDate = dto.getEnddate();
    if (StrUtil.isNotEmpty(toDate)) {
      params.put("endDate", toDate.trim()...substring(0, 10) + " 23:59:59");
    } else {
      toDate = GeneralUtil.formatDate(new Date(), sdf);
      params.put("endDate", toDate + " 23:59:59");
    }
    
    params.put("shopid", shopid);
    params.put("ftype", dto.getDownloadType());
    
    // 调用服务层生成Excel
    shipInboundPlanService.setExcelBookByType(workbook, params);
    workbook.write(fOut);
    workbook.close();
    fOut.flush();
    fOut.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
}

3.2 服务层

3.2.21 服务层(ShipFormServiceImpl.java)ShipInboundPlanServiceImpl

文件路径wimoor-amazon/amazon-boot/src/main/java/com/wimoor/amazon/inbound/service/impl/ShipInboundPlanServiceImpl.java

主要功能:实现发货报表的业务逻辑

核心方法

  1. getShipmentReport() - 获取货件汇总数据
//public 将可售库存转为出库库存IPage<Map<String, Object>> getShipmentReport(Page<?> page, Map<String, Object> param) {
  return this.baseMapper.getShipmentReport(page, param);
}
  1. getShipmentDetailReport() - 获取SKU明细数据
public IPage<Map<String, Object>> getShipmentDetailReport(Page<Object> page, Map<String, Object> param) {
  IPage<Map<String, Object>> result = this.baseMapper.getShipmentDetailReport(page, param);
  if (result != null && result.getRecords().size() > 0 && page.getCurrent() == 1) {
    Map<String, Object> summary = this.baseMapper.getShipmentDetailReportTotal(param);
    Map<String, Object> map = result.getRecords().get(0);
    map.put("summary", summary);
  }
  return result;
}
  1. setExcelBookByType() - 生成Excel文件
public void fulfillableToOutbound(UserInfosetExcelBookByType(SXSSFWorkbook user,ShipFormDTOworkbook, dto)Map<String, Object> params) {
  String warehouseid=dto.getWarehouseid(type = params.get("ftype").toString();
  
  String// formid=dto.getFormid(货件发货明细
  if ("shipment".equals(type)) {
    Map<String, Object> titlemap = new LinkedHashMap<String, Object>();
    Stringtitlemap.put("ShipmentId", number=dto.getNumber("货件编码");
    titlemap.put("groupname", "发货店铺");
    titlemap.put("market", "收货站点");
    titlemap.put("center", "配送中心");
    titlemap.put("warehouse", "发货仓库");
    titlemap.put("createdate", "创建日期");
    titlemap.put("shiped_date", "发货日期");
    titlemap.put("arrivalTime", "预计到货日期");
    titlemap.put("start_receive_date", "开始接受日期");
    titlemap.put("status6date", "完成日期");
    titlemap.put("company", "承运商");
    titlemap.put("transtype", "运输方式");
    titlemap.put("channame", "发货渠道");
    titlemap.put("qtyshipped", "发货数量");
    titlemap.put("qtyreceived", "到货数量");
    titlemap.put("price", "运输费用");
    titlemap.put("otherfee", "其它费用");
    titlemap.put("totalprice", "物流费用统计");
    titlemap.put("transweight", "实际计费重量");
    titlemap.put("weightkg", "预估发货重量");
    titlemap.put("boxweight", "装箱实际重量(kg)");
    titlemap.put("boxvolume", "装箱材积重量");
    titlemap.put("wunit", "重量单位");
    titlemap.put("shipmentstatus", "状态");
    
    List<Map<String, Object>> list = this.baseMapper.getShipmentReport(params);
    Sheet sheet = workbook.createSheet("sheet1");
    
    // 创建标题行
    Row trow = sheet.createRow(0);
    Object[] titlearray = titlemap.keySet().toArray();
    for (int i = 0; i < dto.getList(titlearray.length; i++) {
      Cell cell = trow.createCell(i);
      Object value = titlemap.get(titlearray[i].toString());
      cell.setCellValue(value.toString());
    }
    
    // 填充数据行
    for (int i = 0; i < list.size(); i++) {
      ShipItemDTORow itemrow = dto.getList().get(i)sheet.createRow(i + 1);
        int shipqty =item.getQuantity();
        // 处理产品信息
        // ...
        // 检查库存是否足够
      Map<String, Object> map = inventoryService.findInvDetailById(materialid,list.get(i);
      warehouseid,for user.getCompanyid((int j = 0; j < titlearray.length; j++) {
        Cell cell = row.createCell(j);
        Object value = map.get(titlearray[j].toString());
        //if ...(value //!= 处理库存不足情况
        if(fulfillable<shipqty)null) {
          // 处理组装需求
            // ...cell.setCellValue(value.toString());
        }
      // 执行库存出库操作
        addMaterialFullfillableToOutbound(user, number,formid,warehouseid,materialid ,shipqty);}
    }
  }
  // 库存出库操作其他类型的导出...
private}
void
addMaterialFullfillableToOutbound(UserInfo

3.3 user,String数据访问层

3.3.1 ShipInboundPlanMapper

文件路径wimoor-amazon/amazon-boot/src/main/java/com/wimoor/amazon/inbound/mapper/ShipInboundPlanMapper.java

核心方法

// 获取货件汇总报表(分页)
IPage<Map<String, Object>> getShipmentReport(Page<?> page, @Param("param")Map<String, Object> param);

// 获取货件汇总报表(全部)
List<Map<String, Object>> getShipmentReport(@Param("param")Map<String, Object> param);

// 获取SKU明细报表(分页)
IPage<Map<String, Object>> getShipmentDetailReport(Page<?> page, @Param("param")Map<String, Object> param);

// 获取SKU明细报表(全部)
List<Map<String, Object>> getShipmentDetailReport(@Param("param")Map<String, Object> param);

// 获取SKU明细报表统计
Map<String, Object> getShipmentDetailReportTotal(Map<String, Object> param);

3.3.2 ShipInboundPlanMapper.xml

文件路径wimoor-amazon/amazon-boot/src/main/resources/mapper/inbound/ShipInboundPlanMapper.xml

核心SQL查询

  1. getShipmentDetailReport - 获取SKU明细报表
<select id="getShipmentDetailReport" parameterType="java.util.Map" resultType="java.util.Map">
  select item.SellerSKU sku, ship.ShipmentId, inp.number,String formid,g.name Stringgroupname,
         warehouseid,w.name Stringwarehouse, material,mkp.market, Integership.shiped_date,
         amount)trans.arrivalTime, {item.QuantityShipped, InventoryParameteritem.QuantityReceived, parainp.createdate,
         ship.DestinationFulfillmentCenterId center, detail.channame,
         case when stat.`status`='WORKING' and ship.status>=5 then '已删除' else stat.name end shipmentstatus,
         ifnull(item.QuantityReceived,0) sumrec, ship.status,
         status0date, status1date, status2date, status3date, status4date, status5date, status6date
  from t_erp_ship_inbounditem item
  left join t_erp_ship_inboundshipment ship on ship.ShipmentId = newitem.ShipmentId
  InventoryParameter();left para.setAmount(amount);join para.setFormid(formid);t_erp_ship_inboundtrans para.setMaterial(material);trans EnumByInventoryon statusinvtrans.shipmentid = EnumByInventory.Ready;ship.ShipmentId
  para.setStatus(statusinv);left para.setOperator(user.getId(join t_erp_ship_transdetail detail on detail.id = trans.channel
  left join t_erp_ship_transcompany cop on cop.id = detail.company
  left join t_erp_ship_status stat on stat.`status` = ship.ShipmentStatus
  left join t_erp_ship_inboundplan inp on inp.id = item.inboundplanid
  left join t_marketplace mkp on mkp.marketplaceId = inp.marketplaceid
  left join t_amazon_group g on g.id = inp.amazongroupid
  left join t_erp_warehouse w on w.id = inp.warehouseid
  where inp.shopid = #{param.shopid,jdbcType=CHAR}
    and ship.status > 1
  <if test="param.endDate != null">
    <if test="param.datetype == 'createdate'">
      and inp.createdate &gt;= #{param.fromDate,jdbcType=TIMESTAMP}
      and inp.createdate &lt;= #{param.endDate,jdbcType=TIMESTAMP}
    </if>
    <if test="param.datetype == 'deliverydate'">
      and ship.shiped_date &gt;= #{param.fromDate,jdbcType=TIMESTAMP}
      and ship.shiped_date &lt;= #{param.endDate,jdbcType=TIMESTAMP}
    </if>
  </if>
  <if test="param.groupid != null">
    and inp.amazongroupid = #{param.groupid,jdbcType=CHAR}
  </if>
  <if test="param.marketplaceid != null">
    and inp.marketplaceid = #{param.marketplaceid,jdbcType=CHAR}
  </if>
  <if test="param.search != null">
    and (item.SellerSKU like #{param.search,jdbcType=CHAR} 
         or ship.ShipmentId like #{param.search,jdbcType=CHAR} 
         or inp.number like #{param.search,jdbcType=CHAR})
  </if>
  <if test="param.company != null">
    and cop.name like #{param.company,jdbcType=CHAR}
  </if>
  <if test="param.warehouseid != null">
    and w.id = #{param.warehouseid,jdbcType=CHAR}
  </if>
  <if test="param.iserror != null">
    <if test="param.iserror == 'true'">
      and item.QuantityReceived != item.QuantityShipped
    </if>
    <if test="param.iserror == 'false'">
      and item.QuantityReceived = item.QuantityShipped
    </if>
  </if>
</select>
  1. getShipmentReport - 获取货件汇总报表
<select id="getShipmentReport" parameterType="java.util.Map" resultType="java.util.Map">
  select ship.ShipmentId,
         max(g.name) groupname,
         max(mkp.market) market,
         max(ship.DestinationFulfillmentCenterId) center,
         max(w.name) warehouse,
         max(inp.createdate) createdate,
         max(ship.shiped_date) shiped_date,
         max(trans.arrivalTime) arrivalTime,
         max(status6date) status6date,
         max(company.name) company,
         max(detail.channame) channame,
         count(item.SellerSKU) skunum,
         max(inp.number) number,
         sum(item.QuantityShipped) qtyshipped,
         sum(item.QuantityReceived) qtyreceived,
         max(trans.otherfee) otherfee,
         max(trans.singleprice * trans.transweight) price,
         max(trans.transweight) transweight,
         max(ship.weightkg) weightkg,
         max(ship.boxweight) boxweight,
         max(ship.boxvolume) boxvolume,
         max(ship.wunit) wunit,
         max(stat.name) shipmentstatus,
         max(trans.transtype) transtype,
         max(ship.start_receive_date) start_receive_date
  from t_erp_ship_inbounditem item
  left join t_erp_ship_inboundshipment ship on ship.ShipmentId = item.ShipmentId
  left join t_erp_ship_inboundtrans trans on trans.shipmentid = ship.ShipmentId
  left join t_erp_ship_transdetail detail on detail.id = trans.channel
  left join t_erp_ship_transcompany company on company.id = detail.company
  left join t_erp_ship_status stat on stat.`status` = ship.ShipmentStatus
  left join t_erp_ship_inboundplan inp on inp.id = item.inboundplanid
  left join t_marketplace mkp on mkp.marketplaceId = inp.marketplaceid
  left join t_amazon_group g on g.id = inp.amazongroupid
  left join t_erp_warehouse w on w.id = inp.warehouseid
  where inp.shopid = #{param.shopid,jdbcType=CHAR}
    and ship.status > 1
  <if test="param.endDate != null">
    <if test="param.datetype == 'createdate'">
      and inp.createdate &gt;= #{param.fromDate,jdbcType=TIMESTAMP}
      and inp.createdate &lt;= #{param.endDate,jdbcType=TIMESTAMP}
    </if>
    <if test="param.datetype == 'deliverydate'">
      and ship.shiped_date &gt;= #{param.fromDate,jdbcType=TIMESTAMP}
      and ship.shiped_date &lt;= #{param.endDate,jdbcType=TIMESTAMP}
    </if>
  </if>
  <if test="param.groupid != null">
    and inp.amazongroupid = #{param.groupid,jdbcType=CHAR}
  </if>
  <if test="param.marketplaceid != null">
    and inp.marketplaceid = #{param.marketplaceid,jdbcType=CHAR}
  </if>
  <if test="param.search != null">
    and (ship.ShipmentId like #{param.search,jdbcType=CHAR} 
         or inp.number like #{param.search,jdbcType=CHAR});
  para.setOpttime(new</if>
  Date());<if para.setWarehouse(warehouseid);test="param.company para.setFormtype(!= null">
    and company.name like #{param.company,jdbcType=CHAR}
  </if>
  <if test="outstockform");param.companyid para.setNumber(number);!= para.setShopid(user.getCompanyid());null">
    inventoryFormAgentService.outStockByRead(para);and }company.id = #{param.companyid,jdbcType=CHAR}
  </if>
  <if test="param.warehouseid != null">
    and w.id = #{param.warehouseid,jdbcType=CHAR}
  </if>
  <if test="param.iserror != null">
    <if test="param.iserror == 'true'">
      and sum(item.QuantityReceived) != sum(item.QuantityShipped)
    </if>
    <if test="param.iserror == 'false'">
      and sum(item.QuantityReceived) = sum(item.QuantityShipped)
    </if>
  </if>
  group by ship.ShipmentId
</select>

4. 数据库设计

4.1 核心数据表核心表结构

4.1.1 erp_shipment(t_erp_ship_inboundshipment(货件表)

字段名 数据类型类型 描述说明
ShipmentIdvarchar货件ID(主键)
inboundplanidvarchar发货计划ID
DestinationFulfillmentCenterIdvarchar目标配送中心ID
ShipmentStatusvarchar货件状态
shiped_datedatetime发货日期
status6datedatetime完成日期
weightkgdecimal预估发货重量
boxweightdecimal装箱实际重量
boxvolumedecimal装箱材积重量
wunitvarchar重量单位
statusint状态码
start_receive_datedatetime开始接收日期

4.1.2 t_erp_ship_inbounditem(货件明细表)

字段名类型说明
id BIGINTvarchar 主键ID
shipmentidShipmentId VARCHAR(50)varchar 货件ID
shipmentConfirmationIdinboundplanid VARCHAR(50)varchar 货件确认ID发货计划ID
nameSellerSKU VARCHAR(255)varchar 货件名称产品SKU
referenceidQuantityShipped VARCHAR(50)int 参考ID发货数量
shipmentstatusQuantityReceived VARCHAR(50)int 货件状态
groupidVARCHAR(32)店铺ID
groupnameVARCHAR(255)店铺名称
warehouseVARCHAR(50)发货仓库
countryVARCHAR(50)收货国家
created_atDATETIME创建时间
updated_atDATETIME更新时间接收数量

4.1.23 erp_shipment_item(货件商品表)t_erp_ship_inboundtrans(货件运输表)

字段名 数据类型类型 描述说明
shipmentidvarchar货件ID
channelvarchar渠道ID
arrivalTimedatetime预计到货时间
otherfeedecimal其它费用
singlepricedecimal单价
transweightdecimal运输重量
transtypevarchar运输方式

4.1.4 t_erp_ship_transdetail(运输详情表)

字段名类型说明
id BIGINTvarchar 主键ID
shipmentidcompany VARCHAR(50)varchar 货件ID承运商ID
skuchanname VARCHAR(50)varchar 产品SKU
materialidVARCHAR(32)物料ID
quantityINT数量
unitcostDECIMAL(10,2)单位成本
created_atDATETIME创建时间
updated_atDATETIME更新时间渠道名称

4.21.5 数据流程

  1. 数据同步:定时从亚马逊API获取FBA货件信息
  2. 数据存储:将货件信息存储到erp_shipment和erp_shipment_item表
  3. 数据查询:前端请求时,后端从数据库查询货件详情
  4. 数据处理:处理货件信息,包括产品图片、库存信息等
  5. 数据返回:将处理后的数据返回给前端展示

5. API接口定义

5.1 接口列表

t_erp_ship_transcompany(承运商表)
接口URL字段名 请求方法类型 功能描述说明
/api/v1/shipForm/quotainfo/{shipmentid}id GETvarchar 获取配货单详情承运商ID(主键)
/api/v1/shipForm/getFBAInvDayDetailFieldname POSTvarchar 获取日期字段列表
/api/v1/shipForm/downPDFShipForm/{ftype}POST下载配货单
/api/v1/shipForm/createShipmentGET确认货件
/api/v1/shipForm/disableShipmentGET删除货件
/api/v1/shipForm/updateShipmentPOST更新货件
/api/v1/shipForm/getQRCode/{shipmentid}/{size}POST获取配货单二维码承运商名称

5.2

4.1.6 请求参数

t_erp_ship_inboundplan(发货计划表)
参数名字段名 类型 描述说明
shipmentidid Stringvarchar 货件ID发货计划ID(主键)
ftypenumber Stringvarchar 文件类型(detail/simple)内部编号
fbaShipmentDTOamazongroupid Objectvarchar 货件DTO对象店铺ID
sizemarketplaceid Stringvarchar 二维码大小市场ID
warehouseidvarchar仓库ID
createdatedatetime创建日期

4.1.7 t_amazon_group(店铺表)

字段名类型说明
idvarchar店铺ID(主键)
namevarchar店铺名称

4.1.8 t_marketplace(市场表)

字段名类型说明
marketplaceIdvarchar市场ID(主键)
marketvarchar市场名称

4.1.9 t_erp_warehouse(仓库表)

字段名类型说明
idvarchar仓库ID(主键)
namevarchar仓库名称

4.1.10 t_erp_ship_status(货件状态表)

字段名类型说明
statusvarchar状态码(主键)
namevarchar状态名称

5. 业务流程

5.31 响应格式数据查询流程

sequenceDiagram
    participant User as 用户
    participant Frontend as 前端
    participant Controller as 控制器
    participant Service as 服务层
    participant Mapper as 数据访问层
    participant DB as 数据库

    User->>Frontend: 选择筛选条件
    Frontend->>Frontend: 构建查询参数
    Frontend->>Controller: POST /api/v1/ship/report/getShipmentReport
    Controller->>Controller: 处理查询参数
    Controller->>Service: getShipmentReport(page, param)
    Service->>Mapper: getShipmentReport(page, param)
    Mapper->>DB: 执行SQL查询
    DB->>Mapper: 返回查询结果
    Mapper->>Service: 返回分页数据
    Service->>Controller: 返回分页数据
    Controller->>Frontend: 返回JSON响应
    Frontend->>User: 显示数据表格

5.2 数据导出流程

sequenceDiagram
    participant User as 用户
    participant Frontend as 前端
    participant Controller as 控制器
    participant Service as 服务层
    participant Mapper as 数据访问层
    participant DB as 数据库
    participant Excel as Excel生成器

    User->>Frontend: 点击导出按钮
    Frontend->>Frontend: 构建导出参数
    Frontend->>Controller: POST /api/v1/ship/report/downShipmentExcel
    Controller->>Controller: 处理导出参数
    Controller->>Service: setExcelBookByType(workbook, params)
    Service->>Mapper: getShipmentReport(params)
    Mapper->>DB: 执行SQL查询
    DB->>Mapper: 返回查询结果
    Mapper->>Service: 返回数据列表
    Service->>Excel: 创建Excel工作簿
    Excel->>Excel: 写入标题行
    Excel->>Excel: 写入数据行
    Excel->>Controller: 返回Excel文件
    Controller->>Frontend: 返回文件流
    Frontend->>User: 下载Excel文件

6. API接口文档

6.1 获取货件汇总报表

接口地址POST /api/v1/ship/report/getShipmentReport

请求参数

{
  "currentpage": 1,
  "pagesize": 20,
  "groupid": "店铺ID",
  "marketplaceid": "市场ID",
  "search": "货件编码",
  "datetype": "createdate",
  "fromdate": "2026-01-01",
  "enddate": "2026-01-31",
  "warehouseid": "仓库ID",
  "company": "承运商名称",
  "companyid": "承运商ID",
  "hasexceptionnum": "all"
}

响应参数

{
  "code": 200,
  "msg": "success",
  "data": {
    "shipmentAll"records": [
      {}
        "ShipmentId": "FBA123456789",
        "shipment"groupname": {}"美国店铺",
        "plan"market": {}"US",
        "totalBoxSize"center": "SBD1",
        "warehouse": "深圳仓库",
        "createdate": "2026-01-01 10:00:00",
        "shiped_date": "2026-01-05 15:00:00",
        "arrivalTime": "2026-01-15 00:00:00",
        "start_receive_date": "2026-01-14 10:00:00",
        "status6date": "2026-01-20 16:00:00",
        "company": "DHL",
        "channame": "空运",
        "qtyshipped": 1000,
        "qtyreceived": 998,
        "price": 500.00,
        "otherfee": 50.00,
        "totalprice": 550.00,
        "transweight": 500.5,
        "weightkg": 502.0,
        "fromAddress"boxweight": {}501.0,
        "boxvolume": 503.0,
        "wunit": "kg",
        "toAddress"shipmentstatus": {"已接收",
        "transtype": "AIR"
      }
    ],
    "total": 100,
    "size": 20,
    "current": 1,
    "pages": 5
  }
}

6. 关键技术点

6.1 多标签页动态加载

  • 前端实现:使用v-if条件渲染不同标签页内容
  • 后端实现:根据标签页类型返回对应的数据
  • 技术优势:减少初始加载时间,提高页面响应速度

6.2 动态标签生成获取SKU明细报表

接口地址POST /api/v1/ship/report/getShipmentDetailReport

请求参数

//{
  生成动态标签字段"currentpage": @GetMapping(1,
  "/getFBAInvDayDetailField")pagesize": public20,
  Result<List<Map<String,"groupid": String>>>"店铺ID",
  getFBAInvDayDetailFieldAction(@RequestBody"marketplaceid": InvDayDetailDTO"市场ID",
  query)"search": "SKU或货件编码",
  "datetype": "createdate",
  "fromdate": "2026-01-01",
  "enddate": "2026-01-31",
  "warehouseid": "仓库ID",
  "company": "承运商名称",
  "hasexceptionnum": "all"
}

响应参数

{
  "code": 200,
  "msg": "success",
  "data": {
    Map<String,"records": Date>[
      parameter{
        ="sku": new"SKU001",
        HashMap<String,"ShipmentId": Date>();"FBA123456789",
        //"number": 处理日期参数"SF20260101001",
        //"groupname": ..."美国店铺",
        List<Map<String,"warehouse": String>>"深圳仓库",
        fieldlist"market": ="US",
        iFBAInventoryService.getInvDaySumField(parameter);"shiped_date": return"2026-01-05 Result.success(fieldlist);15:00:00",
        "arrivalTime": "2026-01-15 00:00:00",
        "QuantityShipped": 100,
        "QuantityReceived": 98,
        "createdate": "2026-01-01 10:00:00",
        "center": "SBD1",
        "channame": "空运",
        "shipmentstatus": "已接收",
        "summary": {
          "QuantityShipped": 1000,
          "QuantityReceived": 998
        }
      }
    ],
    "total": 500,
    "size": 20,
    "current": 1,
    "pages": 25
  }
}

6.3 库存状态转换导出货件报表

接口地址POST /api/v1/ship/report/downShipmentExcel

请求参数

{
  "groupid": "店铺ID",
  "marketplaceid": "市场ID",
  "search": "货件编码",
  "datetype": "createdate",
  "fromdate": "2026-01-01",
  "enddate": "2026-01-31",
  "warehouseid": "仓库ID",
  "company": "承运商名称",
  "downloadType": "shipment"
}

响应:Excel文件流

downloadType参数说明

  • 可售库存转出库shipment当货件确认后,将可售库存转为出库库存货件汇总报表
  • 库存不足处理other当库存不足时,自动生成组装任务含子SKU的SKU明细报表
  • 事务管理shipqty确保库存操作的原子性和一致性

6.4 标签打印功能

  • 支持多种标签类型:标准标签、2D条形码、托盘标签发货数量简约版
  • 灵活的打印选项shiptask支持批量打印和单个打印
  • PDF生成:使用iTextPDF库生成PDF格式的标签发货处理任务量

7. 性能优化

7.1 前端优化数据库优化

  1. 虚拟滚动索引优化使用虚拟滚动技术处理大量数据,提高表格渲染速度

    • t_erp_ship_inboundshipment表的ShipmentId字段上建立索引
    • 懒加载:按需加载不同标签页的内容,减少初始加载时间t_erp_ship_inbounditem表的ShipmentIdSellerSKU字段上建立复合索引
    • 组件缓存:对频繁使用的组件进行缓存,提高组件复用率t_erp_ship_inboundplan表的shopidamazongroupidmarketplaceid字段上建立索引
  2. 防抖处理查询优化对搜索等操作添加防抖,减少频繁请求

    • 使用LEFT JOIN避免数据丢失
    • 使用MAX、SUM等聚合函数减少数据传输量
    • 使用分页查询避免一次性加载大量数据

7.2 后端优化前端优化

  1. 索引优化延迟加载在关键查询字段上建立索引,提高查询效率

    • 使用setTimeout延迟查询请求,避免频繁调用
    • 分页查询:使用分页查询,避免一次性返回大量数据使用nextTick确保DOM更新后再执行操作
  2. 缓存机制分页加载对频繁访问的数据进行缓存,减少数据库查询

    • 使用分页组件减少单次加载的数据量
    • 支持自定义每页显示条数
  3. 异步处理缓存优化对耗时操作使用异步处理,提高系统响应速度

    • 缓存筛选条件,避免重复查询
    • 使用computed属性优化计算性能

7.3 数据库优化后端优化

  1. 分表分库异步处理对大量数据进行分表分库,提高数据库性能

    • 使用SXSSFWorkbook流式处理大数据量导出
    • 查询优化:优化SQL查询,减少数据库负载使用异步任务处理耗时操作
  2. 定期清理参数验证定期清理过期数据,保持数据库性能

    • 使用StrUtil工具类进行空值判断
    • 设置合理的默认值

8. 扩展建议扩展功能

8.1 功能扩展自定义列显示

  1. 实时状态推送:添加WebSocket支持,实现货件状态实时推送
  2. 智能预警:根据货件状态和时间,添加智能预警功能
  3. 数据分析:添加货件数据分析功能,提供数据可视化报表
  4. 多平台支持:扩展支持其他电商平台的发货管理

用户可以根据需要自定义表格显示的列,提高数据查看的灵活性。

8.2 技术扩展多格式导出

    支持多种导出格式,满足不同的业务需求:

    • 微服务架构:将模块拆分为独立的微服务,提高系统扩展性普通导出:包含所有字段的完整数据
    • 容器化部署:使用Docker容器化部署,提高部署效率含子SKU:包含子SKU信息的详细版本
    • CI/CD集成:集成持续集成和持续部署,提高开发效率发货数量简约版:只包含发货数量相关信息的简化版本
    • 自动化测试:添加自动化测试,提高代码质量发货处理任务量:包含发货处理任务量的统计数据

8.3 数据统计

在按SKU汇总视图下,自动汇总总发货数量和总接收数量,方便用户快速了解整体情况。

9. 总结注意事项

9.1 数据同步

  • 货件数据通过亚马逊API同步,可能存在延迟
  • 建议定期刷新数据以获取最新信息

9.2 权限控制

  • 用户只能查看自己公司的数据
  • 店铺和市场权限通过用户信息自动过滤

9.3 性能考虑

  • 避免选择过大的日期范围
  • 使用精确的筛选条件减少数据量
  • 大数据量导出可能需要较长时间

9.4 异常处理

  • 接收异常的货件需要特别关注
  • 建议定期检查并处理接收异常

10. 故障排查

10.1 数据不显示

发货详情模块是Wimoor系统中重要的发货管理功能,采用了前后端分离架构,具有高性能、高扩展性的特点。通过多标签页展示、全面的货件信息和灵活的操作功能,帮助卖家高效管理FBA发货流程。可能原因

  • 筛选条件设置不正确
  • 数据同步延迟
  • 权限不足

该模块的设计和实现遵循了现代软件解决方法

engineering
    最佳实践,具有良好的可维护性和可扩展性。在未来的发展中,可以进一步扩展功能,提高系统性能,为卖家提供更全面、更深入的发货管理服务。
  • 检查筛选条件
  • 刷新页面
  • 联系管理员检查权限

10.2 导出失败

可能原因

  • 数据量过大
  • 网络问题
  • 服务器异常

解决方法

  • 缩小日期范围
  • 检查网络连接
  • 联系技术支持

10.3 性能问题

可能原因

  • 数据量过大
  • 查询条件不优化
  • 数据库索引缺失

解决方法

  • 使用分页查询
  • 优化筛选条件
  • 添加数据库索引

文档版本:v1.0
更新时间最后更新:2026-01-26
适用系统:Wimoor 6.0及以上版本FBA发货管理系统