|
|
|
|
|
# 间接支付 - 关联合同选择带回逻辑整理
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
在间接支付页面(`/payment/indirect-payment`)中,"关联合同"功能出现在两个位置:
|
|
|
|
|
|
1. **第三步(事前流程分类)**:当所选分类的 `need_contract` 为 `true` 时
|
|
|
|
|
|
2. **第六步(支付分类)**:当所选支付分类的 `need_contract` 为 `true` 时
|
|
|
|
|
|
|
|
|
|
|
|
## 一、合同选择位置
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 第三步 - 支出合同选择
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:步骤3(选择事前流程分类并填写信息)
|
|
|
|
|
|
|
|
|
|
|
|
**显示条件**:`selectedCategory?.need_contract === true`
|
|
|
|
|
|
|
|
|
|
|
|
**相关变量**:
|
|
|
|
|
|
- `selectedExpenditureContract`:存储选中的支出合同
|
|
|
|
|
|
- `showExpenditureContractSelector`:控制合同选择器对话框显示
|
|
|
|
|
|
|
|
|
|
|
|
**UI 结构**:
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<el-form-item label="关联合同">
|
|
|
|
|
|
<!-- 未选择时显示选择按钮 -->
|
|
|
|
|
|
<div v-if="!selectedExpenditureContract" class="contract-selector-wrapper">
|
|
|
|
|
|
<el-button type="primary" @click="showExpenditureContractSelector = true">
|
|
|
|
|
|
选择合同
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 已选择时显示合同信息卡片 -->
|
|
|
|
|
|
<div v-else class="contract-info-card">
|
|
|
|
|
|
<!-- 显示合同详情:名称、总额、已支付次数、已支付金额、剩余金额等 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 第六步 - 支付合同选择
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:步骤5(填写支付信息)
|
|
|
|
|
|
|
|
|
|
|
|
**显示条件**:`selectedPaymentCategory?.need_contract === true`
|
|
|
|
|
|
|
|
|
|
|
|
**相关变量**:
|
|
|
|
|
|
- `selectedPaymentContract`:存储选中的支付合同
|
|
|
|
|
|
- `showPaymentContractSelector`:控制合同选择器对话框显示
|
|
|
|
|
|
- `isPaymentContractReused`:判断是否复用第三步的合同
|
|
|
|
|
|
- `reusedPaymentContract`:复用的合同(第三步的合同)
|
|
|
|
|
|
|
|
|
|
|
|
**UI 结构**:
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<el-form-item label="关联合同">
|
|
|
|
|
|
<!-- 情况1:复用第三步的合同(不可更改) -->
|
|
|
|
|
|
<div v-if="isPaymentContractReused" class="contract-info-card">
|
|
|
|
|
|
<el-card>
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<span>已选择合同(复用第三步)</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<!-- 显示复用合同的详情 -->
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 情况2:未选择且不复用时显示选择按钮 -->
|
|
|
|
|
|
<div v-else-if="!selectedPaymentContract" class="contract-selector-wrapper">
|
|
|
|
|
|
<el-button type="primary" @click="showPaymentContractSelector = true">
|
|
|
|
|
|
选择合同
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 情况3:已选择时显示合同信息卡片 -->
|
|
|
|
|
|
<div v-else class="contract-info-card">
|
|
|
|
|
|
<!-- 显示合同详情,支持重新选择和清除 -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 二、合同选择器组件
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 组件位置
|
|
|
|
|
|
|
|
|
|
|
|
`czemc-budget-execution-frontend/src/components/ContractSelector.vue`
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 组件功能
|
|
|
|
|
|
|
|
|
|
|
|
1. **搜索功能**:
|
|
|
|
|
|
- 支持按合同编号(`contract_no`)搜索
|
|
|
|
|
|
- 支持按合同名称(`title`)搜索
|
|
|
|
|
|
- 支持回车键触发查询
|
|
|
|
|
|
|
|
|
|
|
|
2. **列表展示**:
|
|
|
|
|
|
- 显示合同编号、合同名称、合同类型、合同金额、金额类型、签订日期、合同履行期
|
|
|
|
|
|
- 支持分页(10/20/50 条每页)
|
|
|
|
|
|
- 支持行点击选择
|
|
|
|
|
|
|
|
|
|
|
|
3. **选择确认**:
|
|
|
|
|
|
- 点击"确定"按钮时,会调用 `contractAPI.detail(contractId)` 获取合同详情
|
|
|
|
|
|
- 合同详情包含支付统计信息(`payment_stats`)
|
|
|
|
|
|
- 通过 `@confirm` 事件将完整的合同详情返回给父组件
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 组件使用
|
|
|
|
|
|
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<!-- 支出合同选择器(第三步) -->
|
|
|
|
|
|
<ContractSelector
|
|
|
|
|
|
:model-value="showExpenditureContractSelector"
|
|
|
|
|
|
@update:model-value="showExpenditureContractSelector = $event"
|
|
|
|
|
|
@confirm="handleExpenditureContractSelected"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 支付合同选择器(第六步) -->
|
|
|
|
|
|
<ContractSelector
|
|
|
|
|
|
:model-value="showPaymentContractSelector"
|
|
|
|
|
|
@update:model-value="showPaymentContractSelector = $event"
|
|
|
|
|
|
@confirm="handlePaymentContractSelected"
|
|
|
|
|
|
/>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 三、合同选择带回逻辑
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 支出合同选择处理(第三步)
|
|
|
|
|
|
|
|
|
|
|
|
**函数**:`handleExpenditureContractSelected(contract)`
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:`IndirectPayment.vue` 第 5885-5911 行
|
|
|
|
|
|
|
|
|
|
|
|
**逻辑流程**:
|
|
|
|
|
|
|
|
|
|
|
|
1. **验证闭口合同**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
if (contract.amount_type === 'fixed') {
|
|
|
|
|
|
// 检查是否已关联过非直接支付
|
|
|
|
|
|
const paymentCount = contract.payment_stats?.payment_count || 0
|
|
|
|
|
|
const paidAmount = Number(contract.payment_stats?.paid_amount || 0)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有已支付记录,不允许选择
|
|
|
|
|
|
if (paidAmount > 0 || paymentCount > 0) {
|
|
|
|
|
|
// 显示警告提示,阻止选择
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. **保存合同**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
selectedExpenditureContract.value = contract
|
|
|
|
|
|
ElMessage.success('合同选择成功')
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验证规则**:
|
|
|
|
|
|
- ✅ **闭口合同**(`amount_type === 'fixed'`):
|
|
|
|
|
|
- 如果 `payment_stats.paid_amount > 0` 或 `payment_stats.payment_count > 0`,说明已关联过非直接支付,**不允许选择**
|
|
|
|
|
|
- 否则允许选择
|
|
|
|
|
|
- ✅ **开口合同**(`amount_type === 'open'`):
|
|
|
|
|
|
- **允许选择**,不检查是否已关联过非直接支付
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 支付合同选择处理(第六步)
|
|
|
|
|
|
|
|
|
|
|
|
**函数**:`handlePaymentContractSelected(contract)`
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:`IndirectPayment.vue` 第 5930-5956 行
|
|
|
|
|
|
|
|
|
|
|
|
**逻辑流程**:
|
|
|
|
|
|
|
|
|
|
|
|
与支出合同选择处理逻辑**完全相同**:
|
|
|
|
|
|
1. 验证闭口合同是否已关联过非直接支付
|
|
|
|
|
|
2. 如果验证通过,保存合同并提示成功
|
|
|
|
|
|
|
|
|
|
|
|
**验证规则**:与支出合同选择相同
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 合同复用逻辑
|
|
|
|
|
|
|
|
|
|
|
|
**触发时机**:当进入第六步(选择支付分类后)时
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:`IndirectPayment.vue` 第 4992-4994 行
|
|
|
|
|
|
|
|
|
|
|
|
**逻辑**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 如果第三步已选择合同,且第六步也需要合同,则自动复用第三步的合同
|
|
|
|
|
|
if (selectedCategory.value?.need_contract &&
|
|
|
|
|
|
selectedExpenditureContract.value &&
|
|
|
|
|
|
selectedPaymentCategory.value?.need_contract) {
|
|
|
|
|
|
selectedPaymentContract.value = selectedExpenditureContract.value
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**判断条件**:
|
|
|
|
|
|
- `isPaymentContractReused` 计算属性:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const isPaymentContractReused = computed(() => {
|
|
|
|
|
|
return selectedCategory.value?.need_contract &&
|
|
|
|
|
|
selectedExpenditureContract.value &&
|
|
|
|
|
|
selectedPaymentCategory.value?.need_contract
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**复用效果**:
|
|
|
|
|
|
- 第六步的合同选择区域显示"已选择合同(复用第三步)"
|
|
|
|
|
|
- 合同信息不可更改(不显示"重新选择"和"清除"按钮)
|
|
|
|
|
|
- 使用第三步选择的合同数据
|
|
|
|
|
|
|
|
|
|
|
|
## 四、合同数据使用
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 合同信息展示
|
|
|
|
|
|
|
|
|
|
|
|
已选择的合同会显示以下信息:
|
|
|
|
|
|
- 合同名称(`title`)
|
|
|
|
|
|
- 合同总额(`amount_total`)
|
|
|
|
|
|
- 已支付次数(`payment_stats.payment_count`)
|
|
|
|
|
|
- 已支付金额(`payment_stats.paid_amount`)
|
|
|
|
|
|
- 剩余金额(仅闭口合同显示,计算公式:`总额 - 已支付金额`)
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1.1 合同总额的使用场景
|
|
|
|
|
|
|
|
|
|
|
|
**重要说明**:合同选择后,**不会自动填充**合同总额到表单字段 `contract_total_amount`。用户需要**手动输入**合同总金额。
|
|
|
|
|
|
|
|
|
|
|
|
合同总额(`contract.amount_total`)的使用场景如下:
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景1:合同信息展示
|
|
|
|
|
|
- **位置**:第三步和第六步的合同选择卡片
|
|
|
|
|
|
- **用途**:显示选中合同的总额信息
|
|
|
|
|
|
- **代码位置**:
|
|
|
|
|
|
- 第三步:`IndirectPayment.vue` 第 631-632 行
|
|
|
|
|
|
- 第六步:`IndirectPayment.vue` 第 901-902 行、941-942 行
|
|
|
|
|
|
- **显示格式**:使用 `formatAmount()` 格式化,显示为带千分位的数字
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景2:计算剩余金额(闭口合同)
|
|
|
|
|
|
- **位置**:合同信息卡片中的"剩余金额"字段
|
|
|
|
|
|
- **用途**:计算闭口合同的剩余可支付金额
|
|
|
|
|
|
- **计算公式**:`剩余金额 = 合同总额 - 已支付金额`
|
|
|
|
|
|
- **代码位置**:
|
|
|
|
|
|
- 支出合同:`IndirectPayment.vue` 第 5919-5926 行
|
|
|
|
|
|
- 支付合同:`IndirectPayment.vue` 第 5964-5971 行
|
|
|
|
|
|
- 复用合同:`IndirectPayment.vue` 第 5986-5993 行
|
|
|
|
|
|
- **注意**:仅闭口合同(`amount_type === 'fixed'`)才显示剩余金额
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景3:预览页面显示
|
|
|
|
|
|
- **位置**:步骤6(总体预览)的合同信息部分
|
|
|
|
|
|
- **用途**:在预览页面显示合同总额
|
|
|
|
|
|
- **代码位置**:`IndirectPayment.vue` 第 773-774 行
|
|
|
|
|
|
- **数据来源**:通过 `getPreviewContract()` 函数获取合同对象
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景4:字段显示条件判断(支付分类)
|
|
|
|
|
|
- **位置**:第六步(填写支付信息)的动态字段显示
|
|
|
|
|
|
- **用途**:根据合同总额判断某些字段是否显示
|
|
|
|
|
|
- **代码位置**:`IndirectPayment.vue` 第 5221-5270 行
|
|
|
|
|
|
- **重要说明**:
|
|
|
|
|
|
- 在支付分类的字段显示条件中,使用的是**表单字段** `formData.contract_total_amount`(用户手动输入的)
|
|
|
|
|
|
- **不是**合同的 `amount_total`(合同对象自带的)
|
|
|
|
|
|
- 代码逻辑:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 非直接支付场景,合同总金额使用事前流程的合同总金额
|
|
|
|
|
|
const contractTotalAmount = formData.value.contract_total_amount || 0
|
|
|
|
|
|
```
|
|
|
|
|
|
- 这意味着:**用户需要手动输入合同总金额,该金额会用于判断支付分类的字段是否显示**
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景5:金额限制条件验证(事前流程分类)
|
|
|
|
|
|
- **位置**:第三步(填写事前流程信息)的金额限制验证
|
|
|
|
|
|
- **用途**:验证用户输入的合同总金额是否满足分类的金额限制条件
|
|
|
|
|
|
- **代码位置**:
|
|
|
|
|
|
- 金额限制条件获取:`IndirectPayment.vue` 第 6070 行
|
|
|
|
|
|
- 金额限制验证:`IndirectPayment.vue` 第 6072-6074 行
|
|
|
|
|
|
- 验证函数:`IndirectPayment.vue` 第 6024-6068 行
|
|
|
|
|
|
- **验证字段**:`formData.contract_total_amount`(用户手动输入的)
|
|
|
|
|
|
- **验证规则**:根据分类配置的 `amount_limit_conditions` 进行验证
|
|
|
|
|
|
- 支持的操作符:`lt`(小于)、`lte`(小于等于)、`gt`(大于)、`gte`(大于等于)、`eq`(等于)
|
|
|
|
|
|
- **UI 显示**:
|
|
|
|
|
|
- 实时显示验证结果(成功/失败)
|
|
|
|
|
|
- 显示金额限制条件的提示文本
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景6:表单验证
|
|
|
|
|
|
- **位置**:第三步进入下一步的验证
|
|
|
|
|
|
- **用途**:验证合同总金额是否已填写且大于0
|
|
|
|
|
|
- **代码位置**:`IndirectPayment.vue` 第 4281-4284 行
|
|
|
|
|
|
- **验证规则**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const contractAmount = Number(formData.value.contract_total_amount)
|
|
|
|
|
|
if (!Number.isFinite(contractAmount) || contractAmount <= 0) {
|
|
|
|
|
|
return false // 不能进入下一步
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 场景7:金额大写显示
|
|
|
|
|
|
- **位置**:第三步的合同总金额输入框下方
|
|
|
|
|
|
- **用途**:将合同总金额转换为中文大写
|
|
|
|
|
|
- **代码位置**:
|
|
|
|
|
|
- 计算属性:`IndirectPayment.vue` 第 5880 行
|
|
|
|
|
|
- 格式化函数:`IndirectPayment.vue` 第 5874-5878 行
|
|
|
|
|
|
- **显示格式**:例如 "壹佰万元整"
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 剩余金额计算
|
|
|
|
|
|
|
|
|
|
|
|
**支出合同剩余金额**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const expenditureContractRemainingAmount = computed(() => {
|
|
|
|
|
|
if (!selectedExpenditureContract.value ||
|
|
|
|
|
|
selectedExpenditureContract.value.amount_type !== 'fixed') {
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
const total = Number(selectedExpenditureContract.value.amount_total) || 0
|
|
|
|
|
|
const paid = Number(selectedExpenditureContract.value.payment_stats?.paid_amount) || 0
|
|
|
|
|
|
return Math.max(0, total - paid)
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**支付合同剩余金额**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const paymentContractRemainingAmount = computed(() => {
|
|
|
|
|
|
if (!selectedPaymentContract.value ||
|
|
|
|
|
|
selectedPaymentContract.value.amount_type !== 'fixed') {
|
|
|
|
|
|
return 0
|
|
|
|
|
|
}
|
|
|
|
|
|
const total = Number(selectedPaymentContract.value.amount_total) || 0
|
|
|
|
|
|
const paid = Number(selectedPaymentContract.value.payment_stats?.paid_amount) || 0
|
|
|
|
|
|
return Math.max(0, total - paid)
|
|
|
|
|
|
})
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 预览页面合同显示
|
|
|
|
|
|
|
|
|
|
|
|
**函数**:`getPreviewContract()`
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:`IndirectPayment.vue` 第 5819-5835 行
|
|
|
|
|
|
|
|
|
|
|
|
**逻辑**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const getPreviewContract = () => {
|
|
|
|
|
|
// 优先使用第六步的合同(如果支付分类需要合同)
|
|
|
|
|
|
if (selectedPaymentCategory.value?.need_contract) {
|
|
|
|
|
|
// 如果复用第三步的合同,使用第三步的合同
|
|
|
|
|
|
if (isPaymentContractReused.value && reusedPaymentContract.value) {
|
|
|
|
|
|
return reusedPaymentContract.value
|
|
|
|
|
|
}
|
|
|
|
|
|
// 否则使用第六步选择的合同
|
|
|
|
|
|
if (selectedPaymentContract.value) {
|
|
|
|
|
|
return selectedPaymentContract.value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果第六步没有合同,但第三步有合同,显示第三步的合同
|
|
|
|
|
|
if (selectedCategory.value?.need_contract && selectedExpenditureContract.value) {
|
|
|
|
|
|
return selectedExpenditureContract.value
|
|
|
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**优先级**:
|
|
|
|
|
|
1. 第六步复用的合同(第三步的合同)
|
|
|
|
|
|
2. 第六步独立选择的合同
|
|
|
|
|
|
3. 第三步的合同(如果第六步不需要合同)
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 提交时的合同ID
|
|
|
|
|
|
|
|
|
|
|
|
**位置**:`IndirectPayment.vue` 第 5620-5628 行
|
|
|
|
|
|
|
|
|
|
|
|
**逻辑**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 支出合同ID(第三步)
|
|
|
|
|
|
const expenditureContractId = selectedCategory.value?.need_contract && selectedExpenditureContract.value
|
|
|
|
|
|
? selectedExpenditureContract.value.id
|
|
|
|
|
|
: null
|
|
|
|
|
|
|
|
|
|
|
|
// 支付合同ID(第六步)
|
|
|
|
|
|
const paymentContractId = selectedPaymentCategory.value?.need_contract
|
|
|
|
|
|
? (isPaymentContractReused.value
|
|
|
|
|
|
? reusedPaymentContract.value?.id
|
|
|
|
|
|
: selectedPaymentContract.value?.id)
|
|
|
|
|
|
: null
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 五、清除合同
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 清除支出合同
|
|
|
|
|
|
|
|
|
|
|
|
**函数**:`clearExpenditureContract()`
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const clearExpenditureContract = () => {
|
|
|
|
|
|
selectedExpenditureContract.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 清除支付合同
|
|
|
|
|
|
|
|
|
|
|
|
**函数**:`clearPaymentContract()`
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const clearPaymentContract = () => {
|
|
|
|
|
|
selectedPaymentContract.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**注意**:如果支付合同是复用的第三步合同,清除操作会清除 `selectedPaymentContract`,但不会影响 `selectedExpenditureContract`。
|
|
|
|
|
|
|
|
|
|
|
|
## 六、关键代码位置
|
|
|
|
|
|
|
|
|
|
|
|
| 功能 | 代码位置 | 行号范围 |
|
|
|
|
|
|
|------|---------|---------|
|
|
|
|
|
|
| 第三步合同选择UI | `IndirectPayment.vue` | 596-647 |
|
|
|
|
|
|
| 第六步合同选择UI | `IndirectPayment.vue` | 884-956 |
|
|
|
|
|
|
| 合同选择器组件使用 | `IndirectPayment.vue` | 1162-1173 |
|
|
|
|
|
|
| 支出合同选择处理 | `IndirectPayment.vue` | 5885-5911 |
|
|
|
|
|
|
| 支付合同选择处理 | `IndirectPayment.vue` | 5930-5956 |
|
|
|
|
|
|
| 合同复用逻辑 | `IndirectPayment.vue` | 4992-4994 |
|
|
|
|
|
|
| 合同复用判断 | `IndirectPayment.vue` | 5974-5978 |
|
|
|
|
|
|
| 预览合同获取 | `IndirectPayment.vue` | 5819-5835 |
|
|
|
|
|
|
| 合同选择器组件 | `components/ContractSelector.vue` | 全部 |
|
|
|
|
|
|
|
|
|
|
|
|
## 七、数据流图
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
用户点击"选择合同"
|
|
|
|
|
|
↓
|
|
|
|
|
|
打开 ContractSelector 对话框
|
|
|
|
|
|
↓
|
|
|
|
|
|
用户搜索/浏览合同列表
|
|
|
|
|
|
↓
|
|
|
|
|
|
用户选择合同并点击"确定"
|
|
|
|
|
|
↓
|
|
|
|
|
|
ContractSelector 调用 contractAPI.detail(contractId)
|
|
|
|
|
|
↓
|
|
|
|
|
|
获取合同详情(包含 payment_stats)
|
|
|
|
|
|
↓
|
|
|
|
|
|
触发 @confirm 事件,传递合同详情
|
|
|
|
|
|
↓
|
|
|
|
|
|
父组件处理函数(handleExpenditureContractSelected / handlePaymentContractSelected)
|
|
|
|
|
|
↓
|
|
|
|
|
|
验证合同(闭口合同检查是否已关联过非直接支付)
|
|
|
|
|
|
↓
|
|
|
|
|
|
验证通过:保存到 selectedExpenditureContract / selectedPaymentContract
|
|
|
|
|
|
验证失败:显示警告,阻止选择
|
|
|
|
|
|
↓
|
|
|
|
|
|
进入第六步时,如果满足条件,自动复用第三步的合同
|
|
|
|
|
|
↓
|
|
|
|
|
|
预览和提交时使用合同数据
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 八、注意事项
|
|
|
|
|
|
|
|
|
|
|
|
1. **闭口合同限制**:
|
|
|
|
|
|
- 闭口合同一旦关联过非直接支付(`paid_amount > 0` 或 `payment_count > 0`),就不能再次关联
|
|
|
|
|
|
- 这个限制是为了保证数据一致性
|
|
|
|
|
|
|
|
|
|
|
|
2. **开口合同(框架协议)**:
|
|
|
|
|
|
- 开口合同不受"已关联过非直接支付"的限制
|
|
|
|
|
|
- 可以多次关联非直接支付
|
|
|
|
|
|
|
|
|
|
|
|
3. **合同复用**:
|
|
|
|
|
|
- 只有在第三步和第六步都需要合同时,才会自动复用
|
|
|
|
|
|
- 复用的合同在第六步不可更改
|
|
|
|
|
|
- 如果用户想使用不同的合同,需要在第三步清除合同,然后在第六步重新选择
|
|
|
|
|
|
|
|
|
|
|
|
4. **合同详情获取**:
|
|
|
|
|
|
- 选择器组件在确认选择时会调用 `contractAPI.detail()` 获取完整的合同详情
|
|
|
|
|
|
- 这确保了返回的合同数据包含最新的支付统计信息
|
|
|
|
|
|
|
|
|
|
|
|
5. **数据同步**:
|
|
|
|
|
|
- 合同选择后,相关的金额计算、预览显示等会自动更新
|
|
|
|
|
|
- 通过 Vue 的响应式系统实现数据同步
|
|
|
|
|
|
|
|
|
|
|
|
## 九、相关API
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 合同列表API
|
|
|
|
|
|
|
|
|
|
|
|
**接口**:`contractAPI.list(params)`
|
|
|
|
|
|
|
|
|
|
|
|
**参数**:
|
|
|
|
|
|
- `page`: 页码
|
|
|
|
|
|
- `page_size`: 每页数量
|
|
|
|
|
|
- `contract_no`: 合同编号(可选)
|
|
|
|
|
|
- `title`: 合同名称(可选)
|
|
|
|
|
|
|
|
|
|
|
|
**返回**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 0,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
data: [/* 合同列表 */],
|
|
|
|
|
|
total: 100
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 合同详情API
|
|
|
|
|
|
|
|
|
|
|
|
**接口**:`contractAPI.detail(contractId)`
|
|
|
|
|
|
|
|
|
|
|
|
**返回**:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
{
|
|
|
|
|
|
code: 0,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
id: 1,
|
|
|
|
|
|
contract_no: "HT2024001",
|
|
|
|
|
|
title: "合同名称",
|
|
|
|
|
|
amount_total: 1000000,
|
|
|
|
|
|
amount_type: "fixed", // "fixed" | "open"
|
|
|
|
|
|
payment_stats: {
|
|
|
|
|
|
payment_count: 0,
|
|
|
|
|
|
paid_amount: 0
|
|
|
|
|
|
},
|
|
|
|
|
|
// ... 其他合同字段
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 十、总结
|
|
|
|
|
|
|
|
|
|
|
|
"关联合同"选择带回的逻辑核心要点:
|
|
|
|
|
|
|
|
|
|
|
|
1. **两个选择位置**:第三步(支出合同)和第六步(支付合同)
|
|
|
|
|
|
2. **统一的选择器组件**:`ContractSelector` 提供搜索、列表、选择功能
|
|
|
|
|
|
3. **严格的验证规则**:闭口合同不能重复关联非直接支付
|
|
|
|
|
|
4. **智能的复用机制**:满足条件时自动复用第三步的合同
|
|
|
|
|
|
5. **完整的数据流**:从选择到验证到保存到使用的完整链路
|
|
|
|
|
|
|