11 小时以前 fb3a5d1f1c77848dfe5fd47d0cb4132ad25c1564
客户往来,供应商往来优化
已添加4个文件
已修改10个文件
1275 ■■■■■ 文件已修改
docs/20260618_customer_transactions_optimization.md 839 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsProductVo.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsShipmentVo.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsSummaryVo.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/20260618_customer_transactions_optimization.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,839 @@
# å®¢æˆ·å¾€æ¥å¤šç»´åº¦æ˜Žç»†åŠŸèƒ½å‰ç«¯è”è°ƒæ–‡æ¡£
> ä¼˜åŒ–客户往来功能,新增多维度明细查询接口,支持产品明细和发货明细维度
## æ¶‰åŠé¡µé¢
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å®¢æˆ·å¾€æ¥åˆ—表页
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å®¢æˆ·å¾€æ¥è¯¦æƒ…页(新增)
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - äº§å“æ˜Žç»†Tab(新增)
- è¥é”€ç®¡ç† / å®¢æˆ·å¾€æ¥ - å‘货明细Tab(新增)
## API
### 1. å®¢æˆ·å¾€æ¥åˆ—表(原有接口,未变更)
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customewTransactions | å®¢æˆ·å¾€æ¥åˆ—表 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| customerName | String | å¦ | å®¢æˆ·åç§°ï¼ˆæ¨¡ç³Šæœç´¢ï¼‰ |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| customerId | Long | å®¢æˆ·ID |
| customerName | String | å®¢æˆ·åç§° |
| contractAmounts | BigDecimal | åˆåŒæ€»é‡‘额 |
| receiptPaymentAmount | BigDecimal | æ”¶æ¬¾é‡‘额 |
| receiptableAmount | BigDecimal | åº”收金额 |
---
### 1.5 å®¢æˆ·å¾€æ¥æ˜Žç»†ï¼ˆåŽŸæœ‰æŽ¥å£ï¼Œæ–°å¢žå­—æ®µï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customewTransactionsDetails | å®¢æˆ·å¾€æ¥æ˜Žç»†ï¼ˆåˆåŒç»´åº¦ï¼‰ |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesLedgerId | Long | é”€å”®å°è´¦ID |
| salesContractNo | String | é”€å”®åˆåŒå· |
| executionDate | LocalDate | åˆåŒç­¾è®¢æ—¥æœŸ |
| contractAmount | BigDecimal | åˆåŒé‡‘额 |
| **productNames** | String | **产品名称列表(逗号分隔)【新增】** |
| receiptPaymentAmount | BigDecimal | æ”¶æ¬¾é‡‘额 |
| receiptableAmount | BigDecimal | åº”收金额 |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "salesLedgerId": 1,
        "salesContractNo": "HT-2026-001",
        "executionDate": "2026-06-01",
        "contractAmount": 50000.00,
        "productNames": "产品A,产品B,产品C",
        "receiptPaymentAmount": 30000.00,
        "receiptableAmount": 20000.00
      }
    ],
    "total": 10
  }
}
```
---
### 2. å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»ï¼ˆæ–°å¢žï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsSummary | å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€» |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| customerId | Long | å®¢æˆ·ID |
| customerName | String | å®¢æˆ·åç§° |
| contractAmounts | BigDecimal | åˆåŒæ€»é‡‘额 |
| contractCount | Integer | åˆåŒæ•°é‡ |
| productCount | Integer | äº§å“ç§ç±»æ•° |
| shippedAmounts | BigDecimal | å‘货总金额 |
| shippedQuantity | BigDecimal | å‘货总数量 |
| receivedAmounts | BigDecimal | æ”¶æ¬¾é‡‘额 |
| receivableAmounts | BigDecimal | åº”收金额 |
| returnAmounts | BigDecimal | é€€è´§é‡‘额 |
| unshippedAmounts | BigDecimal | æœªå‘货金额 |
| receivedRate | BigDecimal | æ”¶æ¬¾çއ(%) |
| shippedRate | BigDecimal | å‘货率(%) |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "customerId": 1,
    "customerName": "客户A",
    "contractAmounts": 100000.00,
    "contractCount": 5,
    "productCount": 12,
    "shippedAmounts": 80000.00,
    "shippedQuantity": 500,
    "receivedAmounts": 60000.00,
    "receivableAmounts": 20000.00,
    "returnAmounts": 5000.00,
    "unshippedAmounts": 20000.00,
    "receivedRate": 75.00,
    "shippedRate": 80.00
  }
}
```
---
### 3. å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†ï¼ˆæ–°å¢žï¼‰
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsProducts | å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»† |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
| salesLedgerId | Long | å¦ | é”€å”®å°è´¦ID(可选,用于筛选某合同) |
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesLedgerId | Long | é”€å”®å°è´¦ID |
| salesContractNo | String | é”€å”®åˆåŒå· |
| productId | Long | äº§å“ID |
| productName | String | äº§å“åç§° |
| model | String | è§„格型号 |
| unit | String | å•位 |
| contractQuantity | BigDecimal | åˆåŒæ•°é‡ |
| taxInclusiveUnitPrice | BigDecimal | åˆåŒå•ä»·(含税) |
| contractAmount | BigDecimal | åˆåŒé‡‘额 |
| shippedQuantity | BigDecimal | å·²å‘货数量 |
| shippedAmount | BigDecimal | å·²å‘货金额 |
| receivedAmount | BigDecimal | å·²æ”¶æ¬¾é‡‘额 |
| receivableAmount | BigDecimal | åº”收金额 |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "salesLedgerId": 1,
        "salesContractNo": "HT-2026-001",
        "productId": 10,
        "productName": "产品A",
        "model": "规格1",
        "unit": "ä»¶",
        "contractQuantity": 100,
        "taxInclusiveUnitPrice": 50.00,
        "contractAmount": 5000.00,
        "shippedQuantity": 80,
        "shippedAmount": 4000.00,
        "receivedAmount": 3000.00,
        "receivableAmount": 1000.00
      }
    ],
    "total": 25,
    "pageNum": 1,
    "pageSize": 10
  }
}
```
---
### 4. å®¢æˆ·å¾€æ¥å‘货明细(新增)
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /metricStatistics/customerTransactionsShipments | å®¢æˆ·å¾€æ¥å‘货明细 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| customerId | Long | æ˜¯ | å®¢æˆ·ID |
| salesLedgerId | Long | å¦ | é”€å”®å°è´¦ID(可选,用于筛选某合同) |
| pageNum | Long | å¦ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Long | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesLedgerId | Long | é”€å”®å°è´¦ID |
| salesContractNo | String | é”€å”®åˆåŒå· |
| shippingId | Long | å‘货单ID |
| shippingNo | String | å‘货单号 |
| productName | String | äº§å“åç§° |
| model | String | è§„格型号 |
| shippingQuantity | BigDecimal | å‘货数量 |
| shippingAmount | BigDecimal | å‘货金额(含税) |
| batchNo | String | å‡ºåº“批号 |
| shippingDate | LocalDate | å‘货日期 |
| approvalStatus | Integer | å®¡æ‰¹çŠ¶æ€(0待审/1已审) |
| receivedAmount | BigDecimal | å·²æ”¶æ¬¾é‡‘额 |
| receivableAmount | BigDecimal | åº”收金额 |
**响应示例:**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "salesLedgerId": 1,
        "salesContractNo": "HT-2026-001",
        "shippingId": 100,
        "shippingNo": "FH-2026-001",
        "productName": "产品A",
        "model": "规格1",
        "shippingQuantity": 50,
        "shippingAmount": 2500.00,
        "batchNo": "20260618001",
        "shippingDate": "2026-06-18",
        "approvalStatus": 1,
        "receivedAmount": 2000.00,
        "receivableAmount": 500.00
      }
    ],
    "total": 30,
    "pageNum": 1,
    "pageSize": 10
  }
}
```
---
## å‰ç«¯é¡µé¢è®¾è®¡
### 1. å®¢æˆ·å¾€æ¥åˆ—表页(优化)
```html
<template>
  <div class="app-container">
    <!-- æœç´¢æ  -->
    <el-form :model="queryParams" ref="queryForm" :inline="true">
      <el-form-item label="客户名称" prop="customerName">
        <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList">搜索</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading">
      <el-table-column label="客户名称" prop="customerName" />
      <el-table-column label="合同总金额" prop="contractAmounts" align="right">
        <template #default="{ row }">
          {{ formatMoney(row.contractAmounts) }}
        </template>
      </el-table-column>
      <el-table-column label="收款金额" prop="receiptPaymentAmount" align="right">
        <template #default="{ row }">
          {{ formatMoney(row.receiptPaymentAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receiptableAmount" align="right">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receiptableAmount > 0 }">
            {{ formatMoney(row.receiptableAmount) }}
          </span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150" align="center">
        <template #default="{ row }">
          <el-button type="text" @click="viewDetail(row)">查看明细</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'CustomerTransactions',
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerName: ''
      }
    }
  },
  created() {
    this.getList()
  },
  methods: {
    getList() {
      this.loading = true
      this.$axios.get('/metricStatistics/customewTransactions', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    resetQuery() {
      this.queryParams.customerName = ''
      this.getList()
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    viewDetail(row) {
      this.$router.push({ path: '/sales/customerTransactions/detail', query: { customerId: row.customerId } })
    }
  }
}
</script>
```
---
### 2. å®¢æˆ·å¾€æ¥è¯¦æƒ…页(新增)
```html
<template>
  <div class="app-container">
    <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
    <el-card class="summary-card">
      <div slot="header">
        <span>{{ summary.customerName }} - å¾€æ¥ç»Ÿè®¡</span>
      </div>
      <el-row :gutter="20">
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">合同总金额</div>
            <div class="stat-value">{{ formatMoney(summary.contractAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">合同数量</div>
            <div class="stat-value">{{ summary.contractCount }}份</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">产品种类</div>
            <div class="stat-value">{{ summary.productCount }}种</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">发货金额</div>
            <div class="stat-value">{{ formatMoney(summary.shippedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">收款金额</div>
            <div class="stat-value text-success">{{ formatMoney(summary.receivedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">应收金额</div>
            <div class="stat-value text-danger">{{ formatMoney(summary.receivableAmounts) }}</div>
          </div>
        </el-col>
      </el-row>
      <el-row :gutter="20" style="margin-top: 15px;">
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">退货金额</div>
            <div class="stat-value">{{ formatMoney(summary.returnAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">未发货金额</div>
            <div class="stat-value text-warning">{{ formatMoney(summary.unshippedAmounts) }}</div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">收款率</div>
            <div class="stat-value">
              <el-progress :percentage="summary.receivedRate || 0" :stroke-width="18" />
            </div>
          </div>
        </el-col>
        <el-col :span="4">
          <div class="stat-item">
            <div class="stat-label">发货率</div>
            <div class="stat-value">
              <el-progress :percentage="summary.shippedRate || 0" :stroke-width="18" color="#67c23a" />
            </div>
          </div>
        </el-col>
      </el-row>
    </el-card>
    <!-- Tab åˆ‡æ¢ -->
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane label="产品明细" name="products">
        <product-table :customerId="customerId" />
      </el-tab-pane>
      <el-tab-pane label="发货明细" name="shipments">
        <shipment-table :customerId="customerId" />
      </el-tab-pane>
      <el-tab-pane label="合同明细" name="contracts">
        <contract-table :customerId="customerId" />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script>
import ProductTable from './components/ProductTable.vue'
import ShipmentTable from './components/ShipmentTable.vue'
import ContractTable from './components/ContractTable.vue'
export default {
  name: 'CustomerTransactionsDetail',
  components: { ProductTable, ShipmentTable, ContractTable },
  data() {
    return {
      customerId: null,
      summary: {},
      activeTab: 'products'
    }
  },
  created() {
    this.customerId = this.$route.query.customerId
    if (this.customerId) {
      this.getSummary()
    }
  },
  methods: {
    getSummary() {
      this.$axios.get('/metricStatistics/customerTransactionsSummary', {
        params: { customerId: this.customerId }
      }).then(res => {
        this.summary = res.data
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    handleTabChange(tab) {
      // Tab åˆ‡æ¢æ—¶åˆ·æ–°å­ç»„件数据
    }
  }
}
</script>
<style scoped>
.summary-card {
  margin-bottom: 20px;
}
.stat-item {
  text-align: center;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
.stat-value {
  font-size: 18px;
  font-weight: bold;
  margin-top: 5px;
}
.text-success {
  color: #67c23a;
}
.text-danger {
  color: #f56c6c;
}
.text-warning {
  color: #e6a23c;
}
</style>
```
---
### 3. äº§å“æ˜Žç»†ç»„ä»¶ (ProductTable.vue)
```html
<template>
  <div>
    <!-- ç­›é€‰ -->
    <el-form :inline="true" size="small">
      <el-form-item label="合同号">
        <el-select v-model="filterData.salesLedgerId" clearable placeholder="全部合同" @change="getList">
          <el-option v-for="item in contractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
        </el-select>
      </el-form-item>
    </el-form>
    <!-- è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading" size="small">
      <el-table-column label="合同号" prop="salesContractNo" width="150" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" width="120" />
      <el-table-column label="单位" prop="unit" width="80" />
      <el-table-column label="合同数量" prop="contractQuantity" align="right" width="100" />
      <el-table-column label="合同单价" prop="taxInclusiveUnitPrice" align="right" width="100">
        <template #default="{ row }">
          {{ formatMoney(row.taxInclusiveUnitPrice) }}
        </template>
      </el-table-column>
      <el-table-column label="合同金额" prop="contractAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.contractAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="已发货数量" prop="shippedQuantity" align="right" width="100" />
      <el-table-column label="已发货金额" prop="shippedAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.shippedAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="已收款金额" prop="receivedAmount" align="right" width="120">
        <template #default="{ row }">
          <span class="text-success">{{ formatMoney(row.receivedAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receivableAmount" align="right" width="120">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receivableAmount > 0 }">
            {{ formatMoney(row.receivableAmount) }}
          </span>
        </template>
      </el-table-column>
      <el-table-column label="发货进度" width="150">
        <template #default="{ row }">
          <el-progress
            :percentage="calcPercent(row.shippedQuantity, row.contractQuantity)"
            :stroke-width="10"
          />
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      layout="total, prev, pager, next"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'ProductTable',
  props: {
    customerId: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerId: null,
        salesLedgerId: null
      },
      filterData: {
        salesLedgerId: null
      },
      contractList: []
    }
  },
  watch: {
    customerId(val) {
      if (val) {
        this.queryParams.customerId = val
        this.getList()
        this.getContractList()
      }
    }
  },
  methods: {
    getList() {
      this.loading = true
      this.queryParams.salesLedgerId = this.filterData.salesLedgerId
      this.$axios.get('/metricStatistics/customerTransactionsProducts', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    getContractList() {
      // èŽ·å–è¯¥å®¢æˆ·çš„åˆåŒåˆ—è¡¨ç”¨äºŽç­›é€‰
      this.$axios.get('/metricStatistics/customewTransactionsDetails', {
        params: { customerId: this.customerId, pageNum: 1, pageSize: 100 }
      }).then(res => {
        this.contractList = res.data.records
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    },
    calcPercent(shipped, total) {
      if (!total || total === 0) return 0
      return Math.round((shipped / total) * 100)
    }
  }
}
</script>
```
---
### 4. å‘货明细组件 (ShipmentTable.vue)
```html
<template>
  <div>
    <!-- ç­›é€‰ -->
    <el-form :inline="true" size="small">
      <el-form-item label="合同号">
        <el-select v-model="filterData.salesLedgerId" clearable placeholder="全部合同" @change="getList">
          <el-option v-for="item in contractList" :key="item.id" :label="item.salesContractNo" :value="item.id" />
        </el-select>
      </el-form-item>
    </el-form>
    <!-- è¡¨æ ¼ -->
    <el-table :data="list" v-loading="loading" size="small">
      <el-table-column label="合同号" prop="salesContractNo" width="150" />
      <el-table-column label="发货单号" prop="shippingNo" width="150" />
      <el-table-column label="产品名称" prop="productName" />
      <el-table-column label="规格型号" prop="model" width="120" />
      <el-table-column label="发货数量" prop="shippingQuantity" align="right" width="100" />
      <el-table-column label="发货金额" prop="shippingAmount" align="right" width="120">
        <template #default="{ row }">
          {{ formatMoney(row.shippingAmount) }}
        </template>
      </el-table-column>
      <el-table-column label="出库批号" prop="batchNo" width="150" />
      <el-table-column label="发货日期" prop="shippingDate" width="120" />
      <el-table-column label="审批状态" prop="approvalStatus" width="100">
        <template #default="{ row }">
          <el-tag :type="row.approvalStatus === 1 ? 'success' : 'warning'" size="mini">
            {{ row.approvalStatus === 1 ? '已审' : '待审' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="已收款金额" prop="receivedAmount" align="right" width="120">
        <template #default="{ row }">
          <span class="text-success">{{ formatMoney(row.receivedAmount) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="应收金额" prop="receivableAmount" align="right" width="120">
        <template #default="{ row }">
          <span :class="{ 'text-danger': row.receivableAmount > 0 }">
            {{ formatMoney(row.receivableAmount) }}
          </span>
        </template>
      </el-table-column>
    </el-table>
    <!-- åˆ†é¡µ -->
    <el-pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      layout="total, prev, pager, next"
      @pagination="getList"
    />
  </div>
</template>
<script>
export default {
  name: 'ShipmentTable',
  props: {
    customerId: {
      type: Number,
      required: true
    }
  },
  data() {
    return {
      loading: false,
      list: [],
      total: 0,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        customerId: null,
        salesLedgerId: null
      },
      filterData: {
        salesLedgerId: null
      },
      contractList: []
    }
  },
  watch: {
    customerId(val) {
      if (val) {
        this.queryParams.customerId = val
        this.getList()
        this.getContractList()
      }
    }
  },
  methods: {
    getList() {
      this.loading = true
      this.queryParams.salesLedgerId = this.filterData.salesLedgerId
      this.$axios.get('/metricStatistics/customerTransactionsShipments', { params: this.queryParams })
        .then(res => {
          this.list = res.data.records
          this.total = res.data.total
        })
        .finally(() => {
          this.loading = false
        })
    },
    getContractList() {
      this.$axios.get('/metricStatistics/customewTransactionsDetails', {
        params: { customerId: this.customerId, pageNum: 1, pageSize: 100 }
      }).then(res => {
        this.contractList = res.data.records
      })
    },
    formatMoney(value) {
      if (!value) return '0.00'
      return Number(value).toFixed(2)
    }
  }
}
</script>
```
---
## æ³¨æ„äº‹é¡¹
1. **路由配置**:需在路由中新增客户往来详情页路由 `/sales/customerTransactions/detail`
2. **组件拆分**:产品明细和发货明细建议拆分为独立组件,便于复用和维护
3. **筛选联动**:产品明细和发货明细支持按合同筛选,合同列表从原有接口获取
4. **数据格式**:金额字段需统一使用 `formatMoney` æ–¹æ³•格式化显示
5. **进度条显示**:产品明细中的发货进度使用 `el-progress` ç»„件直观展示
6. **状态标识**:应收金额大于0时使用红色标识,已收款使用绿色标识
7. **审批状态**:发货明细中的审批状态使用 `el-tag` å±•示,已审为绿色,待审为黄色
---
## æ•°æ®å¯¹æ¯”
### ä¼˜åŒ–前 vs ä¼˜åŒ–后
| ç»´åº¦ | ä¼˜åŒ–前 | ä¼˜åŒ–后 |
|------|--------|--------|
| å®¢æˆ·å¾€æ¥ | åªæœ‰åˆåŒé‡‘额、收款、应收 | æ–°å¢žåˆåŒæ•°ã€äº§å“æ•°ã€å‘货率、收款率等12项指标 |
| æ˜Žç»†ç»´åº¦ | ä»…合同明细 | æ–°å¢žäº§å“æ˜Žç»†ã€å‘货明细 |
| ç­›é€‰èƒ½åŠ› | ä»…按客户名筛选 | æ”¯æŒæŒ‰åˆåŒç­›é€‰äº§å“/发货明细 |
| æ•°æ®è¿½æº¯ | æ— æ³•追溯具体发货 | å¯è¿½æº¯æ¯æ¡å‘货记录的收款情况 |
| è¿›åº¦å±•示 | æ—  | å‘货进度条直观展示 |
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
@@ -8,6 +8,9 @@
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import com.ruoyi.sales.vo.CustomerTransactionsProductVo;
import com.ruoyi.sales.vo.CustomerTransactionsShipmentVo;
import com.ruoyi.sales.vo.CustomerTransactionsSummaryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -77,4 +80,21 @@
    IPage<CustomerTransactionsVo> customewTransactions(Page page, @Param("customerName") String customerName);
    IPage<CustomerTransactionsDetailsVo> customewTransactionsDetails(Page page, @Param("customerId") Long customerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»
     */
    CustomerTransactionsSummaryVo getCustomerTransactionsSummary(@Param("customerId") Long customerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†
     */
    IPage<CustomerTransactionsProductVo> getCustomerTransactionsProducts(Page page,
            @Param("customerId") Long customerId, @Param("salesLedgerId") Long salesLedgerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥å‘货明细
     */
    IPage<CustomerTransactionsShipmentVo> getCustomerTransactionsShipments(Page page,
            @Param("customerId") Long customerId, @Param("salesLedgerId") Long salesLedgerId);
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -9,6 +9,9 @@
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import com.ruoyi.sales.vo.CustomerTransactionsProductVo;
import com.ruoyi.sales.vo.CustomerTransactionsShipmentVo;
import com.ruoyi.sales.vo.CustomerTransactionsSummaryVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -111,4 +114,29 @@
     * @return
     */
    IPage<CustomerTransactionsDetailsVo> customewTransactionsDetails(Page page, Long customerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»ï¼ˆä¼˜åŒ–版)
     * @param customerId å®¢æˆ·ID
     * @return ç»Ÿè®¡æ±‡æ€»æ•°æ®
     */
    CustomerTransactionsSummaryVo getCustomerTransactionsSummary(Long customerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†
     * @param page åˆ†é¡µå‚æ•°
     * @param customerId å®¢æˆ·ID
     * @param salesLedgerId é”€å”®å°è´¦ID(可选)
     * @return äº§å“æ˜Žç»†åˆ†é¡µæ•°æ®
     */
    IPage<CustomerTransactionsProductVo> getCustomerTransactionsProducts(Page page, Long customerId, Long salesLedgerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥å‘货明细
     * @param page åˆ†é¡µå‚æ•°
     * @param customerId å®¢æˆ·ID
     * @param salesLedgerId é”€å”®å°è´¦ID(可选)
     * @return å‘货明细分页数据
     */
    IPage<CustomerTransactionsShipmentVo> getCustomerTransactionsShipments(Page page, Long customerId, Long salesLedgerId);
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -25,6 +25,9 @@
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import com.ruoyi.sales.vo.CustomerTransactionsProductVo;
import com.ruoyi.sales.vo.CustomerTransactionsShipmentVo;
import com.ruoyi.sales.vo.CustomerTransactionsSummaryVo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@@ -391,6 +394,21 @@
        return customerMapper.customewTransactionsDetails(page, customerId);
    }
    @Override
    public CustomerTransactionsSummaryVo getCustomerTransactionsSummary(Long customerId) {
        return customerMapper.getCustomerTransactionsSummary(customerId);
    }
    @Override
    public IPage<CustomerTransactionsProductVo> getCustomerTransactionsProducts(Page page, Long customerId, Long salesLedgerId) {
        return customerMapper.getCustomerTransactionsProducts(page, customerId, salesLedgerId);
    }
    @Override
    public IPage<CustomerTransactionsShipmentVo> getCustomerTransactionsShipments(Page page, Long customerId, Long salesLedgerId) {
        return customerMapper.getCustomerTransactionsShipments(page, customerId, salesLedgerId);
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -38,8 +38,6 @@
    @Excel(name = "采购合同号")
    private String purchaseContractNumber;
    /**
     * ä¾›åº”商名称id
     */
@@ -143,6 +141,9 @@
    private List<SalesLedgerProduct> productData;
    @Schema(description = "批量处理采购台账ID列表")
    private List<Long> ids;
    private List<String> tempFileIds;
    private List<CommonFile> SalesLedgerFiles;
src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java
@@ -55,4 +55,25 @@
        return R.ok(customerService.customewTransactionsDetails(page,customerId));
    }
    @GetMapping("/customerTransactionsSummary")
    @Log(title = "客户往来统计汇总", businessType = BusinessType.OTHER)
    @Operation(summary = "客户往来统计汇总")
    public R customerTransactionsSummary(Long customerId) {
        return R.ok(customerService.getCustomerTransactionsSummary(customerId));
    }
    @GetMapping("/customerTransactionsProducts")
    @Log(title = "客户往来产品明细", businessType = BusinessType.OTHER)
    @Operation(summary = "客户往来产品明细")
    public R customerTransactionsProducts(Page page, Long customerId, Long salesLedgerId) {
        return R.ok(customerService.getCustomerTransactionsProducts(page, customerId, salesLedgerId));
    }
    @GetMapping("/customerTransactionsShipments")
    @Log(title = "客户往来发货明细", businessType = BusinessType.OTHER)
    @Operation(summary = "客户往来发货明细")
    public R customerTransactionsShipments(Page page, Long customerId, Long salesLedgerId) {
        return R.ok(customerService.getCustomerTransactionsShipments(page, customerId, salesLedgerId));
    }
}
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
@@ -28,6 +28,10 @@
    @Excel(name = "客户名称")
    private String customerName;
    @TableField(exist = false)
    @Schema(description = "客户ID")
    private Long customerId;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
@@ -24,6 +24,9 @@
    @Schema(description = "合同金额")
    private BigDecimal contractAmount;
    @Schema(description = "产品名称列表(逗号分隔)")
    private String productNames;
    @Schema(description = "收款金额")
    private BigDecimal receiptPaymentAmount;
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsProductVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.sales.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
 * å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»†VO
 */
@Data
@Schema(name = "CustomerTransactionsProductVo", description = "客户往来产品明细")
public class CustomerTransactionsProductVo {
    @Schema(description = "销售台账ID")
    private Long salesLedgerId;
    @Schema(description = "销售合同号")
    private String salesContractNo;
    @Schema(description = "产品ID")
    private Long productId;
    @Schema(description = "产品名称")
    private String productName;
    @Schema(description = "规格型号")
    private String model;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "合同数量")
    private BigDecimal contractQuantity;
    @Schema(description = "合同单价(含税)")
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "合同金额")
    private BigDecimal contractAmount;
    @Schema(description = "已发货数量")
    private BigDecimal shippedQuantity;
    @Schema(description = "已发货金额")
    private BigDecimal shippedAmount;
    @Schema(description = "已收款金额")
    private BigDecimal receivedAmount;
    @Schema(description = "应收金额")
    private BigDecimal receivableAmount;
}
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsShipmentVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.ruoyi.sales.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å®¢æˆ·å¾€æ¥å‘货明细VO
 */
@Data
@Schema(name = "CustomerTransactionsShipmentVo", description = "客户往来发货明细")
public class CustomerTransactionsShipmentVo {
    @Schema(description = "销售台账ID")
    private Long salesLedgerId;
    @Schema(description = "销售合同号")
    private String salesContractNo;
    @Schema(description = "发货单ID")
    private Long shippingId;
    @Schema(description = "发货单号")
    private String shippingNo;
    @Schema(description = "产品名称")
    private String productName;
    @Schema(description = "规格型号")
    private String model;
    @Schema(description = "发货数量")
    private BigDecimal shippingQuantity;
    @Schema(description = "发货金额(含税)")
    private BigDecimal shippingAmount;
    @Schema(description = "出库批号")
    private String batchNo;
    @Schema(description = "发货日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate shippingDate;
    @Schema(description = "审批状态(0待审/1已审)")
    private Integer approvalStatus;
    @Schema(description = "已收款金额")
    private BigDecimal receivedAmount;
    @Schema(description = "应收金额")
    private BigDecimal receivableAmount;
}
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsSummaryVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.sales.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
/**
 * å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€»VO(优化版)
 */
@Data
@Schema(name = "CustomerTransactionsSummaryVo", description = "客户往来统计汇总")
public class CustomerTransactionsSummaryVo {
    @Schema(description = "客户ID")
    private Long customerId;
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "合同总金额")
    private BigDecimal contractAmounts;
    @Schema(description = "合同数量")
    private Integer contractCount;
    @Schema(description = "产品种类数")
    private Integer productCount;
    @Schema(description = "发货总金额")
    private BigDecimal shippedAmounts;
    @Schema(description = "发货总数量")
    private BigDecimal shippedQuantity;
    @Schema(description = "收款金额")
    private BigDecimal receivedAmounts;
    @Schema(description = "应收金额")
    private BigDecimal receivableAmounts;
    @Schema(description = "退货金额")
    private BigDecimal returnAmounts;
    @Schema(description = "未发货金额")
    private BigDecimal unshippedAmounts;
    @Schema(description = "收款率(%)")
    private BigDecimal receivedRate;
    @Schema(description = "发货率(%)")
    private BigDecimal shippedRate;
}
src/main/resources/mapper/basic/CustomerMapper.xml
@@ -151,6 +151,7 @@
               sl.sales_contract_no,
               sl.execution_date,
               sl.contract_amount,
               IFNULL(T4.productNames, '') AS productNames,
               IFNULL(T1.receiptPaymentAmount, 0) AS receiptPaymentAmount,
               IFNULL(T2.outboundAmount, 0) - IFNULL(T3.returnAmount, 0) AS receiptableAmount
        from sales_ledger sl
@@ -188,6 +189,167 @@
            where rm.status=1
            group by sl.id
        )T3 on T3.id = sl.id
        left join (
            select slp.sales_ledger_id, GROUP_CONCAT(p.product_name) as productNames
            from sales_ledger_product slp
            left join product p on slp.product_id = p.id
            where slp.type = 1
            group by slp.sales_ledger_id
        ) T4 on T4.sales_ledger_id = sl.id
        where sl.customer_id = #{customerId}
        order by sl.id desc
    </select>
    <!-- å®¢æˆ·å¾€æ¥ç»Ÿè®¡æ±‡æ€» -->
    <select id="getCustomerTransactionsSummary" resultType="com.ruoyi.sales.vo.CustomerTransactionsSummaryVo">
        SELECT
            c.id AS customerId,
            c.customer_name AS customerName,
            IFNULL(T1.contractAmounts, 0) AS contractAmounts,
            IFNULL(T1.contractCount, 0) AS contractCount,
            IFNULL(T5.productCount, 0) AS productCount,
            IFNULL(T2.shippedAmounts, 0) AS shippedAmounts,
            IFNULL(T2.shippedQuantity, 0) AS shippedQuantity,
            IFNULL(T3.receivedAmounts, 0) AS receivedAmounts,
            IFNULL(T2.shippedAmounts, 0) - IFNULL(T3.receivedAmounts, 0) AS receivableAmounts,
            IFNULL(T4.returnAmounts, 0) AS returnAmounts,
            IFNULL(T1.contractAmounts, 0) - IFNULL(T2.shippedAmounts, 0) AS unshippedAmounts,
            CASE WHEN IFNULL(T2.shippedAmounts, 0) > 0
                 THEN ROUND(IFNULL(T3.receivedAmounts, 0) / T2.shippedAmounts * 100, 2)
                 ELSE 0 END AS receivedRate,
            CASE WHEN IFNULL(T1.contractAmounts, 0) > 0
                 THEN ROUND(IFNULL(T2.shippedAmounts, 0) / T1.contractAmounts * 100, 2)
                 ELSE 0 END AS shippedRate
        FROM customer c
        LEFT JOIN (
            SELECT customer_id, SUM(contract_amount) AS contractAmounts, COUNT(*) AS contractCount
            FROM sales_ledger WHERE customer_id = #{customerId}
            GROUP BY customer_id
        ) T1 ON T1.customer_id = c.id
        LEFT JOIN (
            SELECT
                sl.customer_id,
                SUM(sor.stock_out_num) AS shippedQuantity,
                SUM(sor.stock_out_num * slp.tax_inclusive_unit_price) AS shippedAmounts
            FROM stock_out_record sor
            LEFT JOIN shipping_info s ON sor.record_id = s.id
            LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
            LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id
            WHERE sor.record_type = '13' AND sor.approval_status = 1 AND slp.type = 1
              AND sl.customer_id = #{customerId}
            GROUP BY sl.customer_id
        ) T2 ON T2.customer_id = c.id
        LEFT JOIN (
            SELECT customer_id, SUM(collection_amount) AS receivedAmounts
            FROM account_sales_collection WHERE customer_id = #{customerId}
            GROUP BY customer_id
        ) T3 ON T3.customer_id = c.id
        LEFT JOIN (
            SELECT
                sl.customer_id,
                SUM(rm.refund_amount) AS returnAmounts
            FROM return_management rm
            LEFT JOIN shipping_info si ON rm.shipping_id = si.id
            LEFT JOIN sales_ledger sl ON si.sales_ledger_id = sl.id
            WHERE rm.status = 1 AND sl.customer_id = #{customerId}
            GROUP BY sl.customer_id
        ) T4 ON T4.customer_id = c.id
        LEFT JOIN (
            SELECT sl.customer_id, COUNT(DISTINCT pm.product_id) AS productCount
            FROM sales_ledger sl
            LEFT JOIN sales_ledger_product slp ON sl.id = slp.sales_ledger_id
            LEFT JOIN product_model pm ON slp.product_id = pm.id
            WHERE sl.customer_id = #{customerId} AND slp.type = 1
            GROUP BY sl.customer_id
        ) T5 ON T5.customer_id = c.id
        WHERE c.id = #{customerId}
    </select>
    <!-- å®¢æˆ·å¾€æ¥äº§å“æ˜Žç»† -->
    <select id="getCustomerTransactionsProducts" resultType="com.ruoyi.sales.vo.CustomerTransactionsProductVo">
        SELECT
            sl.id AS salesLedgerId,
            sl.sales_contract_no AS salesContractNo,
            slp.product_id AS productId,
            IFNULL(p.product_name, slp.product_category) AS productName,
            IFNULL(pm.model, slp.specification_model) AS model,
            IFNULL(pm.unit, slp.unit) AS unit,
            slp.quantity AS contractQuantity,
            slp.tax_inclusive_unit_price AS taxInclusiveUnitPrice,
            slp.quantity * slp.tax_inclusive_unit_price AS contractAmount,
            IFNULL(T1.shippedQuantity, 0) AS shippedQuantity,
            IFNULL(T1.shippedAmount, 0) AS shippedAmount,
            IFNULL(T2.receivedAmount, 0) AS receivedAmount,
            IFNULL(T1.shippedAmount, 0) - IFNULL(T2.receivedAmount, 0) AS receivableAmount
        FROM sales_ledger sl
        LEFT JOIN sales_ledger_product slp ON sl.id = slp.sales_ledger_id
        LEFT JOIN product_model pm ON slp.product_id = pm.id
        LEFT JOIN product p ON pm.product_id = p.id
        LEFT JOIN (
            SELECT
                s.sales_ledger_id,
                s.sales_ledger_product_id,
                SUM(sor.stock_out_num) AS shippedQuantity,
                SUM(sor.stock_out_num * slp2.tax_inclusive_unit_price) AS shippedAmount
            FROM stock_out_record sor
            LEFT JOIN shipping_info s ON sor.record_id = s.id
            LEFT JOIN sales_ledger_product slp2 ON s.sales_ledger_product_id = slp2.id
            WHERE sor.record_type = '13' AND sor.approval_status = 1
            GROUP BY s.sales_ledger_id, s.sales_ledger_product_id
        ) T1 ON T1.sales_ledger_id = sl.id AND T1.sales_ledger_product_id = slp.id
        LEFT JOIN (
            SELECT
                s.sales_ledger_id,
                s.sales_ledger_product_id,
                SUM(ascc.collection_amount) AS receivedAmount
            FROM account_sales_collection ascc
            LEFT JOIN stock_out_record sor ON FIND_IN_SET(sor.id, ascc.stock_out_record_ids) > 0
            LEFT JOIN shipping_info s ON sor.record_id = s.id
            WHERE sor.record_type = '13' AND sor.approval_status = 1
            GROUP BY s.sales_ledger_id, s.sales_ledger_product_id
        ) T2 ON T2.sales_ledger_id = sl.id AND T2.sales_ledger_product_id = slp.id
        WHERE sl.customer_id = #{customerId} AND slp.type = 1
        <if test="salesLedgerId != null">
            AND sl.id = #{salesLedgerId}
        </if>
        ORDER BY sl.id DESC, slp.id DESC
    </select>
    <!-- å®¢æˆ·å¾€æ¥å‘货明细 -->
    <select id="getCustomerTransactionsShipments" resultType="com.ruoyi.sales.vo.CustomerTransactionsShipmentVo">
        SELECT
            sl.id AS salesLedgerId,
            sl.sales_contract_no AS salesContractNo,
            s.id AS shippingId,
            s.shipping_no AS shippingNo,
            IFNULL(p.product_name, slp.product_category) AS productName,
            IFNULL(pm.model, slp.specification_model) AS model,
            sor.stock_out_num AS shippingQuantity,
            sor.stock_out_num * slp.tax_inclusive_unit_price AS shippingAmount,
            sor.batch_no AS batchNo,
            s.shipping_date AS shippingDate,
            sor.approval_status AS approvalStatus,
            IFNULL(T1.receivedAmount, 0) AS receivedAmount,
            sor.stock_out_num * slp.tax_inclusive_unit_price - IFNULL(T1.receivedAmount, 0) AS receivableAmount
        FROM stock_out_record sor
        LEFT JOIN shipping_info s ON sor.record_id = s.id
        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
        LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id
        LEFT JOIN product_model pm ON slp.product_id = pm.id
        LEFT JOIN product p ON pm.product_id = p.id
        LEFT JOIN (
            SELECT
                sor2.id AS stock_out_record_id,
                SUM(ascc.collection_amount) AS receivedAmount
            FROM account_sales_collection ascc
            LEFT JOIN stock_out_record sor2 ON FIND_IN_SET(sor2.id, ascc.stock_out_record_ids) > 0
            WHERE ascc.customer_id = #{customerId}
            GROUP BY sor2.id
        ) T1 ON T1.stock_out_record_id = sor.id
        WHERE sor.record_type = '13' AND sl.customer_id = #{customerId}
        <if test="salesLedgerId != null">
            AND sl.id = #{salesLedgerId}
        </if>
        ORDER BY sor.id DESC
    </select>
