xykj-plus/xykj-web-plus/backend-optimization-contract-list.md

239 lines
7.1 KiB
Markdown
Raw Permalink Normal View History

# 合同列表查询优化建议
## 问题描述
合同列表需要显示发票相关的金额信息,需要关联发票表进行计算:
- **合同金额**:合同本身的金额
- **已开票金额**:该合同下所有发票的开票金额总和
- **已付款金额**:已付款的发票金额总和
- **未确认开票金额**:合同金额 - 已开票金额(即尚未开票的金额)
## 数据库表关系
```
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. 前端列表能正确格式化显示金额(千分位分隔,保留两位小数)