5 小时以前 c653ef74b8b332a530f8d1b54a316d382d0647d1
1.客户,销售,供应商,采购增加总合同号
已添加20个文件
已修改4个文件
1805 ■■■■■ 文件已修改
docs/customer_contract_record.md 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/customer_contract_record.sql 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/purchase_master_contract_linkage.md 345 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sales_master_contract_linkage.md 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sales_quotation_add_master_contract_no.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/supplier_contract_record.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerContractRecordController.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierContractRecordController.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerContractRecordDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/SupplierContractRecordDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerContractRecordMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/SupplierContractRecordMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerContractRecord.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/SupplierContractRecord.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerContractRecordService.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/SupplierContractRecordService.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerContractRecordServiceImpl.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/SupplierContractRecordServiceImpl.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerContractRecordMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/SupplierContractRecordMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityInspectMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/customer_contract_record.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,264 @@
# å®¢æˆ·æ¡£æ¡ˆç§æµ·-合同记录
## æ¶‰åŠé¡µé¢
- å®¢æˆ·æ¡£æ¡ˆï¼ˆç§æµ·ï¼‰è¯¦æƒ…页
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /basic/customer-contract/list/{customerId} | æŸ¥è¯¢åˆåŒè®°å½•列表 |
| GET | /basic/customer-contract/{id} | èŽ·å–åˆåŒè®°å½•è¯¦æƒ… |
| POST | /basic/customer-contract | æ–°å¢žåˆåŒè®°å½• |
| PUT | /basic/customer-contract | ä¿®æ”¹åˆåŒè®°å½• |
| DELETE | /basic/customer-contract/{id} | åˆ é™¤åˆåŒè®°å½• |
**注意:附件使用系统通用接口 `/basic/storage-attachment` ç®¡ç†**
### è¯·æ±‚参数
**新增/修改合同记录:**
```json
{
    "id": null,  // ä¿®æ”¹æ—¶å¿…ä¼ 
    "customerId": 1,  // å®¢æˆ·æ¡£æ¡ˆID(必填)
    "masterContractNo": "HT202606240001",  // æ€»åˆåŒå·ï¼ˆå¿…填)
    "entryDate": "2026-06-24 10:30:00",  // å½•入日期(必填)
    "entryPerson": "张三",  // å½•入人姓名
    "entryPersonId": 1,  // å½•入人ID
    "remark": "备注信息",  // å¤‡æ³¨
    "storageBlobDTOs": [  // é™„件列表
        { "id": 1 },
        { "id": 2 }
    ]
}
```
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | å¦ | ä¿®æ”¹æ—¶å¿…ä¼  |
| customerId | Long | æ˜¯ | å®¢æˆ·æ¡£æ¡ˆID |
| masterContractNo | String | æ˜¯ | æ€»åˆåŒå· |
| entryDate | String | æ˜¯ | å½•入日期(yyyy-MM-dd HH:mm:ss) |
| entryPerson | String | å¦ | å½•入人姓名 |
| entryPersonId | Long | å¦ | å½•入人ID |
| remark | String | å¦ | å¤‡æ³¨ |
| storageBlobDTOs | List | å¦ | é™„ä»¶ID列表 |
**响应:**
```json
{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "id": 1,
        "customerId": 1,
        "masterContractNo": "HT202606240001",
        "entryDate": "2026-06-24 10:30:00",
        "entryPerson": "张三",
        "entryPersonId": 1,
        "remark": "备注信息",
        "fileList": [
            {
                "id": 1,
                "name": "合同扫描件.pdf",
                "previewURL": "xxx",
                "downloadURL": "xxx",
                "byteSize": 102400,
                "contentType": "application/pdf"
            }
        ]
    }
}
```
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. åœ¨å®¢æˆ·æ¡£æ¡ˆï¼ˆç§æµ·ï¼‰è¯¦æƒ…页添加合同记录Tab
```html
<el-tabs v-model="activeTab">
  <!-- å…¶ä»–Tab -->
  <el-tab-pane label="合同记录" name="contract">
    <el-button type="primary" size="small" @click="handleAddContract">新增合同记录</el-button>
    <el-table :data="contractList" style="width: 100%; margin-top: 15px">
      <el-table-column prop="masterContractNo" label="总合同号" />
      <el-table-column prop="entryDate" label="录入日期" width="180" />
      <el-table-column prop="entryPerson" label="录入人" width="120" />
      <el-table-column prop="remark" label="备注" show-overflow-tooltip />
      <el-table-column label="附件" width="100">
        <template #default="{ row }">
          <el-link type="primary" v-if="row.fileList && row.fileList.length" @click="handleViewFiles(row)">
            æŸ¥çœ‹({{ row.fileList.length }})
          </el-link>
          <span v-else>-</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150">
        <template #default="{ row }">
          <el-button type="text" size="small" @click="handleEditContract(row)">编辑</el-button>
          <el-button type="text" size="small" style="color: #f56c6c" @click="handleDeleteContract(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-tab-pane>
</el-tabs>
```
### 2. æ–°å¢ž/编辑合同记录弹窗(使用系统通用上传组件)
```html
<el-dialog :title="contractDialogTitle" v-model="contractDialogVisible" width="600px">
  <el-form :model="contractForm" :rules="contractRules" ref="contractFormRef" label-width="100px">
    <el-form-item label="总合同号" prop="masterContractNo">
      <el-input v-model="contractForm.masterContractNo" placeholder="请输入总合同号" />
    </el-form-item>
    <el-form-item label="录入日期" prop="entryDate">
      <el-date-picker
        v-model="contractForm.entryDate"
        type="datetime"
        placeholder="选择日期时间"
        value-format="yyyy-MM-dd HH:mm:ss"
        style="width: 100%"
      />
    </el-form-item>
    <el-form-item label="录入人" prop="entryPerson">
      <el-input v-model="contractForm.entryPerson" placeholder="请输入录入人" />
    </el-form-item>
    <el-form-item label="附件">
      <!-- ä½¿ç”¨ç³»ç»Ÿå°è£…的文件上传组件 -->
      <file-upload
        v-model="contractForm.storageBlobDTOs"
        :record-type="'customer_contract_record'"
        :record-id="contractForm.id"
      />
    </el-form-item>
    <el-form-item label="备注">
      <el-input v-model="contractForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
    </el-form-item>
  </el-form>
  <template #footer>
    <el-button @click="contractDialogVisible = false">取消</el-button>
    <el-button type="primary" @click="submitContractForm">确定</el-button>
  </template>
</el-dialog>
```
### 3. data æ•°æ®
```js
data() {
  return {
    // åˆåŒè®°å½•相关
    contractList: [],
    contractDialogVisible: false,
    contractDialogTitle: '',
    contractForm: {
      id: null,
      customerId: null,
      masterContractNo: '',
      entryDate: '',
      entryPerson: '',
      entryPersonId: null,
      remark: '',
      storageBlobDTOs: []  // é™„件列表,使用系统通用格式
    },
    contractRules: {
      masterContractNo: [{ required: true, message: '请输入总合同号', trigger: 'blur' }],
      entryDate: [{ required: true, message: '请选择录入日期', trigger: 'change' }]
    }
  }
}
```
### 4. æ–¹æ³•
```js
methods: {
  // èŽ·å–åˆåŒè®°å½•åˆ—è¡¨ï¼ˆå«é™„ä»¶ï¼‰
  async getContractList(customerId) {
    const res = await this.$http.get(`/basic/customer-contract/list/${customerId}`)
    if (res.code === 200) {
      this.contractList = res.data
    }
  },
  // æ–°å¢žåˆåŒè®°å½•
  handleAddContract() {
    this.contractDialogTitle = '新增合同记录'
    this.contractForm = {
      id: null,
      customerId: this.customerId,
      masterContractNo: '',
      entryDate: '',
      entryPerson: '',
      entryPersonId: null,
      remark: '',
      storageBlobDTOs: []
    }
    this.contractDialogVisible = true
  },
  // ç¼–辑合同记录
  async handleEditContract(row) {
    this.contractDialogTitle = '编辑合同记录'
    const res = await this.$http.get(`/basic/customer-contract/${row.id}`)
    if (res.code === 200) {
      this.contractForm = {
        ...res.data,
        storageBlobDTOs: res.data.fileList ? res.data.fileList.map(f => ({ id: f.id })) : []
      }
      this.contractDialogVisible = true
    }
  },
  // åˆ é™¤åˆåŒè®°å½•
  async handleDeleteContract(row) {
    await this.$confirm('确定删除该合同记录吗?', '提示', { type: 'warning' })
    const res = await this.$http.delete(`/basic/customer-contract/${row.id}`)
    if (res.code === 200) {
      this.$message.success('删除成功')
      this.getContractList(this.customerId)
    }
  },
  // æäº¤åˆåŒè®°å½•表单
  async submitContractForm() {
    await this.$refs.contractFormRef.validate()
    const res = await this.$http.post('/basic/customer-contract', this.contractForm)
    if (res.code === 200) {
      this.$message.success('操作成功')
      this.contractDialogVisible = false
      this.getContractList(this.customerId)
    }
  },
  // æŸ¥çœ‹é™„件列表
  handleViewFiles(row) {
    // å¼¹çª—展示附件列表,使用 row.fileList
  }
}
```
## åŽç«¯æ–‡ä»¶æ¸…单
| æ–‡ä»¶è·¯å¾„ | è¯´æ˜Ž |
|---------|------|
| `src/main/java/com/ruoyi/basic/pojo/CustomerContractRecord.java` | å®žä½“ç±» |
| `src/main/java/com/ruoyi/basic/dto/CustomerContractRecordDto.java` | DTO(含附件列表) |
| `src/main/java/com/ruoyi/basic/mapper/CustomerContractRecordMapper.java` | Mapper接口 |
| `src/main/resources/mapper/basic/CustomerContractRecordMapper.xml` | Mapper XML |
| `src/main/java/com/ruoyi/basic/service/CustomerContractRecordService.java` | Service接口 |
| `src/main/java/com/ruoyi/basic/service/impl/CustomerContractRecordServiceImpl.java` | Service实现 |
| `src/main/java/com/ruoyi/basic/controller/CustomerContractRecordController.java` | Controller |
| `src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java` | æ·»åŠ æžšä¸¾å€¼ `CUSTOMER_CONTRACT_RECORD` |
## æ³¨æ„äº‹é¡¹
- é™„件上传使用系统封装的 `StorageAttachment` æœåŠ¡ï¼Œé€šè¿‡ `record_type='customer_contract_record'` å’Œ `record_id` å…³è”
- å‰ç«¯å¯ä½¿ç”¨é¡¹ç›®å°è£…çš„ `file-upload` ç»„件,组件内部调用 `/basic/storage-attachment` æŽ¥å£
- åˆ é™¤åˆåŒè®°å½•时,后端会自动同步删除关联的附件
- æ–°å¢žåˆåŒè®°å½•成功后,返回的 `data.id` ä¸ºæ–°å¢žè®°å½•çš„ID
docs/customer_contract_record.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
-- å®¢æˆ·æ¡£æ¡ˆç§æµ·-合同记录子表
CREATE TABLE `customer_contract_record` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `customer_id` bigint NOT NULL COMMENT '关联客户档案ID(私海客户)',
    `master_contract_no` varchar(100) DEFAULT NULL COMMENT '总合同号',
    `entry_date` datetime DEFAULT NULL COMMENT '录入日期',
    `entry_person` varchar(50) DEFAULT NULL COMMENT '录入人',
    `entry_person_id` bigint DEFAULT NULL COMMENT '录入人ID',
    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
    `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
    `create_user` int DEFAULT NULL COMMENT '创建用户',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_user` int DEFAULT NULL COMMENT '修改用户',
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    KEY `idx_customer_id` (`customer_id`) COMMENT '客户ID索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='客户档案私海-合同记录表';
-- æ³¨æ„ï¼šé™„件使用系统统一的 storage_attachment è¡¨ç®¡ç†
-- éœ€è¦åœ¨ RecordTypeEnum æžšä¸¾ä¸­æ·»åŠ ï¼šCUSTOMER_CONTRACT_RECORD("customer_contract_record")
-- é™„件关联方式:record_type = 'customer_contract_record', record_id = åˆåŒè®°å½•ID
docs/purchase_master_contract_linkage.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,345 @@
# é‡‡è´­å°è´¦-总合同号联动选择及附件查看
## æ¶‰åŠé¡µé¢
- ä¾›åº”商管理详情页(新增合同记录Tab)
- é‡‡è´­å°è´¦åˆ—表页/新增编辑页
## æ–°å¢žAPI
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /basic/supplier-contract/list/{supplierId} | æŸ¥è¯¢ä¾›åº”商合同记录列表(供下拉选择) |
| GET | /basic/supplier-contract/{id} | èŽ·å–ä¾›åº”å•†åˆåŒè®°å½•è¯¦æƒ… |
| POST | /basic/supplier-contract | æ–°å¢žä¾›åº”商合同记录 |
| PUT | /basic/supplier-contract | ä¿®æ”¹ä¾›åº”商合同记录 |
| DELETE | /basic/supplier-contract/{id} | åˆ é™¤ä¾›åº”商合同记录 |
| GET | /basic/supplier-contract/files/byContractNo?masterContractNo=xxx | æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„ä»¶ |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. ä¾›åº”商管理详情页 - æ·»åŠ åˆåŒè®°å½•Tab
```html
<el-tabs v-model="activeTab">
  <!-- åŸºæœ¬ä¿¡æ¯Tab -->
  <el-tab-pane label="基本信息" name="basic">...</el-tab-pane>
  <!-- åˆåŒè®°å½•Tab -->
  <el-tab-pane label="合同记录" name="contract">
    <el-button type="primary" size="small" @click="handleAddContract">新增合同记录</el-button>
    <el-table :data="contractList" style="width: 100%; margin-top: 15px">
      <el-table-column prop="masterContractNo" label="总合同号" />
      <el-table-column prop="entryDate" label="录入日期" width="180" />
      <el-table-column prop="entryPerson" label="录入人" width="120" />
      <el-table-column prop="remark" label="备注" show-overflow-tooltip />
      <el-table-column label="附件" width="100">
        <template #default="{ row }">
          <el-link type="primary" v-if="row.fileList && row.fileList.length" @click="handleViewFiles(row)">
            æŸ¥çœ‹({{ row.fileList.length }})
          </el-link>
          <span v-else>-</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="150">
        <template #default="{ row }">
          <el-button type="text" size="small" @click="handleEditContract(row)">编辑</el-button>
          <el-button type="text" size="small" style="color: #f56c6c" @click="handleDeleteContract(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-tab-pane>
</el-tabs>
```
### 2. æ–°å¢ž/编辑合同记录弹窗
```html
<el-dialog :title="contractDialogTitle" v-model="contractDialogVisible" width="600px">
  <el-form :model="contractForm" :rules="contractRules" ref="contractFormRef" label-width="100px">
    <el-form-item label="总合同号" prop="masterContractNo">
      <el-input v-model="contractForm.masterContractNo" placeholder="请输入总合同号" />
    </el-form-item>
    <el-form-item label="录入日期" prop="entryDate">
      <el-date-picker
        v-model="contractForm.entryDate"
        type="datetime"
        placeholder="选择日期时间"
        value-format="yyyy-MM-dd HH:mm:ss"
        style="width: 100%"
      />
    </el-form-item>
    <el-form-item label="录入人" prop="entryPerson">
      <el-input v-model="contractForm.entryPerson" placeholder="请输入录入人" />
    </el-form-item>
    <el-form-item label="附件">
      <file-upload
        v-model="contractForm.storageBlobDTOs"
        :record-type="'supplier_contract_record'"
        :record-id="contractForm.id"
      />
    </el-form-item>
    <el-form-item label="备注">
      <el-input v-model="contractForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
    </el-form-item>
  </el-form>
  <template #footer>
    <el-button @click="contractDialogVisible = false">取消</el-button>
    <el-button type="primary" @click="submitContractForm">确定</el-button>
  </template>
</el-dialog>
```
### 3. é‡‡è´­å°è´¦æ–°å¢ž/编辑页 - ä¾›åº”商选择后联动总合同号
```html
<el-form-item label="供应商" prop="supplierId">
  <el-select
    v-model="form.supplierId"
    placeholder="请选择供应商"
    filterable
    @change="handleSupplierChange"
  >
    <el-option
      v-for="item in supplierList"
      :key="item.id"
      :label="item.supplierName"
      :value="item.id"
    />
  </el-select>
</el-form-item>
<el-form-item label="总合同号" prop="masterContractNo">
  <el-select
    v-model="form.masterContractNo"
    placeholder="请先选择供应商"
    filterable
    clearable
    :disabled="!form.supplierId"
  >
    <el-option
      v-for="item in contractList"
      :key="item.id"
      :label="item.masterContractNo"
      :value="item.masterContractNo"
    />
  </el-select>
</el-form-item>
```
### 4. é‡‡è´­å°è´¦åˆ—表页 - æ“ä½œæ æ·»åŠ æŸ¥çœ‹æ€»åˆåŒé™„ä»¶æŒ‰é’®
```html
<el-table-column label="操作" width="200">
  <template #default="{ row }">
    <el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
    <el-button type="text" size="small" @click="handleDelete(row)">删除</el-button>
    <el-button
      type="text"
      size="small"
      v-if="row.masterContractNo"
      @click="handleViewContractFiles(row)"
    >查看附件</el-button>
  </template>
</el-table-column>
```
### 5. æ€»åˆåŒé™„件查看弹窗
```html
<el-dialog title="总合同附件" v-model="contractFileDialogVisible" width="600px">
  <el-table :data="contractFileList" v-if="contractFileList.length">
    <el-table-column prop="name" label="文件名称" />
    <el-table-column prop="byteSize" label="文件大小" width="120">
      <template #default="{ row }">
        {{ formatFileSize(row.byteSize) }}
      </template>
    </el-table-column>
    <el-table-column label="操作" width="150">
      <template #default="{ row }">
        <el-link type="primary" :href="row.previewURL" target="_blank">预览</el-link>
        <el-link type="primary" :href="row.downloadURL" style="margin-left: 10px">下载</el-link>
      </template>
    </el-table-column>
  </el-table>
  <el-empty v-else description="暂无附件" />
</el-dialog>
```
### 6. data æ•°æ®
```js
data() {
  return {
    // åˆåŒåˆ—表(供下拉选择)
    contractList: [],
    // åˆåŒè®°å½•列表(供应商详情页)
    contractRecordList: [],
    contractDialogVisible: false,
    contractDialogTitle: '',
    contractForm: {
      id: null,
      supplierId: null,
      masterContractNo: '',
      entryDate: '',
      entryPerson: '',
      entryPersonId: null,
      remark: '',
      storageBlobDTOs: []
    },
    contractRules: {
      masterContractNo: [{ required: true, message: '请输入总合同号', trigger: 'blur' }],
      entryDate: [{ required: true, message: '请选择录入日期', trigger: 'change' }]
    },
    // æ€»åˆåŒé™„件弹窗
    contractFileDialogVisible: false,
    contractFileList: []
  }
}
```
### 7. æ–¹æ³•
```js
methods: {
  // ä¾›åº”商选择变化时,加载该供应商的合同列表
  async handleSupplierChange(supplierId) {
    this.form.masterContractNo = ''  // æ¸…空总合同号
    this.contractList = []
    if (!supplierId) return
    const res = await this.$http.get(`/basic/supplier-contract/list/${supplierId}`)
    if (res.code === 200) {
      this.contractList = res.data
    }
  },
  // èŽ·å–ä¾›åº”å•†åˆåŒè®°å½•åˆ—è¡¨ï¼ˆä¾›åº”å•†è¯¦æƒ…é¡µï¼‰
  async getContractList(supplierId) {
    const res = await this.$http.get(`/basic/supplier-contract/list/${supplierId}`)
    if (res.code === 200) {
      this.contractRecordList = res.data
    }
  },
  // æ–°å¢žåˆåŒè®°å½•
  handleAddContract() {
    this.contractDialogTitle = '新增合同记录'
    this.contractForm = {
      id: null,
      supplierId: this.supplierId,
      masterContractNo: '',
      entryDate: '',
      entryPerson: '',
      entryPersonId: null,
      remark: '',
      storageBlobDTOs: []
    }
    this.contractDialogVisible = true
  },
  // ç¼–辑合同记录
  async handleEditContract(row) {
    this.contractDialogTitle = '编辑合同记录'
    const res = await this.$http.get(`/basic/supplier-contract/${row.id}`)
    if (res.code === 200) {
      this.contractForm = {
        ...res.data,
        storageBlobDTOs: res.data.fileList ? res.data.fileList.map(f => ({ id: f.id })) : []
      }
      this.contractDialogVisible = true
    }
  },
  // åˆ é™¤åˆåŒè®°å½•
  async handleDeleteContract(row) {
    await this.$confirm('确定删除该合同记录吗?', '提示', { type: 'warning' })
    const res = await this.$http.delete(`/basic/supplier-contract/${row.id}`)
    if (res.code === 200) {
      this.$message.success('删除成功')
      this.getContractList(this.supplierId)
    }
  },
  // æäº¤åˆåŒè®°å½•表单
  async submitContractForm() {
    await this.$refs.contractFormRef.validate()
    const res = await this.$http.post('/basic/supplier-contract', this.contractForm)
    if (res.code === 200) {
      this.$message.success('操作成功')
      this.contractDialogVisible = false
      this.getContractList(this.supplierId)
    }
  },
  // æŸ¥çœ‹æ€»åˆåŒé™„ä»¶
  async handleViewContractFiles(row) {
    if (!row.masterContractNo) {
      this.$message.warning('该记录没有总合同号')
      return
    }
    const res = await this.$http.get('/basic/supplier-contract/files/byContractNo', {
      params: { masterContractNo: row.masterContractNo }
    })
    if (res.code === 200) {
      this.contractFileList = res.data || []
      this.contractFileDialogVisible = true
    }
  },
  // æ ¼å¼åŒ–文件大小
  formatFileSize(bytes) {
    if (!bytes) return '0 B'
    const k = 1024
    const sizes = ['B', 'KB', 'MB', 'GB']
    const i = Math.floor(Math.log(bytes) / Math.log(k))
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
  },
  // æŸ¥çœ‹é™„件列表
  handleViewFiles(row) {
    this.contractFileList = row.fileList || []
    this.contractFileDialogVisible = true
  },
  // ç¼–辑页打开时回显总合同号
  async handleEdit(row) {
    this.form = { ...row }
    this.dialogVisible = true
    // åŠ è½½ä¾›åº”å•†çš„åˆåŒåˆ—è¡¨
    if (row.supplierId) {
      const res = await this.$http.get(`/basic/supplier-contract/list/${row.supplierId}`)
      if (res.code === 200) {
        this.contractList = res.data
      }
    }
  }
}
```
### 8. é‡‡è´­å°è´¦åˆ—表页 - è¡¨æ ¼æ˜¾ç¤ºæ€»åˆåŒå·åˆ—
```html
<el-table-column prop="masterContractNo" label="总合同号" width="150" show-overflow-tooltip />
```
## åŽç«¯æ–‡ä»¶æ¸…单
| æ–‡ä»¶è·¯å¾„ | è¯´æ˜Ž |
|---------|------|
| `src/main/java/com/ruoyi/basic/pojo/SupplierContractRecord.java` | å®žä½“ç±» |
| `src/main/java/com/ruoyi/basic/dto/SupplierContractRecordDto.java` | DTO |
| `src/main/java/com/ruoyi/basic/mapper/SupplierContractRecordMapper.java` | Mapper接口 |
| `src/main/resources/mapper/basic/SupplierContractRecordMapper.xml` | Mapper XML |
| `src/main/java/com/ruoyi/basic/service/SupplierContractRecordService.java` | Service接口 |
| `src/main/java/com/ruoyi/basic/service/impl/SupplierContractRecordServiceImpl.java` | Service实现 |
| `src/main/java/com/ruoyi/basic/controller/SupplierContractRecordController.java` | Controller |
| `src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java` | æ·»åŠ æžšä¸¾å€¼ `SUPPLIER_CONTRACT_RECORD` |
## æ³¨æ„äº‹é¡¹
- æ€»åˆåŒå·æ¥æºäºŽä¾›åº”商管理的合同记录子表
- é€‰æ‹©é¡ºåºï¼šå…ˆé€‰ä¾›åº”商 -> è‡ªåŠ¨åŠ è½½è¯¥ä¾›åº”å•†çš„åˆåŒåˆ—è¡¨ -> å†é€‰æ€»åˆåŒå·
- é‡‡è´­å°è´¦è¡¨å·²æœ‰ `master_contract_no` å­—段,无需修改数据库
- æŸ¥çœ‹é™„件按钮仅在总合同号存在时显示
- é™„件列表通过总合同号查询,支持预览和下载
docs/sales_master_contract_linkage.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,216 @@
# é”€å”®å°è´¦/销售报价-总合同号联动选择及附件查看
## æ¶‰åŠé¡µé¢
- é”€å”®å°è´¦åˆ—表页/新增编辑页
- é”€å”®æŠ¥ä»·åˆ—表页/新增编辑页
## æ–°å¢žAPI
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /basic/customer-contract/list/{customerId} | æŸ¥è¯¢å®¢æˆ·åˆåŒè®°å½•列表(供下拉选择) |
| GET | /basic/customer-contract/files/byContractNo?masterContractNo=xxx | æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„ä»¶ |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é”€å”®å°è´¦æ–°å¢ž/编辑页 - å®¢æˆ·é€‰æ‹©åŽè”动总合同号
```html
<el-form-item label="客户名称" prop="customerId">
  <el-select
    v-model="form.customerId"
    placeholder="请选择客户"
    filterable
    @change="handleCustomerChange"
  >
    <el-option
      v-for="item in customerList"
      :key="item.id"
      :label="item.customerName"
      :value="item.id"
    />
  </el-select>
</el-form-item>
<el-form-item label="总合同号" prop="masterContractNo">
  <el-select
    v-model="form.masterContractNo"
    placeholder="请先选择客户"
    filterable
    clearable
    :disabled="!form.customerId"
  >
    <el-option
      v-for="item in contractList"
      :key="item.id"
      :label="item.masterContractNo"
      :value="item.masterContractNo"
    />
  </el-select>
</el-form-item>
```
### 2. é”€å”®æŠ¥ä»·æ–°å¢ž/编辑页 - åŒæ ·æ·»åŠ å®¢æˆ·å’Œæ€»åˆåŒå·è”åŠ¨
```html
<el-form-item label="客户名称" prop="customerId">
  <el-select
    v-model="form.customerId"
    placeholder="请选择客户"
    filterable
    @change="handleCustomerChange"
  >
    <el-option
      v-for="item in customerList"
      :key="item.id"
      :label="item.customerName"
      :value="item.id"
    />
  </el-select>
</el-form-item>
<el-form-item label="总合同号" prop="masterContractNo">
  <el-select
    v-model="form.masterContractNo"
    placeholder="请先选择客户"
    filterable
    clearable
    :disabled="!form.customerId"
  >
    <el-option
      v-for="item in contractList"
      :key="item.id"
      :label="item.masterContractNo"
      :value="item.masterContractNo"
    />
  </el-select>
</el-form-item>
```
### 3. é”€å”®å°è´¦åˆ—表页 - æ“ä½œæ æ·»åŠ æŸ¥çœ‹æ€»åˆåŒé™„ä»¶æŒ‰é’®
```html
<el-table-column label="操作" width="200">
  <template #default="{ row }">
    <el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
    <el-button type="text" size="small" @click="handleDelete(row)">删除</el-button>
    <el-button
      type="text"
      size="small"
      v-if="row.masterContractNo"
      @click="handleViewContractFiles(row)"
    >查看附件</el-button>
  </template>
</el-table-column>
```
### 4. æ€»åˆåŒé™„件查看弹窗
```html
<el-dialog title="总合同附件" v-model="contractFileDialogVisible" width="600px">
  <el-table :data="contractFileList" v-if="contractFileList.length">
    <el-table-column prop="name" label="文件名称" />
    <el-table-column prop="byteSize" label="文件大小" width="120">
      <template #default="{ row }">
        {{ formatFileSize(row.byteSize) }}
      </template>
    </el-table-column>
    <el-table-column label="操作" width="150">
      <template #default="{ row }">
        <el-link type="primary" :href="row.previewURL" target="_blank">预览</el-link>
        <el-link type="primary" :href="row.downloadURL" style="margin-left: 10px">下载</el-link>
      </template>
    </el-table-column>
  </el-table>
  <el-empty v-else description="暂无附件" />
</el-dialog>
```
### 5. data æ•°æ®
```js
data() {
  return {
    // åˆåŒåˆ—表(供下拉选择)
    contractList: [],
    // æ€»åˆåŒé™„件弹窗
    contractFileDialogVisible: false,
    contractFileList: []
  }
}
```
### 6. æ–¹æ³•
```js
methods: {
  // å®¢æˆ·é€‰æ‹©å˜åŒ–时,加载该客户的合同列表
  async handleCustomerChange(customerId) {
    this.form.masterContractNo = ''  // æ¸…空总合同号
    this.contractList = []
    if (!customerId) return
    const res = await this.$http.get(`/basic/customer-contract/list/${customerId}`)
    if (res.code === 200) {
      this.contractList = res.data
    }
  },
  // æŸ¥çœ‹æ€»åˆåŒé™„ä»¶
  async handleViewContractFiles(row) {
    if (!row.masterContractNo) {
      this.$message.warning('该记录没有总合同号')
      return
    }
    const res = await this.$http.get('/basic/customer-contract/files/byContractNo', {
      params: { masterContractNo: row.masterContractNo }
    })
    if (res.code === 200) {
      this.contractFileList = res.data || []
      this.contractFileDialogVisible = true
    }
  },
  // æ ¼å¼åŒ–文件大小
  formatFileSize(bytes) {
    if (!bytes) return '0 B'
    const k = 1024
    const sizes = ['B', 'KB', 'MB', 'GB']
    const i = Math.floor(Math.log(bytes) / Math.log(k))
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
  }
}
```
### 7. é”€å”®å°è´¦/报价列表页 - è¡¨æ ¼æ˜¾ç¤ºæ€»åˆåŒå·åˆ—
```html
<el-table-column prop="masterContractNo" label="总合同号" width="150" show-overflow-tooltip />
```
### 8. ç¼–辑页打开时回显总合同号
```js
// æ‰“开编辑弹窗时,加载客户的合同列表并回显
async handleEdit(row) {
  this.form = { ...row }
  this.dialogVisible = true
  // åŠ è½½å®¢æˆ·çš„åˆåŒåˆ—è¡¨
  if (row.customerId) {
    const res = await this.$http.get(`/basic/customer-contract/list/${row.customerId}`)
    if (res.code === 200) {
      this.contractList = res.data
    }
  }
}
```
## æ³¨æ„äº‹é¡¹
- æ€»åˆåŒå·æ¥æºäºŽå®¢æˆ·æ¡£æ¡ˆç§æµ·çš„合同记录子表
- é€‰æ‹©é¡ºåºï¼šå…ˆé€‰å®¢æˆ· -> è‡ªåŠ¨åŠ è½½è¯¥å®¢æˆ·çš„åˆåŒåˆ—è¡¨ -> å†é€‰æ€»åˆåŒå·
- é”€å”®å°è´¦è¡¨å·²æœ‰ `master_contract_no` å­—段,销售报价表需新增该字段
- æŸ¥çœ‹é™„件按钮仅在总合同号存在时显示
- é™„件列表通过总合同号查询,支持预览和下载
docs/sales_quotation_add_master_contract_no.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
-- é”€å”®æŠ¥ä»·è¡¨æ·»åŠ æ€»åˆåŒå·å­—æ®µ
ALTER TABLE `sales_quotation`
ADD COLUMN `master_contract_no` varchar(100) DEFAULT NULL COMMENT '总合同号' AFTER `customer_id`;
-- æ³¨æ„ï¼šé”€å”®å°è´¦è¡¨å·²æœ‰ master_contract_no å­—段,无需修改
-- æ³¨æ„ï¼šcustomer_contract_record è¡¨å·²åœ¨å‰é¢åˆ›å»ºï¼Œç”¨äºŽå­˜å‚¨å®¢æˆ·åˆåŒè®°å½•及附件
docs/supplier_contract_record.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
-- ä¾›åº”商管理-合同记录子表
CREATE TABLE `supplier_contract_record` (
    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `supplier_id` bigint NOT NULL COMMENT '关联供应商ID',
    `master_contract_no` varchar(100) DEFAULT NULL COMMENT '总合同号',
    `entry_date` datetime DEFAULT NULL COMMENT '录入日期',
    `entry_person` varchar(50) DEFAULT NULL COMMENT '录入人',
    `entry_person_id` bigint DEFAULT NULL COMMENT '录入人ID',
    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
    `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
    `create_user` int DEFAULT NULL COMMENT '创建用户',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_user` int DEFAULT NULL COMMENT '修改用户',
    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    KEY `idx_supplier_id` (`supplier_id`) COMMENT '供应商ID索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='供应商管理-合同记录表';
-- æ³¨æ„ï¼šé™„件使用系统统一的 storage_attachment è¡¨ç®¡ç†
-- éœ€è¦åœ¨ RecordTypeEnum æžšä¸¾ä¸­æ·»åŠ ï¼šSUPPLIER_CONTRACT_RECORD("supplier_contract_record")
-- é™„件关联方式:record_type = 'supplier_contract_record', record_id = åˆåŒè®°å½•ID
-- é‡‡è´­å°è´¦è¡¨æ·»åŠ ä¾›åº”å•†æ€»åˆåŒå·å­—æ®µï¼ˆå¦‚æžœè¿˜æ²¡æœ‰çš„è¯ï¼‰
ALTER TABLE `purchase_ledger` ADD COLUMN `master_contract_no` varchar(100) DEFAULT NULL COMMENT '总合同号' AFTER `supplier_id`;
src/main/java/com/ruoyi/basic/controller/CustomerContractRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.dto.CustomerContractRecordDto;
import com.ruoyi.basic.service.CustomerContractRecordService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * å®¢æˆ·åˆåŒè®°å½•Controller
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Tag(name = "客户合同记录")
@RestController
@RequestMapping("/basic/customer-contract")
@RequiredArgsConstructor
public class CustomerContractRecordController extends BaseController {
    private final CustomerContractRecordService customerContractRecordService;
    /**
     * æŸ¥è¯¢å®¢æˆ·åˆåŒè®°å½•列表
     */
    @Operation(summary = "查询客户合同记录列表")
    @GetMapping("/list/{customerId}")
    public AjaxResult list(@PathVariable Long customerId) {
        List<CustomerContractRecordDto> list = customerContractRecordService.selectListByCustomerId(customerId);
        return AjaxResult.success(list);
    }
    /**
     * èŽ·å–å®¢æˆ·åˆåŒè®°å½•è¯¦ç»†ä¿¡æ¯
     */
    @Operation(summary = "获取客户合同记录详细信息")
    @GetMapping("/{id}")
    public AjaxResult getInfo(@PathVariable Long id) {
        return AjaxResult.success(customerContractRecordService.selectDetailById(id));
    }
    /**
     * æ–°å¢žå®¢æˆ·åˆåŒè®°å½•
     */
    @Operation(summary = "新增客户合同记录")
    @Log(title = "客户合同记录-新增", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody CustomerContractRecordDto dto) {
        int result = customerContractRecordService.insertCustomerContractRecord(dto);
        return toAjax(result);
    }
    /**
     * ä¿®æ”¹å®¢æˆ·åˆåŒè®°å½•
     */
    @Operation(summary = "修改客户合同记录")
    @Log(title = "客户合同记录-修改", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody CustomerContractRecordDto dto) {
        return toAjax(customerContractRecordService.updateCustomerContractRecord(dto));
    }
    /**
     * åˆ é™¤å®¢æˆ·åˆåŒè®°å½•
     */
    @Operation(summary = "删除客户合同记录")
    @Log(title = "客户合同记录-删除", businessType = BusinessType.DELETE)
    @DeleteMapping("/{id}")
    public AjaxResult remove(@PathVariable Long id) {
        return toAjax(customerContractRecordService.deleteCustomerContractRecord(id));
    }
    /**
     * æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„件(供销售台账、销售报价等模块使用)
     */
    @Operation(summary = "根据总合同号查询附件")
    @GetMapping("/files/byContractNo")
    public AjaxResult getFilesByContractNo(@RequestParam String masterContractNo) {
        return AjaxResult.success(customerContractRecordService.selectFilesByMasterContractNo(masterContractNo));
    }
}
src/main/java/com/ruoyi/basic/controller/SupplierContractRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.dto.SupplierContractRecordDto;
import com.ruoyi.basic.service.SupplierContractRecordService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * ä¾›åº”商合同记录Controller
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Tag(name = "供应商合同记录")
@RestController
@RequestMapping("/basic/supplier-contract")
@RequiredArgsConstructor
public class SupplierContractRecordController extends BaseController {
    private final SupplierContractRecordService supplierContractRecordService;
    /**
     * æŸ¥è¯¢ä¾›åº”商合同记录列表
     */
    @Operation(summary = "查询供应商合同记录列表")
    @GetMapping("/list/{supplierId}")
    public AjaxResult list(@PathVariable Long supplierId) {
        List<SupplierContractRecordDto> list = supplierContractRecordService.selectListBySupplierId(supplierId);
        return AjaxResult.success(list);
    }
    /**
     * èŽ·å–ä¾›åº”å•†åˆåŒè®°å½•è¯¦ç»†ä¿¡æ¯
     */
    @Operation(summary = "获取供应商合同记录详细信息")
    @GetMapping("/{id}")
    public AjaxResult getInfo(@PathVariable Long id) {
        return AjaxResult.success(supplierContractRecordService.selectDetailById(id));
    }
    /**
     * æ–°å¢žä¾›åº”商合同记录
     */
    @Operation(summary = "新增供应商合同记录")
    @Log(title = "供应商合同记录-新增", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody SupplierContractRecordDto dto) {
        int result = supplierContractRecordService.insertSupplierContractRecord(dto);
        return toAjax(result);
    }
    /**
     * ä¿®æ”¹ä¾›åº”商合同记录
     */
    @Operation(summary = "修改供应商合同记录")
    @Log(title = "供应商合同记录-修改", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody SupplierContractRecordDto dto) {
        return toAjax(supplierContractRecordService.updateSupplierContractRecord(dto));
    }
    /**
     * åˆ é™¤ä¾›åº”商合同记录
     */
    @Operation(summary = "删除供应商合同记录")
    @Log(title = "供应商合同记录-删除", businessType = BusinessType.DELETE)
    @DeleteMapping("/{id}")
    public AjaxResult remove(@PathVariable Long id) {
        return toAjax(supplierContractRecordService.deleteSupplierContractRecord(id));
    }
    /**
     * æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„件(供采购台账等模块使用)
     */
    @Operation(summary = "根据总合同号查询附件")
    @GetMapping("/files/byContractNo")
    public AjaxResult getFilesByContractNo(@RequestParam String masterContractNo) {
        return AjaxResult.success(supplierContractRecordService.selectFilesByMasterContractNo(masterContractNo));
    }
}
src/main/java/com/ruoyi/basic/dto/CustomerContractRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.CustomerContractRecord;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * å®¢æˆ·åˆåŒè®°å½•DTO
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(name = "客户合同记录DTO")
public class CustomerContractRecordDto extends CustomerContractRecord {
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "附件列表")
    private List<StorageBlobVO> fileList;
    @Schema(description = "附件ID列表(用于保存)")
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/basic/dto/SupplierContractRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.SupplierContractRecord;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * ä¾›åº”商合同记录DTO
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(name = "供应商合同记录DTO")
public class SupplierContractRecordDto extends SupplierContractRecord {
    @Schema(description = "供应商名称")
    private String supplierName;
    @Schema(description = "附件列表")
    private List<StorageBlobVO> fileList;
    @Schema(description = "附件ID列表(用于保存)")
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -173,6 +173,8 @@
    CUSTOMER_FOLLOW_UP_FILE("customer_follow_up_file"),
    CUSTOMER_FOLLOW_UP("customer_follow_up"),
    CUSTOMER_PRIVATE("customer_private"),
    CUSTOMER_CONTRACT_RECORD("customer_contract_record"),
    SUPPLIER_CONTRACT_RECORD("supplier_contract_record"),
    // Approve
    WORKING_HOURS_SETTING("working_hours_setting"),
    OVERTIME_SETTING("overtime_setting"),
src/main/java/com/ruoyi/basic/mapper/CustomerContractRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.CustomerContractRecord;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * å®¢æˆ·åˆåŒè®°å½•Mapper接口
 *
 * @author ruoyi
 * @date 2026-06-24
 */
public interface CustomerContractRecordMapper extends BaseMapper<CustomerContractRecord> {
    /**
     * æŸ¥è¯¢å®¢æˆ·åˆåŒè®°å½•列表
     *
     * @param customerId å®¢æˆ·ID
     * @return åˆåŒè®°å½•列表
     */
    List<CustomerContractRecord> selectListByCustomerId(@Param("customerId") Long customerId);
}
src/main/java/com/ruoyi/basic/mapper/SupplierContractRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.SupplierContractRecord;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * ä¾›åº”商合同记录Mapper接口
 *
 * @author ruoyi
 * @date 2026-06-24
 */
public interface SupplierContractRecordMapper extends BaseMapper<SupplierContractRecord> {
    /**
     * æŸ¥è¯¢ä¾›åº”商合同记录列表
     *
     * @param supplierId ä¾›åº”商ID
     * @return åˆåŒè®°å½•列表
     */
    List<SupplierContractRecord> selectListBySupplierId(@Param("supplierId") Long supplierId);
}
src/main/java/com/ruoyi/basic/pojo/CustomerContractRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * å®¢æˆ·æ¡£æ¡ˆç§æµ·-合同记录表
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Data
@TableName("customer_contract_record")
@Schema(name = "客户合同记录")
public class CustomerContractRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å…³è”客户档案ID(私海客户)
     */
    @Schema(description = "关联客户档案ID")
    private Long customerId;
    /**
     * æ€»åˆåŒå·
     */
    @Schema(description = "总合同号")
    private String masterContractNo;
    /**
     * å½•入日期
     */
    @Schema(description = "录入日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "entry_date")
    private LocalDateTime entryDate;
    /**
     * å½•入人
     */
    @Schema(description = "录入人")
    private String entryPerson;
    /**
     * å½•入人ID
     */
    @Schema(description = "录入人ID")
    private Long entryPersonId;
    /**
     * å¤‡æ³¨
     */
    @Schema(description = "备注")
    private String remark;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * éƒ¨é—¨ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * åˆ›å»ºç”¨æˆ·
     */
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹ç”¨æˆ·
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/basic/pojo/SupplierContractRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * ä¾›åº”商管理-合同记录表
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Data
@TableName("supplier_contract_record")
@Schema(name = "供应商合同记录")
public class SupplierContractRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å…³è”供应商ID
     */
    @Schema(description = "关联供应商ID")
    private Long supplierId;
    /**
     * æ€»åˆåŒå·
     */
    @Schema(description = "总合同号")
    private String masterContractNo;
    /**
     * å½•入日期
     */
    @Schema(description = "录入日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "entry_date")
    private LocalDateTime entryDate;
    /**
     * å½•入人
     */
    @Schema(description = "录入人")
    private String entryPerson;
    /**
     * å½•入人ID
     */
    @Schema(description = "录入人ID")
    private Long entryPersonId;
    /**
     * å¤‡æ³¨
     */
    @Schema(description = "备注")
    private String remark;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * éƒ¨é—¨ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * åˆ›å»ºç”¨æˆ·
     */
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹ç”¨æˆ·
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/basic/service/CustomerContractRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.CustomerContractRecordDto;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.pojo.CustomerContractRecord;
import java.util.List;
/**
 * å®¢æˆ·åˆåŒè®°å½•Service接口
 *
 * @author ruoyi
 * @date 2026-06-24
 */
public interface CustomerContractRecordService extends IService<CustomerContractRecord> {
    /**
     * æŸ¥è¯¢å®¢æˆ·åˆåŒè®°å½•列表
     *
     * @param customerId å®¢æˆ·ID
     * @return åˆåŒè®°å½•列表
     */
    List<CustomerContractRecordDto> selectListByCustomerId(Long customerId);
    /**
     * æŸ¥è¯¢å®¢æˆ·åˆåŒè®°å½•详情(含附件)
     *
     * @param id åˆåŒè®°å½•ID
     * @return åˆåŒè®°å½•详情
     */
    CustomerContractRecordDto selectDetailById(Long id);
    /**
     * æ–°å¢žå®¢æˆ·åˆåŒè®°å½•
     *
     * @param dto åˆåŒè®°å½•信息
     * @return ç»“æžœ
     */
    int insertCustomerContractRecord(CustomerContractRecordDto dto);
    /**
     * ä¿®æ”¹å®¢æˆ·åˆåŒè®°å½•
     *
     * @param dto åˆåŒè®°å½•信息
     * @return ç»“æžœ
     */
    int updateCustomerContractRecord(CustomerContractRecordDto dto);
    /**
     * åˆ é™¤å®¢æˆ·åˆåŒè®°å½•
     *
     * @param id åˆåŒè®°å½•ID
     * @return ç»“æžœ
     */
    int deleteCustomerContractRecord(Long id);
    /**
     * æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„件列表
     *
     * @param masterContractNo æ€»åˆåŒå·
     * @return é™„件列表
     */
    List<StorageBlobVO> selectFilesByMasterContractNo(String masterContractNo);
}
src/main/java/com/ruoyi/basic/service/SupplierContractRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.dto.SupplierContractRecordDto;
import com.ruoyi.basic.pojo.SupplierContractRecord;
import java.util.List;
/**
 * ä¾›åº”商合同记录Service接口
 *
 * @author ruoyi
 * @date 2026-06-24
 */
public interface SupplierContractRecordService extends IService<SupplierContractRecord> {
    /**
     * æŸ¥è¯¢ä¾›åº”商合同记录列表
     *
     * @param supplierId ä¾›åº”商ID
     * @return åˆåŒè®°å½•列表
     */
    List<SupplierContractRecordDto> selectListBySupplierId(Long supplierId);
    /**
     * æŸ¥è¯¢ä¾›åº”商合同记录详情(含附件)
     *
     * @param id åˆåŒè®°å½•ID
     * @return åˆåŒè®°å½•详情
     */
    SupplierContractRecordDto selectDetailById(Long id);
    /**
     * æ–°å¢žä¾›åº”商合同记录
     *
     * @param dto åˆåŒè®°å½•信息
     * @return ç»“æžœ
     */
    int insertSupplierContractRecord(SupplierContractRecordDto dto);
    /**
     * ä¿®æ”¹ä¾›åº”商合同记录
     *
     * @param dto åˆåŒè®°å½•信息
     * @return ç»“æžœ
     */
    int updateSupplierContractRecord(SupplierContractRecordDto dto);
    /**
     * åˆ é™¤ä¾›åº”商合同记录
     *
     * @param id åˆåŒè®°å½•ID
     * @return ç»“æžœ
     */
    int deleteSupplierContractRecord(Long id);
    /**
     * æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢é™„件列表
     *
     * @param masterContractNo æ€»åˆåŒå·
     * @return é™„件列表
     */
    List<StorageBlobVO> selectFilesByMasterContractNo(String masterContractNo);
}
src/main/java/com/ruoyi/basic/service/impl/CustomerContractRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerContractRecordDto;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerContractRecordMapper;
import com.ruoyi.basic.pojo.CustomerContractRecord;
import com.ruoyi.basic.service.CustomerContractRecordService;
import com.ruoyi.basic.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
 * å®¢æˆ·åˆåŒè®°å½•Service实现类
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Service
@RequiredArgsConstructor
public class CustomerContractRecordServiceImpl extends ServiceImpl<CustomerContractRecordMapper, CustomerContractRecord> implements CustomerContractRecordService {
    private final CustomerContractRecordMapper customerContractRecordMapper;
    private final FileUtil fileUtil;
    @Override
    public List<CustomerContractRecordDto> selectListByCustomerId(Long customerId) {
        List<CustomerContractRecord> records = customerContractRecordMapper.selectListByCustomerId(customerId);
        return records.stream().map(record -> {
            CustomerContractRecordDto dto = new CustomerContractRecordDto();
            BeanUtils.copyProperties(record, dto);
            // èŽ·å–é™„ä»¶åˆ—è¡¨
            List<StorageBlobVO> files = fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                    RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, record.getId());
            dto.setFileList(files);
            return dto;
        }).collect(Collectors.toList());
    }
    @Override
    public CustomerContractRecordDto selectDetailById(Long id) {
        CustomerContractRecord record = customerContractRecordMapper.selectById(id);
        if (record == null) {
            return null;
        }
        CustomerContractRecordDto dto = new CustomerContractRecordDto();
        BeanUtils.copyProperties(record, dto);
        // èŽ·å–é™„ä»¶åˆ—è¡¨
        List<StorageBlobVO> files = fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, id);
        dto.setFileList(files);
        return dto;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertCustomerContractRecord(CustomerContractRecordDto dto) {
        CustomerContractRecord record = new CustomerContractRecord();
        BeanUtils.copyProperties(dto, record);
        int result = customerContractRecordMapper.insert(record);
        // ä¿å­˜é™„ä»¶
        if (dto.getStorageBlobDTOs() != null && !dto.getStorageBlobDTOs().isEmpty()) {
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                    "file", RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, record.getId(), dto.getStorageBlobDTOs());
        }
        // è¿”回新增记录的ID
        dto.setId(record.getId());
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateCustomerContractRecord(CustomerContractRecordDto dto) {
        CustomerContractRecord record = new CustomerContractRecord();
        BeanUtils.copyProperties(dto, record);
        int result = customerContractRecordMapper.updateById(record);
        // æ›´æ–°é™„ä»¶
        if (dto.getStorageBlobDTOs() != null) {
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                    "file", RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, record.getId(), dto.getStorageBlobDTOs());
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteCustomerContractRecord(Long id) {
        // åˆ é™¤é™„ä»¶
        fileUtil.deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, id);
        return customerContractRecordMapper.deleteById(id);
    }
    @Override
    public List<StorageBlobVO> selectFilesByMasterContractNo(String masterContractNo) {
        // æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢åˆåŒè®°å½•
        CustomerContractRecord record = customerContractRecordMapper.selectOne(
                new LambdaQueryWrapper<CustomerContractRecord>()
                        .eq(CustomerContractRecord::getMasterContractNo, masterContractNo)
                        .last("LIMIT 1"));
        if (record == null) {
            return Collections.emptyList();
        }
        // èŽ·å–é™„ä»¶åˆ—è¡¨
        return fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                RecordTypeEnum.CUSTOMER_CONTRACT_RECORD, record.getId());
    }
}
src/main/java/com/ruoyi/basic/service/impl/SupplierContractRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.dto.SupplierContractRecordDto;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.SupplierContractRecordMapper;
import com.ruoyi.basic.pojo.SupplierContractRecord;
import com.ruoyi.basic.service.SupplierContractRecordService;
import com.ruoyi.basic.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
 * ä¾›åº”商合同记录Service实现类
 *
 * @author ruoyi
 * @date 2026-06-24
 */
@Service
@RequiredArgsConstructor
public class SupplierContractRecordServiceImpl extends ServiceImpl<SupplierContractRecordMapper, SupplierContractRecord> implements SupplierContractRecordService {
    private final SupplierContractRecordMapper supplierContractRecordMapper;
    private final FileUtil fileUtil;
    @Override
    public List<SupplierContractRecordDto> selectListBySupplierId(Long supplierId) {
        List<SupplierContractRecord> records = supplierContractRecordMapper.selectListBySupplierId(supplierId);
        return records.stream().map(record -> {
            SupplierContractRecordDto dto = new SupplierContractRecordDto();
            BeanUtils.copyProperties(record, dto);
            // èŽ·å–é™„ä»¶åˆ—è¡¨
            List<StorageBlobVO> files = fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                    RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, record.getId());
            dto.setFileList(files);
            return dto;
        }).collect(Collectors.toList());
    }
    @Override
    public SupplierContractRecordDto selectDetailById(Long id) {
        SupplierContractRecord record = supplierContractRecordMapper.selectById(id);
        if (record == null) {
            return null;
        }
        SupplierContractRecordDto dto = new SupplierContractRecordDto();
        BeanUtils.copyProperties(record, dto);
        // èŽ·å–é™„ä»¶åˆ—è¡¨
        List<StorageBlobVO> files = fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, id);
        dto.setFileList(files);
        return dto;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertSupplierContractRecord(SupplierContractRecordDto dto) {
        SupplierContractRecord record = new SupplierContractRecord();
        BeanUtils.copyProperties(dto, record);
        int result = supplierContractRecordMapper.insert(record);
        // ä¿å­˜é™„ä»¶
        if (dto.getStorageBlobDTOs() != null && !dto.getStorageBlobDTOs().isEmpty()) {
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                    "file", RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, record.getId(), dto.getStorageBlobDTOs());
        }
        // è¿”回新增记录的ID
        dto.setId(record.getId());
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateSupplierContractRecord(SupplierContractRecordDto dto) {
        SupplierContractRecord record = new SupplierContractRecord();
        BeanUtils.copyProperties(dto, record);
        int result = supplierContractRecordMapper.updateById(record);
        // æ›´æ–°é™„ä»¶
        if (dto.getStorageBlobDTOs() != null) {
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                    "file", RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, record.getId(), dto.getStorageBlobDTOs());
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteSupplierContractRecord(Long id) {
        // åˆ é™¤é™„ä»¶
        fileUtil.deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, id);
        return supplierContractRecordMapper.deleteById(id);
    }
    @Override
    public List<StorageBlobVO> selectFilesByMasterContractNo(String masterContractNo) {
        // æ ¹æ®æ€»åˆåŒå·æŸ¥è¯¢åˆåŒè®°å½•
        SupplierContractRecord record = supplierContractRecordMapper.selectOne(
                new LambdaQueryWrapper<SupplierContractRecord>()
                        .eq(SupplierContractRecord::getMasterContractNo, masterContractNo)
                        .last("LIMIT 1"));
        if (record == null) {
            return Collections.emptyList();
        }
        // èŽ·å–é™„ä»¶åˆ—è¡¨
        return fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(
                RecordTypeEnum.SUPPLIER_CONTRACT_RECORD, record.getId());
    }
}
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
@@ -26,6 +26,10 @@
    @ApiModelProperty(value = "客户id")
    private Long customerId;
    @ApiModelProperty(value = "总合同号")
    @Excel(name = "总合同号")
    private String masterContractNo;
    @ApiModelProperty(value = "业务员")
    @Excel(name = "业务员")
    private String salesperson;
src/main/resources/mapper/basic/CustomerContractRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.basic.mapper.CustomerContractRecordMapper">
    <resultMap id="CustomerContractRecordResult" type="com.ruoyi.basic.pojo.CustomerContractRecord">
        <id property="id" column="id"/>
        <result property="customerId" column="customer_id"/>
        <result property="masterContractNo" column="master_contract_no"/>
        <result property="entryDate" column="entry_date"/>
        <result property="entryPerson" column="entry_person"/>
        <result property="entryPersonId" column="entry_person_id"/>
        <result property="remark" column="remark"/>
        <result property="tenantId" column="tenant_id"/>
        <result property="deptId" column="dept_id"/>
        <result property="createUser" column="create_user"/>
        <result property="createTime" column="create_time"/>
        <result property="updateUser" column="update_user"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    <select id="selectListByCustomerId" resultMap="CustomerContractRecordResult">
        SELECT id, customer_id, master_contract_no, entry_date, entry_person, entry_person_id, remark,
               tenant_id, dept_id, create_user, create_time, update_user, update_time
        FROM customer_contract_record
        WHERE customer_id = #{customerId}
        ORDER BY entry_date DESC, create_time DESC
    </select>
</mapper>
src/main/resources/mapper/basic/SupplierContractRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.basic.mapper.SupplierContractRecordMapper">
    <resultMap id="SupplierContractRecordResult" type="com.ruoyi.basic.pojo.SupplierContractRecord">
        <id property="id" column="id"/>
        <result property="supplierId" column="supplier_id"/>
        <result property="masterContractNo" column="master_contract_no"/>
        <result property="entryDate" column="entry_date"/>
        <result property="entryPerson" column="entry_person"/>
        <result property="entryPersonId" column="entry_person_id"/>
        <result property="remark" column="remark"/>
        <result property="tenantId" column="tenant_id"/>
        <result property="deptId" column="dept_id"/>
        <result property="createUser" column="create_user"/>
        <result property="createTime" column="create_time"/>
        <result property="updateUser" column="update_user"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    <select id="selectListBySupplierId" resultMap="SupplierContractRecordResult">
        SELECT id, supplier_id, master_contract_no, entry_date, entry_person, entry_person_id, remark,
               tenant_id, dept_id, create_user, create_time, update_user, update_time
        FROM supplier_contract_record
        WHERE supplier_id = #{supplierId}
        ORDER BY entry_date DESC, create_time DESC
    </select>
</mapper>
src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -65,7 +65,7 @@
            AND (sl.sales_contract_no like concat('%',#{qualityInspect.salesContractNo},'%')
                OR po_sales.sales_contract_no like concat('%',#{qualityInspect.salesContractNo},'%'))
        </if>
        ORDER BY qi.check_time DESC
        ORDER BY qi.create_time DESC
    </select>
    <select id="qualityInspectExport" resultType="com.ruoyi.quality.pojo.QualityInspect">
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -41,6 +41,13 @@
        END as shippingStatus,
        CASE
         WHEN T1.type != 2 THEN NULL
         WHEN EXISTS (
             SELECT 1 FROM stock_in_record sir
             WHERE sir.record_type = '7' AND sir.record_id = T1.sales_ledger_id
             AND ((sir.batch_no IS NOT NULL AND sir.batch_no LIKE CONCAT('%-', T1.id))
                  OR (sir.batch_no IS NULL AND sir.product_model_id = T1.product_model_id))
             AND sir.approval_status = 2
         ) THEN '已驳回'
         WHEN IFNULL(t4.approved_stock_in_num, 0) &lt;= 0 THEN '待入库'
         WHEN IFNULL(t4.approved_stock_in_num, 0) &gt;= IFNULL(T1.quantity, 0) THEN '完全入库'
        ELSE '入库中'