</mapper>
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -81,13 +81,13 @@
                                   sir.stock_in_num
                            FROM stock_in_record sir
                            INNER JOIN quality_inspect qi
                                ON TRIM(sir.record_type) = '10'
                                ON TRIM(sir.record_type) IN ('6', '10')
                                AND sir.record_id = qi.id
                            INNER JOIN sales_ledger_product slp
                                ON slp.type = 2
                                AND slp.sales_ledger_id = qi.purchase_ledger_id
                                AND slp.product_model_id = qi.product_model_id
                            WHERE sir.approval_status = 1
                            WHERE qi.inspect_state = 1
                        ) rel
                        GROUP BY rel.sales_ledger_product_id
                    ) approved_qty ON approved_qty.sales_ledger_product_id = slp.id
@@ -120,6 +120,12 @@
                <if test="c.supplierId != null">
                    AND pl.supplier_id = #{c.supplierId}
                </if>
                <if test="c.ids != null and c.ids.size() > 0">
                    AND pl.id IN
                    <foreach collection="c.ids" item="id" open="(" separator="," close=")">
                        #{id}
                    </foreach>
                </if>
                <if test="c.approvalStatus != null">
                    AND pl.approval_status = #{c.approvalStatus}
                </if>
src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -48,6 +48,9 @@
        <if test="req.expressNumber != null and req.expressNumber != ''">
            AND s.express_number LIKE CONCAT('%',#{req.expressNumber},'%')
        </if>
        <if test="req.customerId != null">
            AND sl.customer_id = #{req.customerId}
        </if>
        order by create_time DESC
    </select>
    <select id="listAll" resultType="com.ruoyi.sales.pojo.ShippingInfo">