09-发票管理API接口.md 24 KB

发票管理模块 - API接口文档

模块: 09-发票管理
版本: v1.0
日期: 2025-10-03
关联文档:

  • 需求文档: docs/09-发票管理模块需求.md
  • 原型图: yuanxing/09-发票管理原型图.md
  • 数据库: sql/crm/09-crm_invoice.sql

📋 目录


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 | 否 | 创建结束时间 |

返回示例:

{
  "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

请求体:

{
  "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(申请中)
  • 申请人自动设置为当前用户

返回示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 1,
    "invoiceNo": "INV20250125001"
  }
}

1.4 修改发票申请

接口地址: PUT /crm/invoice

接口描述: 修改发票申请信息

权限标识: crm:invoice:edit

请求体: 同新增接口,但需要传入id

业务规则:

  • 只有申请中状态的发票才能修改
  • 已开票的发票不允许修改

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.5 删除发票

接口地址: DELETE /crm/invoice/{ids}

接口描述: 删除发票(支持批量)

权限标识: crm:invoice:remove

请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | ids | Long[] | 是 | 发票ID数组 |

业务规则:

  • 只能删除申请中或已驳回状态的发票
  • 已开票的发票不允许删除

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.6 审批发票

接口地址: PUT /crm/invoice/approve

接口描述: 审批发票申请

权限标识: crm:invoice:approve

请求体:

{
  "id": 1,
  "approved": true,
  "rejectReason": "",
  "remark": "审批通过"
}

请求参数说明: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | approved | Boolean | 是 | 是否通过:true-通过,false-驳回 | | rejectReason | String | 否 | 驳回原因(驳回时必填),最大500字符 | | remark | String | 否 | 审批说明 |

业务规则:

  • 只有申请中状态的发票才能审批
  • 审批通过后状态变为approved
  • 审批驳回后状态变为rejected
  • 记录审批人和审批时间

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.7 开具发票

接口地址: PUT /crm/invoice/issue

接口描述: 开具发票

权限标识: crm:invoice:issue

请求体:

{
  "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(已开票)
  • 记录开票人和开票日期
  • 自动更新合同的已开票金额

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.8 寄出发票

接口地址: PUT /crm/invoice/express

接口描述: 寄出发票

权限标识: crm:invoice:express

请求体:

{
  "id": 1,
  "expressCompany": "顺丰速运",
  "expressNo": "SF1234567890",
  "remark": ""
}

请求参数说明: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | expressCompany | String | 是 | 快递公司,最大50字符 | | expressNo | String | 是 | 快递单号,最大100字符 | | remark | String | 否 | 寄出说明 |

业务规则:

  • 只有已开票状态的发票才能寄出
  • 状态变更为delivered(已寄出)
  • 快递状态变更为sent
  • 记录寄出时间

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.9 确认签收

接口地址: PUT /crm/invoice/receive

接口描述: 确认发票签收

权限标识: crm:invoice:receive

请求体:

{
  "id": 1,
  "remark": "客户已签收"
}

请求参数说明: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | remark | String | 否 | 签收说明 |

业务规则:

  • 只有已寄出状态的发票才能确认签收
  • 状态变更为received(已签收)
  • 快递状态变更为received
  • 记录签收时间

返回示例:

{
  "code": 200,
  "msg": "操作成功"
}

1.10 作废发票

接口地址: PUT /crm/invoice/invalid

接口描述: 作废发票

权限标识: crm:invoice:invalid

请求体:

{
  "id": 1,
  "invalidReason": "客户要求作废",
  "remark": ""
}

请求参数说明: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long | 是 | 发票ID | | invalidReason | String | 是 | 作废原因,最大500字符 | | remark | String | 否 | 备注 |

业务规则:

  • 已开票的发票可以作废
  • 状态变更为invalid(已作废)
  • 记录作废原因和作废时间
  • 需要更新合同的已开票金额

返回示例:

{
  "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字段

返回示例:

{
  "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字段

返回示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "fileUrl": "/upload/invoice/INV20250125001.jpg"
  }
}

1.13 查询合同发票

接口地址: GET /crm/invoice/contract/{contractId}

接口描述: 查询指定合同的所有发票

权限标识: crm:invoice:query

请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | contractId | Long | 是 | 合同ID |

返回示例:

{
  "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 | 否 | 结束时间 |

返回示例:

{
  "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

请求参数: 无

返回示例:

{
  "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

请求参数: 无

返回示例:

{
  "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 | 否 | 结束时间 |

返回示例:

{
  "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 | 否 | 结束时间 |

返回示例:

{
  "code": 200,
  "msg": "查询成功",
  "data": [
    {
      "ownerUserId": 1,
      "ownerUserName": "张三",
      "totalCount": 35,
      "totalAmount": 1850000.00,
      "invoicedCount": 28,
      "invoicedAmount": 1450000.00
    }
  ]
}

3. 数据模型

3.1 InvoiceBo(发票业务对象)

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(发票视图对象)

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. 关联关系: 发票关联合同和回款,形成完整的财务链条

文档结束