From fb3a5d1f1c77848dfe5fd47d0cb4132ad25c1564 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 24 六月 2026 13:09:12 +0800
Subject: [PATCH] 客户往来,供应商往来优化

---
 src/main/resources/mapper/basic/CustomerMapper.xml                       |  162 +++++++
 src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java                 |   20 
 src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java              |    5 
 src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml              |   10 
 src/main/java/com/ruoyi/basic/service/ICustomerService.java              |   28 +
 src/main/resources/mapper/sales/ShippingInfoMapper.xml                   |    3 
 docs/20260618_customer_transactions_optimization.md                      |  839 ++++++++++++++++++++++++++++++++++++++
 src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java                     |    4 
 src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java      |   18 
 src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java |   21 
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java      |    3 
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsShipmentVo.java     |   56 ++
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsSummaryVo.java      |   53 ++
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsProductVo.java      |   53 ++
 14 files changed, 1,271 insertions(+), 4 deletions(-)

diff --git a/docs/20260618_customer_transactions_optimization.md b/docs/20260618_customer_transactions_optimization.md
new file mode 100644
index 0000000..ebb7fa2
--- /dev/null
+++ b/docs/20260618_customer_transactions_optimization.md
@@ -0,0 +1,839 @@
+# 瀹㈡埛寰�鏉ュ缁村害鏄庣粏鍔熻兘鍓嶇鑱旇皟鏂囨。
+
+> 浼樺寲瀹㈡埛寰�鏉ュ姛鑳斤紝鏂板澶氱淮搴︽槑缁嗘煡璇㈡帴鍙o紝鏀寔浜у搧鏄庣粏鍜屽彂璐ф槑缁嗙淮搴�
+
+## 娑夊強椤甸潰
+
+- 钀ラ攢绠$悊 / 瀹㈡埛寰�鏉� - 瀹㈡埛寰�鏉ュ垪琛ㄩ〉
+- 钀ラ攢绠$悊 / 瀹㈡埛寰�鏉� - 瀹㈡埛寰�鏉ヨ鎯呴〉锛堟柊澧烇級
+- 钀ラ攢绠$悊 / 瀹㈡埛寰�鏉� - 浜у搧鏄庣粏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 | 閿�鍞彴璐D |
+| 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 | 鍚� | 閿�鍞彴璐D锛堝彲閫夛紝鐢ㄤ簬绛涢�夋煇鍚堝悓锛� |
+| pageNum | Long | 鍚� | 椤电爜锛岄粯璁�1 |
+| pageSize | Long | 鍚� | 姣忛〉鏉℃暟锛岄粯璁�10 |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| salesLedgerId | Long | 閿�鍞彴璐D |
+| 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 | 鍚� | 閿�鍞彴璐D锛堝彲閫夛紝鐢ㄤ簬绛涢�夋煇鍚堝悓锛� |
+| pageNum | Long | 鍚� | 椤电爜锛岄粯璁�1 |
+| pageSize | Long | 鍚� | 姣忛〉鏉℃暟锛岄粯璁�10 |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| salesLedgerId | Long | 閿�鍞彴璐D |
+| salesContractNo | String | 閿�鍞悎鍚屽彿 |
+| shippingId | Long | 鍙戣揣鍗旾D |
+| 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. **绛涢�夎仈鍔�**锛氫骇鍝佹槑缁嗗拰鍙戣揣鏄庣粏鏀寔鎸夊悎鍚岀瓫閫夛紝鍚堝悓鍒楄〃浠庡師鏈夋帴鍙h幏鍙�
+4. **鏁版嵁鏍煎紡**锛氶噾棰濆瓧娈甸渶缁熶竴浣跨敤 `formatMoney` 鏂规硶鏍煎紡鍖栨樉绀�
+5. **杩涘害鏉℃樉绀�**锛氫骇鍝佹槑缁嗕腑鐨勫彂璐ц繘搴︿娇鐢� `el-progress` 缁勪欢鐩磋灞曠ず
+6. **鐘舵�佹爣璇�**锛氬簲鏀堕噾棰濆ぇ浜�0鏃朵娇鐢ㄧ孩鑹叉爣璇嗭紝宸叉敹娆句娇鐢ㄧ豢鑹叉爣璇�
+7. **瀹℃壒鐘舵��**锛氬彂璐ф槑缁嗕腑鐨勫鎵圭姸鎬佷娇鐢� `el-tag` 灞曠ず锛屽凡瀹′负缁胯壊锛屽緟瀹′负榛勮壊
+
+---
+
+## 鏁版嵁瀵规瘮
+
+### 浼樺寲鍓� vs 浼樺寲鍚�
+
+| 缁村害 | 浼樺寲鍓� | 浼樺寲鍚� |
+|------|--------|--------|
+| 瀹㈡埛寰�鏉� | 鍙湁鍚堝悓閲戦銆佹敹娆俱�佸簲鏀� | 鏂板鍚堝悓鏁般�佷骇鍝佹暟銆佸彂璐х巼銆佹敹娆剧巼绛�12椤规寚鏍� |
+| 鏄庣粏缁村害 | 浠呭悎鍚屾槑缁� | 鏂板浜у搧鏄庣粏銆佸彂璐ф槑缁� |
+| 绛涢�夎兘鍔� | 浠呮寜瀹㈡埛鍚嶇瓫閫� | 鏀寔鎸夊悎鍚岀瓫閫変骇鍝�/鍙戣揣鏄庣粏 |
+| 鏁版嵁杩芥函 | 鏃犳硶杩芥函鍏蜂綋鍙戣揣 | 鍙拷婧瘡鏉″彂璐ц褰曠殑鏀舵鎯呭喌 |
+| 杩涘害灞曠ず | 鏃� | 鍙戣揣杩涘害鏉$洿瑙傚睍绀� |
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java b/src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
index f871b91..1bc9f0f 100644
--- a/src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
+++ b/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);
 }
