# 发票管理模块 - API接口文档 > **模块**: 09-发票管理 > **版本**: v1.0 > **日期**: 2025-10-03 > **关联文档**: > - 需求文档: `docs/09-发票管理模块需求.md` > - 原型图: `yuanxing/09-发票管理原型图.md` > - 数据库: `sql/crm/09-crm_invoice.sql` --- ## 📋 目录 - [1. 发票管理接口](#1-发票管理接口) - [2. 统计分析接口](#2-统计分析接口) - [3. 数据模型](#3-数据模型) --- ## 1. 发票管理接口 ### 1.1 查询发票列表(分页) **接口地址**: `GET /crm/invoice/list` **接口描述**: 分页查询发票列表,支持多条件筛选 **权限标识**: `crm:invoice:list` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | pageNum | Integer | 否 | 页码,默认1 | | pageSize | Integer | 否 | 每页条数,默认10 | | invoiceNo | String | 否 | 发票编号(模糊查询) | | invoiceNumber | String | 否 | 发票号码(模糊查询) | | contractId | Long | 否 | 合同ID | | contractNo | String | 否 | 合同编号(模糊查询) | | customerId | Long | 否 | 客户ID | | customerName | String | 否 | 客户名称(模糊查询) | | paymentId | Long | 否 | 回款ID | | invoiceType | String | 否 | 发票类型 | | invoiceTitle | String | 否 | 发票抬头(模糊查询) | | status | String | 否 | 发票状态 | | expressStatus | String | 否 | 快递状态 | | ownerUserId | Long | 否 | 负责人ID | | applyBy | Long | 否 | 申请人ID | | invoiceDateBegin | String | 否 | 开票日期开始 | | invoiceDateEnd | String | 否 | 开票日期结束 | | beginTime | String | 否 | 创建开始时间 | | endTime | String | 否 | 创建结束时间 | **返回示例**: ```json { "code": 200, "msg": "查询成功", "rows": [ { "id": 1, "invoiceNo": "INV20250125001", "invoiceCode": "044001800104", "invoiceNumber": "12345678", "contractId": 1, "contractNo": "CON20250115001", "customerId": 1, "customerName": "华为技术有限公司", "paymentId": 1, "paymentNo": "PAY20250120001", "invoiceType": "special", "invoiceTitle": "华为技术有限公司", "taxNo": "91440300xxxx", "companyAddress": "深圳市龙岗区xxx", "companyPhone": "0755-12345678", "bankName": "中国工商银行深圳分行", "bankAccount": "4000 0000 0000 1234", "amount": 38207.55, "taxRate": 6.00, "taxAmount": 2292.45, "totalAmount": 40500.00, "invoiceDate": "2025-01-25", "status": "invoiced", "invoiceFile": "/upload/invoice/INV20250125001.pdf", "invoiceImage": "/upload/invoice/INV20250125001.jpg", "expressCompany": "顺丰速运", "expressNo": "SF1234567890", "expressStatus": "sent", "expressTime": "2025-01-26 10:00:00", "mailAddress": "深圳市龙岗区xxx", "mailContact": "张伟", "mailMobile": "13800138000", "ownerUserId": 1, "ownerUserName": "张三", "applyBy": 1, "applyByName": "张三", "applyTime": "2025-01-25 09:00:00", "approveBy": 2, "approveByName": "李四", "approveTime": "2025-01-25 10:00:00", "invoiceBy": 3, "invoiceByName": "王五", "createTime": "2025-01-25 09:00:00", "remark": "" } ], "total": 125 } ``` --- ### 1.2 查询发票详情 **接口地址**: `GET /crm/invoice/{id}` **接口描述**: 根据ID查询发票详细信息 **权限标识**: `crm:invoice:query` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | **返回示例**: 同1.1列表返回的单条数据,包含完整字段 --- ### 1.3 新增发票申请 **接口地址**: `POST /crm/invoice` **接口描述**: 新增发票申请信息 **权限标识**: `crm:invoice:add` **请求体**: ```json { "invoiceNo": "INV20250125001", "contractId": 1, "customerId": 1, "paymentId": 1, "invoiceType": "special", "invoiceTitle": "华为技术有限公司", "taxNo": "91440300xxxx", "companyAddress": "深圳市龙岗区xxx", "companyPhone": "0755-12345678", "bankName": "中国工商银行深圳分行", "bankAccount": "4000 0000 0000 1234", "amount": 38207.55, "taxRate": 6.00, "mailAddress": "深圳市龙岗区xxx", "mailContact": "张伟", "mailMobile": "13800138000", "ownerUserId": 1, "remark": "" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | invoiceNo | String | 是 | 发票编号,唯一,格式:INV+yyyyMMdd+4位流水号 | | contractId | Long | 是 | 合同ID | | customerId | Long | 是 | 客户ID | | paymentId | Long | 否 | 回款ID | | invoiceType | String | 是 | 发票类型:special/normal/electronic | | invoiceTitle | String | 是 | 发票抬头,最大200字符 | | taxNo | String | 是 | 纳税人识别号,最大50字符 | | companyAddress | String | 否 | 企业地址,最大255字符 | | companyPhone | String | 否 | 企业电话,最大50字符 | | bankName | String | 否 | 开户银行,最大100字符 | | bankAccount | String | 否 | 银行账号,最大100字符 | | amount | BigDecimal | 是 | 开票金额(不含税),保留2位小数 | | taxRate | BigDecimal | 是 | 税率(百分比),默认6.00 | | mailAddress | String | 是 | 邮寄地址,最大255字符 | | mailContact | String | 是 | 收件人,最大50字符 | | mailMobile | String | 是 | 收件电话,最大20字符 | | ownerUserId | Long | 否 | 负责人ID,默认当前用户 | | remark | String | 否 | 备注,最大500字符 | **校验规则**: - 发票编号必须唯一 - 开票金额必须大于0 - 合同ID必须存在 - 纳税人识别号格式验证 **业务规则**: - 自动计算税额:amount * taxRate / 100 - 自动计算价税合计:amount + taxAmount - 默认状态为applying(申请中) - 申请人自动设置为当前用户 **返回示例**: ```json { "code": 200, "msg": "操作成功", "data": { "id": 1, "invoiceNo": "INV20250125001" } } ``` --- ### 1.4 修改发票申请 **接口地址**: `PUT /crm/invoice` **接口描述**: 修改发票申请信息 **权限标识**: `crm:invoice:edit` **请求体**: 同新增接口,但需要传入id **业务规则**: - 只有申请中状态的发票才能修改 - 已开票的发票不允许修改 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.5 删除发票 **接口地址**: `DELETE /crm/invoice/{ids}` **接口描述**: 删除发票(支持批量) **权限标识**: `crm:invoice:remove` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | ids | Long[] | 是 | 发票ID数组 | **业务规则**: - 只能删除申请中或已驳回状态的发票 - 已开票的发票不允许删除 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.6 审批发票 **接口地址**: `PUT /crm/invoice/approve` **接口描述**: 审批发票申请 **权限标识**: `crm:invoice:approve` **请求体**: ```json { "id": 1, "approved": true, "rejectReason": "", "remark": "审批通过" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | approved | Boolean | 是 | 是否通过:true-通过,false-驳回 | | rejectReason | String | 否 | 驳回原因(驳回时必填),最大500字符 | | remark | String | 否 | 审批说明 | **业务规则**: - 只有申请中状态的发票才能审批 - 审批通过后状态变为approved - 审批驳回后状态变为rejected - 记录审批人和审批时间 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.7 开具发票 **接口地址**: `PUT /crm/invoice/issue` **接口描述**: 开具发票 **权限标识**: `crm:invoice:issue` **请求体**: ```json { "id": 1, "invoiceCode": "044001800104", "invoiceNumber": "12345678", "invoiceDate": "2025-01-25", "remark": "" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | invoiceCode | String | 是 | 发票代码,最大50字符 | | invoiceNumber | String | 是 | 发票号码,最大50字符 | | invoiceDate | String | 是 | 开票日期 yyyy-MM-dd | | remark | String | 否 | 开票说明 | **业务规则**: - 只有已审批状态的发票才能开票 - 状态变更为invoiced(已开票) - 记录开票人和开票日期 - 自动更新合同的已开票金额 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.8 寄出发票 **接口地址**: `PUT /crm/invoice/express` **接口描述**: 寄出发票 **权限标识**: `crm:invoice:express` **请求体**: ```json { "id": 1, "expressCompany": "顺丰速运", "expressNo": "SF1234567890", "remark": "" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | expressCompany | String | 是 | 快递公司,最大50字符 | | expressNo | String | 是 | 快递单号,最大100字符 | | remark | String | 否 | 寄出说明 | **业务规则**: - 只有已开票状态的发票才能寄出 - 状态变更为delivered(已寄出) - 快递状态变更为sent - 记录寄出时间 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.9 确认签收 **接口地址**: `PUT /crm/invoice/receive` **接口描述**: 确认发票签收 **权限标识**: `crm:invoice:receive` **请求体**: ```json { "id": 1, "remark": "客户已签收" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | remark | String | 否 | 签收说明 | **业务规则**: - 只有已寄出状态的发票才能确认签收 - 状态变更为received(已签收) - 快递状态变更为received - 记录签收时间 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.10 作废发票 **接口地址**: `PUT /crm/invoice/invalid` **接口描述**: 作废发票 **权限标识**: `crm:invoice:invalid` **请求体**: ```json { "id": 1, "invalidReason": "客户要求作废", "remark": "" } ``` **请求参数说明**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | invalidReason | String | 是 | 作废原因,最大500字符 | | remark | String | 否 | 备注 | **业务规则**: - 已开票的发票可以作废 - 状态变更为invalid(已作废) - 记录作废原因和作废时间 - 需要更新合同的已开票金额 **返回示例**: ```json { "code": 200, "msg": "操作成功" } ``` --- ### 1.11 上传发票文件 **接口地址**: `POST /crm/invoice/uploadFile` **接口描述**: 上传发票PDF文件 **权限标识**: `crm:invoice:edit` **请求参数**: multipart/form-data | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | file | File | 是 | 发票文件(PDF) | **业务规则**: - 支持PDF格式 - 文件大小不超过10MB - 更新invoiceFile字段 **返回示例**: ```json { "code": 200, "msg": "操作成功", "data": { "fileUrl": "/upload/invoice/INV20250125001.pdf" } } ``` --- ### 1.12 上传发票图片 **接口地址**: `POST /crm/invoice/uploadImage` **接口描述**: 上传发票扫描件图片 **权限标识**: `crm:invoice:edit` **请求参数**: multipart/form-data | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | file | File | 是 | 发票图片(JPG/PNG) | **业务规则**: - 支持JPG、PNG格式 - 文件大小不超过5MB - 更新invoiceImage字段 **返回示例**: ```json { "code": 200, "msg": "操作成功", "data": { "fileUrl": "/upload/invoice/INV20250125001.jpg" } } ``` --- ### 1.13 查询合同发票 **接口地址**: `GET /crm/invoice/contract/{contractId}` **接口描述**: 查询指定合同的所有发票 **权限标识**: `crm:invoice:query` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | contractId | Long | 是 | 合同ID | **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": [ { "id": 1, "invoiceNo": "INV20250125001", "invoiceNumber": "12345678", "invoiceType": "special", "totalAmount": 40500.00, "invoiceDate": "2025-01-25", "status": "delivered" } ] } ``` --- ### 1.14 导出发票 **接口地址**: `POST /crm/invoice/export` **接口描述**: 导出发票数据到Excel **权限标识**: `crm:invoice:export` **请求体**: 查询参数同1.1列表查询接口 **返回**: Excel文件流 --- ## 2. 统计分析接口 ### 2.1 发票统计概览 **接口地址**: `GET /crm/invoice/statistics/overview` **接口描述**: 获取发票统计概览数据 **权限标识**: `crm:invoice:list` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | ownerUserId | Long | 否 | 负责人ID,不传则查询当前用户 | | beginTime | String | 否 | 开始时间 | | endTime | String | 否 | 结束时间 | **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": { "totalCount": 125, "applyingCount": 15, "approvedCount": 8, "invoicedCount": 65, "deliveredCount": 25, "receivedCount": 10, "rejectedCount": 2, "invalidCount": 0, "totalAmount": 5680000.00, "avgAmount": 45440.00 } } ``` --- ### 2.2 发票状态分布 **接口地址**: `GET /crm/invoice/statistics/status` **接口描述**: 按状态统计发票分布情况 **权限标识**: `crm:invoice:list` **请求参数**: 无 **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": [ { "status": "invoiced", "statusName": "已开票", "count": 65, "amount": 3850000.00, "percent": 52.00 } ] } ``` --- ### 2.3 发票类型分布 **接口地址**: `GET /crm/invoice/statistics/type` **接口描述**: 按发票类型统计分布情况 **权限标识**: `crm:invoice:list` **请求参数**: 无 **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": [ { "invoiceType": "special", "typeName": "增值税专用发票", "count": 85, "amount": 4580000.00, "percent": 68.00 }, { "invoiceType": "normal", "typeName": "增值税普通发票", "count": 35, "amount": 950000.00, "percent": 28.00 } ] } ``` --- ### 2.4 发票趋势分析 **接口地址**: `GET /crm/invoice/statistics/trend` **接口描述**: 分析发票数量和金额趋势 **权限标识**: `crm:invoice:list` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | type | String | 否 | 统计类型:month/quarter/year,默认month | | beginTime | String | 否 | 开始时间 | | endTime | String | 否 | 结束时间 | **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": [ { "period": "2025-01", "totalCount": 28, "totalAmount": 1580000.00, "invoicedCount": 22, "invoicedAmount": 1250000.00 } ] } ``` --- ### 2.5 负责人发票统计 **接口地址**: `GET /crm/invoice/statistics/owner` **接口描述**: 按负责人统计发票情况 **权限标识**: `crm:invoice:list` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | beginTime | String | 否 | 开始时间 | | endTime | String | 否 | 结束时间 | **返回示例**: ```json { "code": 200, "msg": "查询成功", "data": [ { "ownerUserId": 1, "ownerUserName": "张三", "totalCount": 35, "totalAmount": 1850000.00, "invoicedCount": 28, "invoicedAmount": 1450000.00 } ] } ``` --- ## 3. 数据模型 ### 3.1 InvoiceBo(发票业务对象) ```java public class CrmInvoiceBo extends BaseEntity { /** 主键ID */ @NotNull(message = "主键不能为空", groups = { EditGroup.class }) private Long id; /** 发票编号 */ @NotBlank(message = "发票编号不能为空", groups = { AddGroup.class, EditGroup.class }) private String invoiceNo; /** 发票代码 */ private String invoiceCode; /** 发票号码 */ private String invoiceNumber; /** 合同ID */ @NotNull(message = "合同ID不能为空", groups = { AddGroup.class, EditGroup.class }) private Long contractId; /** 客户ID */ @NotNull(message = "客户ID不能为空", groups = { AddGroup.class, EditGroup.class }) private Long customerId; /** 回款ID */ private Long paymentId; /** 发票类型 */ @NotBlank(message = "发票类型不能为空", groups = { AddGroup.class, EditGroup.class }) private String invoiceType; /** 发票抬头 */ @NotBlank(message = "发票抬头不能为空", groups = { AddGroup.class, EditGroup.class }) private String invoiceTitle; /** 纳税人识别号 */ @NotBlank(message = "纳税人识别号不能为空", groups = { AddGroup.class, EditGroup.class }) private String taxNo; /** 企业地址 */ private String companyAddress; /** 企业电话 */ private String companyPhone; /** 开户银行 */ private String bankName; /** 银行账号 */ private String bankAccount; /** 开票金额(不含税) */ @NotNull(message = "开票金额不能为空", groups = { AddGroup.class, EditGroup.class }) @Min(value = 0, message = "开票金额必须大于0") private BigDecimal amount; /** 税率 */ @NotNull(message = "税率不能为空", groups = { AddGroup.class, EditGroup.class }) private BigDecimal taxRate; /** 开票日期 */ private String invoiceDate; /** 邮寄地址 */ @NotBlank(message = "邮寄地址不能为空", groups = { AddGroup.class, EditGroup.class }) private String mailAddress; /** 收件人 */ @NotBlank(message = "收件人不能为空", groups = { AddGroup.class, EditGroup.class }) private String mailContact; /** 收件电话 */ @NotBlank(message = "收件电话不能为空", groups = { AddGroup.class, EditGroup.class }) private String mailMobile; /** 负责人ID */ private Long ownerUserId; /** 备注 */ private String remark; } ``` --- ### 3.2 InvoiceVo(发票视图对象) ```java public class CrmInvoiceVo implements Serializable { /** 主键ID */ @ExcelProperty(value = "ID") private Long id; /** 发票编号 */ @ExcelProperty(value = "发票编号") private String invoiceNo; /** 发票代码 */ @ExcelProperty(value = "发票代码") private String invoiceCode; /** 发票号码 */ @ExcelProperty(value = "发票号码") private String invoiceNumber; /** 合同ID */ private Long contractId; /** 合同编号 */ @ExcelProperty(value = "合同编号") private String contractNo; /** 客户ID */ private Long customerId; /** 客户名称 */ @ExcelProperty(value = "客户名称") private String customerName; /** 回款ID */ private Long paymentId; /** 回款编号 */ private String paymentNo; /** 发票类型 */ @ExcelProperty(value = "发票类型", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "crm_invoice_type") private String invoiceType; /** 发票抬头 */ @ExcelProperty(value = "发票抬头") private String invoiceTitle; /** 纳税人识别号 */ @ExcelProperty(value = "纳税人识别号") private String taxNo; /** 企业地址 */ private String companyAddress; /** 企业电话 */ private String companyPhone; /** 开户银行 */ private String bankName; /** 银行账号 */ private String bankAccount; /** 开票金额(不含税) */ @ExcelProperty(value = "开票金额") private BigDecimal amount; /** 税率 */ @ExcelProperty(value = "税率") private BigDecimal taxRate; /** 税额 */ @ExcelProperty(value = "税额") private BigDecimal taxAmount; /** 价税合计 */ @ExcelProperty(value = "价税合计") private BigDecimal totalAmount; /** 开票日期 */ @ExcelProperty(value = "开票日期") private String invoiceDate; /** 发票状态 */ @ExcelProperty(value = "发票状态", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "crm_invoice_status") private String status; /** 驳回原因 */ private String rejectReason; /** 作废原因 */ private String invalidReason; /** 作废时间 */ private LocalDateTime invalidTime; /** 发票PDF文件 */ private String invoiceFile; /** 发票图片 */ private String invoiceImage; /** 快递公司 */ @ExcelProperty(value = "快递公司") private String expressCompany; /** 快递单号 */ @ExcelProperty(value = "快递单号") private String expressNo; /** 快递状态 */ @ExcelProperty(value = "快递状态", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "crm_express_status") private String expressStatus; /** 寄出时间 */ private LocalDateTime expressTime; /** 签收时间 */ private LocalDateTime receiveTime; /** 邮寄地址 */ private String mailAddress; /** 收件人 */ @ExcelProperty(value = "收件人") private String mailContact; /** 收件电话 */ private String mailMobile; /** 负责人ID */ private Long ownerUserId; /** 负责人姓名 */ @ExcelProperty(value = "负责人") private String ownerUserName; /** 申请人ID */ private Long applyBy; /** 申请人姓名 */ @ExcelProperty(value = "申请人") private String applyByName; /** 申请时间 */ @ExcelProperty(value = "申请时间") private LocalDateTime applyTime; /** 审批人ID */ private Long approveBy; /** 审批人姓名 */ @ExcelProperty(value = "审批人") private String approveByName; /** 审批时间 */ private LocalDateTime approveTime; /** 开票人ID */ private Long invoiceBy; /** 开票人姓名 */ @ExcelProperty(value = "开票人") private String invoiceByName; /** 创建时间 */ @ExcelProperty(value = "创建时间") private LocalDateTime createTime; /** 备注 */ @ExcelProperty(value = "备注") private String remark; } ``` --- ## 4. 数据字典 ### 4.1 发票类型 (crm_invoice_type) | 字典值 | 字典标签 | 说明 | |--------|---------|------| | special | 增值税专用发票 | 可抵扣进项税 | | normal | 增值税普通发票 | 不可抵扣 | | electronic | 电子发票 | 电子版发票 | --- ### 4.2 发票状态 (crm_invoice_status) | 字典值 | 字典标签 | 说明 | |--------|---------|------| | applying | 申请中 | 发票申请中 | | approved | 已审批 | 审批通过 | | invoiced | 已开票 | 已开具发票 | | delivered | 已寄出 | 已寄出发票 | | received | 已签收 | 客户已签收 | | rejected | 已驳回 | 审批驳回 | | invalid | 已作废 | 发票作废 | --- ### 4.3 快递状态 (crm_express_status) | 字典值 | 字典标签 | 说明 | |--------|---------|------| | no | 未寄出 | 尚未寄出 | | sent | 已寄出 | 已寄出 | | received | 已签收 | 已签收 | --- ## 5. 权限标识汇总 | 权限标识 | 说明 | |---------|------| | crm:invoice:list | 发票列表查询 | | crm:invoice:query | 发票详情查询 | | crm:invoice:add | 发票新增 | | crm:invoice:edit | 发票修改 | | crm:invoice:remove | 发票删除 | | crm:invoice:export | 发票导出 | | crm:invoice:approve | 发票审批 | | crm:invoice:issue | 发票开具 | | crm:invoice:express | 发票寄出 | | crm:invoice:receive | 确认签收 | | crm:invoice:invalid | 发票作废 | --- ## 6. 注意事项 1. **发票编号唯一性**: 同一租户下发票编号必须唯一 2. **发票号码唯一性**: 发票号码应该全局唯一(税务要求) 3. **金额自动计算**: 自动计算税额和价税合计 4. **状态流转**: 申请中->已审批->已开票->已寄出->已签收 5. **审批流程**: 发票申请需要审批通过后才能开票 6. **开票信息**: 专用发票需要完整的开票信息(地址、电话、银行) 7. **快递跟踪**: 记录快递公司和快递单号,方便跟踪 8. **作废处理**: 已开票的发票作废需要更新合同的开票金额 9. **数据权限**: 需要根据负责人字段实现数据权限控制 10. **关联关系**: 发票关联合同和回款,形成完整的财务链条 --- **文档结束**