xykj-plus/xykj-web-plus/backend-optimization-contract-list.md
2026-06-13 13:48:51 +08:00

239 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 合同列表查询优化建议
## 问题描述
合同列表需要显示发票相关的金额信息,需要关联发票表进行计算:
- **合同金额**:合同本身的金额
- **已开票金额**:该合同下所有发票的开票金额总和
- **已付款金额**:已付款的发票金额总和
- **未确认开票金额**:合同金额 - 已开票金额(即尚未开票的金额)
## 数据库表关系
```
biz_contract (合同表)
|
|-- contract_id (合同ID)
|
|-- biz_invoice (发票表)
|
|-- contract_id (关联合同ID)
|-- invoice_amount (开票金额)
|-- status (发票状态: 0=已开票未付款, 1=已开票已付款)
```
## SQL 查询优化建议
### 当前查询(可能缺少发票关联)
```sql
SELECT
bc.*,
COUNT(ba.attachment_id) as attachment_count
FROM biz_contract bc
LEFT JOIN biz_attachment ba ON bc.contract_id = ba.business_id AND ba.business_type = 'contract'
WHERE 1=1
-- 问题:缺少发票表的关联
```
### 优化后的查询
```sql
SELECT
bc.contract_id,
bc.contract_code,
bc.contract_name,
bc.contract_amount,
bc.contract_type,
-- 已开票金额(该合同下所有发票的总金额)
COALESCE(invoiced.total_invoiced_amount, 0) as invoiced_amount,
-- 已付款金额(已付款的发票总金额)
COALESCE(paid.total_paid_amount, 0) as paid_amount,
-- 未确认开票金额(合同金额 - 已开票金额)
bc.contract_amount - COALESCE(invoiced.total_invoiced_amount, 0) as unpaid_amount,
-- 附件数量
COALESCE(attachment_count, 0) as attachment_count
FROM biz_contract bc
-- 关联已开票金额统计
LEFT JOIN (
SELECT
contract_id,
SUM(invoice_amount) as total_invoiced_amount
FROM biz_invoice
WHERE status IN ('0', '1') -- 已开票状态
GROUP BY contract_id
) invoiced ON bc.contract_id = invoiced.contract_id
-- 关联已付款金额统计
LEFT JOIN (
SELECT
contract_id,
SUM(invoice_amount) as total_paid_amount
FROM biz_invoice
WHERE status = '1' -- 已付款状态
GROUP BY contract_id
) paid ON bc.contract_id = paid.contract_id
-- 附件数量统计
LEFT JOIN (
SELECT
business_id,
COUNT(*) as attachment_count
FROM biz_attachment
WHERE business_type = 'contract'
GROUP BY business_id
) attachments ON bc.contract_id = attachments.business_id
WHERE 1=1
ORDER BY bc.create_time DESC
```
## MyBatis-Plus 配置建议
### 方案1在 VO 类中添加统计字段
创建 `BizContractVO` 类:
```java
@Data
public class BizContractVO extends BizContract {
/** 已开票金额 */
private BigDecimal invoicedAmount;
/** 已付款金额 */
private BigDecimal paidAmount;
/** 未确认开票金额 */
private BigDecimal unpaidAmount;
/** 附件数量 */
private Integer attachmentCount;
}
```
### 方案2在 Mapper 中使用关联查询
```java
@Mapper
public interface BizContractMapper extends BaseMapper<BizContract> {
@Select("""
SELECT
bc.contract_id,
bc.contract_code,
bc.contract_name,
bc.contract_amount,
bc.contract_type,
bc.paid_amount,
bc.unpaid_amount,
COALESCE(invoiced.total_invoiced_amount, 0) as invoiced_amount,
bc.contract_amount - COALESCE(invoiced.total_invoiced_amount, 0) as unpaid_amount,
COALESCE(paid.total_paid_amount, 0) as paid_amount,
COALESCE(attachments.attachment_count, 0) as attachment_count
FROM biz_contract bc
LEFT JOIN (
SELECT contract_id, SUM(invoice_amount) as total_invoiced_amount
FROM biz_invoice
WHERE status IN ('0', '1')
GROUP BY contract_id
) invoiced ON bc.contract_id = invoiced.contract_id
LEFT JOIN (
SELECT contract_id, SUM(invoice_amount) as total_paid_amount
FROM biz_invoice
WHERE status = '1'
GROUP BY contract_id
) paid ON bc.contract_id = paid.contract_id
LEFT JOIN (
SELECT business_id, COUNT(*) as attachment_count
FROM biz_attachment
WHERE business_type = 'contract'
GROUP BY business_id
) attachments ON bc.contract_id = attachments.business_id
${ew.customSqlSegment}
""")
Page<BizContractVO> selectContractPage(
@Param("page") Page<BizContractVO> page,
@Param("ew") Wrapper<BizContract> queryWrapper
);
}
```
## 字段计算逻辑说明
### 1. 合同金额 (contract_amount)
- 直接使用合同表中的 `contract_amount` 字段
- 金额由合同创建时确定
### 2. 已开票金额 (invoiced_amount)
```sql
SUM(invoice_amount)
WHERE contract_id = ?
AND status IN ('0', '1') -- 已开票状态(已开票未付款 + 已开票已付款)
```
- 计算该合同下所有已开票的发票金额总和
- 不论是否付款,只要开票就算
### 3. 已付款金额 (paid_amount)
```sql
SUM(invoice_amount)
WHERE contract_id = ?
AND status = '1' -- 已付款状态
```
- 计算该合同下已付款的发票金额总和
- 只有状态为"已开票已付款"的发票才算
### 4. 未确认开票金额 (unpaid_amount)
```sql
contract_amount - invoiced_amount
```
- 合同金额减去已开票金额
- 表示尚未开票的金额
## 数据返回格式示例
```json
{
"code": 200,
"msg": "查询成功",
"rows": [
{
"contractId": 1,
"contractCode": "CONTRACT001",
"contractName": "技术服务合同",
"contractAmount": 100000.00,
"contractTypeName": "人月合同",
"invoicedAmount": 60000.00,
"paidAmount": 40000.00,
"unpaidAmount": 40000.00,
"attachmentCount": 2,
"createTime": "2024-01-01 10:00:00"
}
],
"total": 100
}
```
## 前端显示
| 列名 | 说明 | 示例 |
|------|------|------|
| 合同编号 | 合同的唯一编号 | CONTRACT001 |
| 合同名称 | 合同的名称 | 技术服务合同 |
| 合同金额(元) | 合同总金额 | 100,000.00 |
| 已开票金额(元) | 该合同已开票金额总和 | 60,000.00 |
| 已付款金额(元) | 该合同已付款金额总和 | 40,000.00 |
| 未付款金额(元) | 合同金额-已开票金额 | 40,000.00 |
## 注意事项
1. **LEFT JOIN 使用**:确保使用 LEFT JOIN避免因为没有发票数据的合同导致记录不显示
2. **金额计算精度**:使用 `DECIMAL``BIGINT` 类型存储金额,避免精度丢失
3. **NULL 值处理**:使用 `COALESCE(value, 0)` 将 NULL 转换为 0
4. **性能优化**:为关联字段添加索引
- `biz_invoice.contract_id`
- `biz_invoice.status`
5. **数据一致性**:确保发票状态正确更新(从"已开票未付款"到"已开票已付款"
## 验证方法
1. 查看合同列表API返回的数据是否包含 `invoicedAmount` 字段
2. 确认金额计算正确:
- `invoicedAmount` = 所有发票金额总和
- `paidAmount` = 已付款发票金额总和
- `unpaidAmount` = `contractAmount` - `invoicedAmount`
3. 前端列表能正确格式化显示金额(千分位分隔,保留两位小数)