diff --git a/src/main/java/com/ruoyi/basic/service/ICustomerService.java b/src/main/java/com/ruoyi/basic/service/ICustomerService.java
index 27e4739..53ab6d6 100644
--- a/src/main/java/com/ruoyi/basic/service/ICustomerService.java
+++ b/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 閿�鍞彴璐D锛堝彲閫夛級
+     * @return 浜у搧鏄庣粏鍒嗛〉鏁版嵁
+     */
+    IPage<CustomerTransactionsProductVo> getCustomerTransactionsProducts(Page page, Long customerId, Long salesLedgerId);
+
+    /**
+     * 鏌ヨ瀹㈡埛寰�鏉ュ彂璐ф槑缁�
+     * @param page 鍒嗛〉鍙傛暟
+     * @param customerId 瀹㈡埛ID
+     * @param salesLedgerId 閿�鍞彴璐D锛堝彲閫夛級
+     * @return 鍙戣揣鏄庣粏鍒嗛〉鏁版嵁
+     */
+    IPage<CustomerTransactionsShipmentVo> getCustomerTransactionsShipments(Page page, Long customerId, Long salesLedgerId);
 }
diff --git a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
index eef85aa..edf927c 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
+++ b/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);
+    }
+
     /**
      * 涓嬪垝绾垮懡鍚嶈浆椹煎嘲鍛藉悕
      */
diff --git a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
index 322eabf..c578b78 100644
--- a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -38,8 +38,6 @@
     @Excel(name = "閲囪喘鍚堝悓鍙�")
     private String purchaseContractNumber;
 
-
-
     /**
      * 渚涘簲鍟嗗悕绉癷d
      */
@@ -143,6 +141,9 @@
 
     private List<SalesLedgerProduct> productData;
 
+    @Schema(description = "鎵归噺澶勭悊閲囪喘鍙拌处ID鍒楄〃")
+    private List<Long> ids;
+
     private List<String> tempFileIds;
 
     private List<CommonFile> SalesLedgerFiles;
diff --git a/src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java b/src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java
index 577508a..f6fe494 100644
--- a/src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java
+++ b/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));
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java b/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
index ed9a268..e564770 100644
--- a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
+++ b/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;
 
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
index 992860e..3771647 100644
--- a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
+++ b/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;
 
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsProductVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsProductVo.java
new file mode 100644
index 0000000..9b0452b
--- /dev/null
+++ b/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;
+
+/**
+ * 瀹㈡埛寰�鏉ヤ骇鍝佹槑缁哣O
+ */
+@Data
+@Schema(name = "CustomerTransactionsProductVo", description = "瀹㈡埛寰�鏉ヤ骇鍝佹槑缁�")
+public class CustomerTransactionsProductVo {
+
+    @Schema(description = "閿�鍞彴璐D")
+    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;
+}
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsShipmentVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsShipmentVo.java
new file mode 100644
index 0000000..9ce8033
--- /dev/null
+++ b/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;
+
+/**
+ * 瀹㈡埛寰�鏉ュ彂璐ф槑缁哣O
+ */
+@Data
+@Schema(name = "CustomerTransactionsShipmentVo", description = "瀹㈡埛寰�鏉ュ彂璐ф槑缁�")
+public class CustomerTransactionsShipmentVo {
+
+    @Schema(description = "閿�鍞彴璐D")
+    private Long salesLedgerId;
+
+    @Schema(description = "閿�鍞悎鍚屽彿")
+    private String salesContractNo;
+
+    @Schema(description = "鍙戣揣鍗旾D")
+    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;
+}
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsSummaryVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsSummaryVo.java
new file mode 100644
index 0000000..f172f67
--- /dev/null
+++ b/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;
+
+/**
+ * 瀹㈡埛寰�鏉ョ粺璁℃眹鎬籚O锛堜紭鍖栫増锛�
+ */
+@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;
+}
diff --git a/src/main/resources/mapper/basic/CustomerMapper.xml b/src/main/resources/mapper/basic/CustomerMapper.xml
index 8117c66..fe3309b 100644
--- a/src/main/resources/mapper/basic/CustomerMapper.xml
+++ b/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>
diff --git a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
index a818160..bac1534 100644
--- a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
+++ b/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>
diff --git a/src/main/resources/mapper/sales/ShippingInfoMapper.xml b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
index 762c2d4..9d387a5 100644
--- a/src/main/resources/mapper/sales/ShippingInfoMapper.xml
+++ b/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">

--
Gitblit v1.9.3