feat: Implement initial customer authorization code and invoice management modules with API documentation and configuration.

This commit is contained in:
wood911010 committed 2026-02-24 18:50:47 +08:00
1 parent c3d0f4aa2a
commit b65b4ac801
10 files changed
+1099 -642

No files matched your search

@@ -0,0 +1,80 @@
# 2026.2.24-用户端发票申请修改
## 1. 需求背景
目前用户申请发票后,如果被管理员驳回(状态为 `rejected`),用户无法修改该申请信息并重新提交。由于每个订单只能关联一个发票记录,用户也无法为该订单重新发起新的申请。因此需要增加一个发票修改接口,允许用户在驳回状态下编辑发票信息并重新进入待处理状态。
## 2. 业务流程
1. **用户侧**:查看发票详情,发现状态为“已驳回”,查看“驳回原因”和“修改建议”。
2. **用户侧**:点击修改,编辑发票抬头、税号、邮箱等信息。
3. **提交修改**:调用修改接口。
- 系统校验发票当前状态必须为 `rejected`。
- 系统校验发票所属权。
- 系统更新发票信息,并将状态重置为 `pending`。
- 系统清空原有的驳回信息(可选,或保留历史但在界面不再作为当前驳回提示)。
4. **管理侧**:发票重新出现在待处理列表中。
## 3. 接口设计
### 3.1 修改发票申请 (客户端)
- **接口地址**:`PUT /api/cu/invoices/{id}`
- **鉴权**:需要客户端登录态 (`CustomerAuth`)
- **请求参数 (JSON Body)**:
| 字段名 | 类型 | 必填 | 说明 |
| :--- | :--- | :--- | :--- |
| invoice_type | string | 是 | 发票类型: personal/enterprise/vat_special |
| receiver_email| string | 是 | 收票邮箱 |
| title | string | 是 | 发票抬头 |
| taxpayer_id | string | 否 | 纳税人识别号 (企业类型必填) |
| content | string | 否 | 开票内容 |
| remark | string | 否 | 备注 |
> [!NOTE]
> `order_id` 不允许修改,因为发票记录与订单是一一绑定的。
- **逻辑校验**:
1. **存在性校验**:发票 ID 必须存在。
2. **权限校验**:发票必须属于当前登录的客户用户 (`cu_user_id`)。
3. **状态校验**:只有 `status = 'rejected'` 的发票允许修改。若状态为 `pending` 或 `issued`,则返回错误。
4. **字段校验**:与创建发票时的校验逻辑一致(如企业类型必须有税号)。
- **数据更新**:
- 更新传入的业务字段。
- `status` 更新为 `pending`。
- `updated_at` 更新为当前时间。
- `reject_reason`、`suggestion`、`rejected_at`、`rejected_by` 建议清空,以表示这是一次全新的提交。
### 3.2 响应示例
**成功响应**:
```json
{
"code": "000000",
"message": "成功",
"data": {
"id": "invoice-uuid",
"invoice_no": "INV20260224001",
"status": "pending"
},
"timestamp": "2026-02-24T18:00:00Z"
}
```
**失败响应 (状态错误)**:
```json
{
"code": "700004",
"message": "发票当前状态不允许该操作",
"timestamp": "2026-02-24T18:00:00Z"
}
```
## 4. 影响范围
- **后端模型**:需新增 `InvoiceUpdateRequest` 结构体。
- **后端服务**:`CuInvoiceService` 新增 `UpdateInvoice` 方法。
- **后端接口**:`router.go` 注册新路由。
- **前端**:发票详情页增加修改按钮(仅在驳回时显示),跳转至编辑页。
## 5. 错误码补充
- `700006`: 发票当前状态不允许修改 (如果 700004 语义不够具体可以新增,或沿用 700004)。
+3 -1
View File
@@ -2,8 +2,10 @@
```bash
# 使用默认配置
go run cmd/main.go
cd cmd
go run .
# 指定配置文件
cd cmd
go run . -config configs/config.dev.yaml
```
+342 -278
View File
@@ -1,17 +1,10 @@
// Package swagger GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
// Code generated by swaggo/swag. DO NOT EDIT.
package swagger
import (
"bytes"
"encoding/json"
"strings"
"text/template"
import "github.com/swaggo/swag"
"github.com/swaggo/swag"
)
var doc = `{
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
@@ -737,25 +730,18 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"id": {
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
}
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
]
}
}
}
}
@@ -912,6 +898,103 @@ var doc = `{
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "客户修改已被驳回的发票申请并重新提交",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"发票管理"
],
"summary": "客户修改发票申请",
"parameters": [
{
"type": "string",
"description": "发票ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "发票修改请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.InvoiceUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
}
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "发票不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/invoices/{id}/download": {
@@ -1011,52 +1094,38 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"token": {
"type": "string"
},
{
"user": {
"type": "object",
"properties": {
"token": {
"customer_id": {
"type": "string"
},
"user": {
"allOf": [
{
"type": "object"
},
{
"type": "object",
"properties": {
"customer_id": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
}
}
}
}
@@ -1220,34 +1289,27 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
{
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
"id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
}
"id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
]
}
}
}
}
@@ -1306,22 +1368,15 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"license_count": {
"type": "integer"
},
{
"type": "object",
"properties": {
"license_count": {
"type": "integer"
},
"package_id": {
"type": "string"
}
}
"package_id": {
"type": "string"
}
]
}
}
}
],
@@ -1337,31 +1392,24 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"discount_description": {
"type": "string"
},
{
"type": "object",
"properties": {
"discount_description": {
"type": "string"
},
"discount_rate": {
"type": "number"
},
"license_count": {
"type": "integer"
},
"total_amount": {
"type": "number"
},
"unit_price": {
"type": "number"
}
}
"discount_rate": {
"type": "number"
},
"license_count": {
"type": "integer"
},
"total_amount": {
"type": "number"
},
"unit_price": {
"type": "number"
}
]
}
}
}
}
@@ -1543,19 +1591,19 @@ var doc = `{
}
},
"400": {
"description": ""
"description": "Bad Request"
},
"401": {
"description": ""
"description": "Unauthorized"
},
"403": {
"description": ""
"description": "Forbidden"
},
"404": {
"description": ""
"description": "Not Found"
},
"409": {
"description": ""
"description": "Conflict"
}
}
}
@@ -1609,34 +1657,27 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
{
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
"order_id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
}
"order_id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
]
}
}
}
}
@@ -1697,31 +1738,24 @@ var doc = `{
"data": {
"type": "array",
"items": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"id": {
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"max_devices": {
"type": "integer"
},
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"type": {
"type": "string"
}
}
"max_devices": {
"type": "integer"
},
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"type": {
"type": "string"
}
]
}
}
}
}
@@ -2128,52 +2162,38 @@ var doc = `{
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"token": {
"type": "string"
},
{
"user": {
"type": "object",
"properties": {
"token": {
"customer_id": {
"type": "string"
},
"user": {
"allOf": [
{
"type": "object"
},
{
"type": "object",
"properties": {
"customer_id": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
}
}
}
}
@@ -6506,7 +6526,11 @@ var doc = `{
},
"customer_info": {
"description": "客户信息(仅在详情接口返回)",
"$ref": "#/definitions/models.CustomerInfoForAuthCode"
"allOf": [
{
"$ref": "#/definitions/models.CustomerInfoForAuthCode"
}
]
},
"customer_name": {
"description": "客户名称",
@@ -6815,7 +6839,11 @@ var doc = `{
"properties": {
"new_authorization_code": {
"description": "新生成的授权码信息",
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
}
]
}
}
},
@@ -7133,6 +7161,10 @@ var doc = `{
"description": "订单过期时间",
"type": "string"
},
"has_invoice_applied": {
"description": "是否已申请发票(根据 invoices 表判断)",
"type": "boolean"
},
"id": {
"description": "订单ID",
"type": "string"
@@ -7528,7 +7560,11 @@ var doc = `{
},
"authorization_stats": {
"description": "授权统计信息(仅在详情接口返回)",
"$ref": "#/definitions/models.AuthorizationStats"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationStats"
}
]
},
"company_size": {
"type": "string"
@@ -7853,11 +7889,19 @@ var doc = `{
"properties": {
"period": {
"description": "时间段信息",
"$ref": "#/definitions/models.TrendPeriod"
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod"
}
]
},
"summary": {
"description": "汇总信息",
"$ref": "#/definitions/models.TrendSummary"
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary"
}
]
},
"trend_data": {
"description": "趋势数据",
@@ -7893,7 +7937,11 @@ var doc = `{
},
"authorization_info": {
"description": "授权信息",
"$ref": "#/definitions/models.AuthorizationInfo"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationInfo"
}
]
},
"device_info": {
"description": "设备信息",
@@ -8489,6 +8537,47 @@ var doc = `{
}
}
},
"models.InvoiceUpdateRequest": {
"type": "object",
"required": [
"invoice_type",
"receiver_email",
"title"
],
"properties": {
"content": {
"description": "开票内容,非必填,最多200字符",
"type": "string",
"maxLength": 200
},
"invoice_type": {
"description": "发票类型,必填,personal-个人/enterprise-企业普票/vat_special-增值税专用发票",
"type": "string",
"enum": [
"personal",
"enterprise",
"vat_special"
]
},
"receiver_email": {
"description": "收票邮箱,必填,格式为邮箱地址",
"type": "string"
},
"remark": {
"description": "备注,可选",
"type": "string"
},
"taxpayer_id": {
"description": "纳税人识别号,企业发票必填,个人发票可为空",
"type": "string"
},
"title": {
"description": "发票抬头,必填,最多200字符",
"type": "string",
"maxLength": 200
}
}
},
"models.InvoiceUploadResponse": {
"type": "object",
"properties": {
@@ -8717,7 +8806,11 @@ var doc = `{
},
"authorization_code": {
"description": "关联字段(用于查询时的JOIN)",
"$ref": "#/definitions/models.AuthorizationCode"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCode"
}
]
},
"authorization_code_id": {
"type": "string"
@@ -9303,7 +9396,11 @@ var doc = `{
},
"business_order": {
"description": "关联的业务订单信息",
"$ref": "#/definitions/models.PaymentBusinessOrder"
"allOf": [
{
"$ref": "#/definitions/models.PaymentBusinessOrder"
}
]
},
"payment_no": {
"description": "支付单号",
@@ -9418,7 +9515,11 @@ var doc = `{
},
"growth_rate": {
"description": "增长率(同比上月)",
"$ref": "#/definitions/models.GrowthRate"
"allOf": [
{
"$ref": "#/definitions/models.GrowthRate"
}
]
},
"total_auth_codes": {
"description": "总授权码数量(所有授权码的累计总数)",
@@ -9512,6 +9613,7 @@ var doc = `{
},
"securityDefinitions": {
"BearerAuth": {
"description": "Bearer token for authentication",
"type": "apiKey",
"name": "Authorization",
"in": "header"
@@ -9519,56 +9621,18 @@ var doc = `{
}
}`
type swaggerInfo struct {
Version string
Host string
BasePath string
Schemes []string
Title string
Description string
}
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{
Version: "1.0",
Host: "",
BasePath: "/",
Schemes: []string{"http", "https"},
Title: "License Manager API",
Description: "Bearer token for authentication",
}
type s struct{}
func (s *s) ReadDoc() string {
sInfo := SwaggerInfo
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
t, err := template.New("swagger_info").Funcs(template.FuncMap{
"marshal": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
"escape": func(v interface{}) string {
// escape tabs
str := strings.Replace(v.(string), "\t", "\\t", -1)
// replace " with \", and if that results in \\", replace that with \\\"
str = strings.Replace(str, "\"", "\\\"", -1)
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
},
}).Parse(doc)
if err != nil {
return doc
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, sInfo); err != nil {
return doc
}
return tpl.String()
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "/",
Schemes: []string{"http", "https"},
Title: "License Manager API",
Description: "软件授权管理平台API文档",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register("swagger", &s{})
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}
+329 -220
View File
@@ -5,7 +5,7 @@
],
"swagger": "2.0",
"info": {
"description": "Bearer token for authentication",
"description": "软件授权管理平台API文档",
"title": "License Manager API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
@@ -726,25 +726,18 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"id": {
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
}
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
]
}
}
}
}
@@ -901,6 +894,103 @@
}
}
}
},
"put": {
"security": [
{
"BearerAuth": []
}
],
"description": "客户修改已被驳回的发票申请并重新提交",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"发票管理"
],
"summary": "客户修改发票申请",
"parameters": [
{
"type": "string",
"description": "发票ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "发票修改请求",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.InvoiceUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"invoice_no": {
"type": "string"
},
"status": {
"type": "string"
}
}
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"403": {
"description": "权限不足",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"404": {
"description": "发票不存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/cu/invoices/{id}/download": {
@@ -1000,52 +1090,38 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"token": {
"type": "string"
},
{
"user": {
"type": "object",
"properties": {
"token": {
"customer_id": {
"type": "string"
},
"user": {
"allOf": [
{
"type": "object"
},
{
"type": "object",
"properties": {
"customer_id": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
}
}
}
}
@@ -1209,34 +1285,27 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
{
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
"id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
}
"id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
]
}
}
}
}
@@ -1295,22 +1364,15 @@
"in": "body",
"required": true,
"schema": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"license_count": {
"type": "integer"
},
{
"type": "object",
"properties": {
"license_count": {
"type": "integer"
},
"package_id": {
"type": "string"
}
}
"package_id": {
"type": "string"
}
]
}
}
}
],
@@ -1326,31 +1388,24 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"discount_description": {
"type": "string"
},
{
"type": "object",
"properties": {
"discount_description": {
"type": "string"
},
"discount_rate": {
"type": "number"
},
"license_count": {
"type": "integer"
},
"total_amount": {
"type": "number"
},
"unit_price": {
"type": "number"
}
}
"discount_rate": {
"type": "number"
},
"license_count": {
"type": "integer"
},
"total_amount": {
"type": "number"
},
"unit_price": {
"type": "number"
}
]
}
}
}
}
@@ -1532,19 +1587,19 @@
}
},
"400": {
"description": ""
"description": "Bad Request"
},
"401": {
"description": ""
"description": "Unauthorized"
},
"403": {
"description": ""
"description": "Forbidden"
},
"404": {
"description": ""
"description": "Not Found"
},
"409": {
"description": ""
"description": "Conflict"
}
}
}
@@ -1598,34 +1653,27 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
{
"type": "object",
"properties": {
"expire_time": {
"type": "string"
},
"order_id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
}
"order_id": {
"type": "string"
},
"order_no": {
"type": "string"
},
"payment_no": {
"type": "string"
},
"payment_url": {
"type": "string"
},
"total_amount": {
"type": "number"
}
]
}
}
}
}
@@ -1686,31 +1734,24 @@
"data": {
"type": "array",
"items": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"id": {
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"max_devices": {
"type": "integer"
},
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"type": {
"type": "string"
}
}
"max_devices": {
"type": "integer"
},
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"type": {
"type": "string"
}
]
}
}
}
}
@@ -2117,52 +2158,38 @@
"type": "object",
"properties": {
"data": {
"allOf": [
{
"type": "object"
"type": "object",
"properties": {
"token": {
"type": "string"
},
{
"user": {
"type": "object",
"properties": {
"token": {
"customer_id": {
"type": "string"
},
"user": {
"allOf": [
{
"type": "object"
},
{
"type": "object",
"properties": {
"customer_id": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
"email": {
"type": "string"
},
"id": {
"type": "string"
},
"phone": {
"type": "string"
},
"real_name": {
"type": "string"
},
"status": {
"type": "string"
},
"user_role": {
"type": "string"
}
}
}
]
}
}
}
}
@@ -6495,7 +6522,11 @@
},
"customer_info": {
"description": "客户信息(仅在详情接口返回)",
"$ref": "#/definitions/models.CustomerInfoForAuthCode"
"allOf": [
{
"$ref": "#/definitions/models.CustomerInfoForAuthCode"
}
]
},
"customer_name": {
"description": "客户名称",
@@ -6804,7 +6835,11 @@
"properties": {
"new_authorization_code": {
"description": "新生成的授权码信息",
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
}
]
}
}
},
@@ -7122,6 +7157,10 @@
"description": "订单过期时间",
"type": "string"
},
"has_invoice_applied": {
"description": "是否已申请发票(根据 invoices 表判断)",
"type": "boolean"
},
"id": {
"description": "订单ID",
"type": "string"
@@ -7517,7 +7556,11 @@
},
"authorization_stats": {
"description": "授权统计信息(仅在详情接口返回)",
"$ref": "#/definitions/models.AuthorizationStats"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationStats"
}
]
},
"company_size": {
"type": "string"
@@ -7842,11 +7885,19 @@
"properties": {
"period": {
"description": "时间段信息",
"$ref": "#/definitions/models.TrendPeriod"
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod"
}
]
},
"summary": {
"description": "汇总信息",
"$ref": "#/definitions/models.TrendSummary"
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary"
}
]
},
"trend_data": {
"description": "趋势数据",
@@ -7882,7 +7933,11 @@
},
"authorization_info": {
"description": "授权信息",
"$ref": "#/definitions/models.AuthorizationInfo"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationInfo"
}
]
},
"device_info": {
"description": "设备信息",
@@ -8478,6 +8533,47 @@
}
}
},
"models.InvoiceUpdateRequest": {
"type": "object",
"required": [
"invoice_type",
"receiver_email",
"title"
],
"properties": {
"content": {
"description": "开票内容,非必填,最多200字符",
"type": "string",
"maxLength": 200
},
"invoice_type": {
"description": "发票类型,必填,personal-个人/enterprise-企业普票/vat_special-增值税专用发票",
"type": "string",
"enum": [
"personal",
"enterprise",
"vat_special"
]
},
"receiver_email": {
"description": "收票邮箱,必填,格式为邮箱地址",
"type": "string"
},
"remark": {
"description": "备注,可选",
"type": "string"
},
"taxpayer_id": {
"description": "纳税人识别号,企业发票必填,个人发票可为空",
"type": "string"
},
"title": {
"description": "发票抬头,必填,最多200字符",
"type": "string",
"maxLength": 200
}
}
},
"models.InvoiceUploadResponse": {
"type": "object",
"properties": {
@@ -8706,7 +8802,11 @@
},
"authorization_code": {
"description": "关联字段(用于查询时的JOIN)",
"$ref": "#/definitions/models.AuthorizationCode"
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCode"
}
]
},
"authorization_code_id": {
"type": "string"
@@ -9292,7 +9392,11 @@
},
"business_order": {
"description": "关联的业务订单信息",
"$ref": "#/definitions/models.PaymentBusinessOrder"
"allOf": [
{
"$ref": "#/definitions/models.PaymentBusinessOrder"
}
]
},
"payment_no": {
"description": "支付单号",
@@ -9407,7 +9511,11 @@
},
"growth_rate": {
"description": "增长率(同比上月)",
"$ref": "#/definitions/models.GrowthRate"
"allOf": [
{
"$ref": "#/definitions/models.GrowthRate"
}
]
},
"total_auth_codes": {
"description": "总授权码数量(所有授权码的累计总数)",
@@ -9501,6 +9609,7 @@
},
"securityDefinitions": {
"BearerAuth": {
"description": "Bearer token for authentication",
"type": "apiKey",
"name": "Authorization",
"in": "header"
+227 -143
View File
@@ -109,7 +109,8 @@ definitions:
description: 客户ID
type: string
customer_info:
$ref: '#/definitions/models.CustomerInfoForAuthCode'
allOf:
- $ref: '#/definitions/models.CustomerInfoForAuthCode'
description: 客户信息(仅在详情接口返回)
customer_name:
description: 客户名称
@@ -337,7 +338,8 @@ definitions:
models.AuthorizationCodeShareResponse:
properties:
new_authorization_code:
$ref: '#/definitions/models.AuthorizationCodeShareResponseItem'
allOf:
- $ref: '#/definitions/models.AuthorizationCodeShareResponseItem'
description: 新生成的授权码信息
type: object
models.AuthorizationCodeShareResponseItem:
@@ -567,6 +569,9 @@ definitions:
expired_at:
description: 订单过期时间
type: string
has_invoice_applied:
description: 是否已申请发票(根据 invoices 表判断)
type: boolean
id:
description: 订单ID
type: string
@@ -860,7 +865,8 @@ definitions:
address:
type: string
authorization_stats:
$ref: '#/definitions/models.AuthorizationStats'
allOf:
- $ref: '#/definitions/models.AuthorizationStats'
description: 授权统计信息(仅在详情接口返回)
company_size:
type: string
@@ -1094,10 +1100,12 @@ definitions:
models.DashboardAuthorizationTrendResponse:
properties:
period:
$ref: '#/definitions/models.TrendPeriod'
allOf:
- $ref: '#/definitions/models.TrendPeriod'
description: 时间段信息
summary:
$ref: '#/definitions/models.TrendSummary'
allOf:
- $ref: '#/definitions/models.TrendSummary'
description: 汇总信息
trend_data:
description: 趋势数据
@@ -1122,7 +1130,8 @@ definitions:
description: 激活时间
type: string
authorization_info:
$ref: '#/definitions/models.AuthorizationInfo'
allOf:
- $ref: '#/definitions/models.AuthorizationInfo'
description: 授权信息
device_info:
additionalProperties: true
@@ -1556,6 +1565,37 @@ definitions:
description: 发票总数
type: integer
type: object
models.InvoiceUpdateRequest:
properties:
content:
description: 开票内容,非必填,最多200字符
maxLength: 200
type: string
invoice_type:
description: 发票类型,必填,personal-个人/enterprise-企业普票/vat_special-增值税专用发票
enum:
- personal
- enterprise
- vat_special
type: string
receiver_email:
description: 收票邮箱,必填,格式为邮箱地址
type: string
remark:
description: 备注,可选
type: string
taxpayer_id:
description: 纳税人识别号,企业发票必填,个人发票可为空
type: string
title:
description: 发票抬头,必填,最多200字符
maxLength: 200
type: string
required:
- invoice_type
- receiver_email
- title
type: object
models.InvoiceUploadResponse:
properties:
file_url:
@@ -1725,7 +1765,8 @@ definitions:
activation_ip:
type: string
authorization_code:
$ref: '#/definitions/models.AuthorizationCode'
allOf:
- $ref: '#/definitions/models.AuthorizationCode'
description: 关联字段(用于查询时的JOIN)
authorization_code_id:
type: string
@@ -2139,7 +2180,8 @@ definitions:
description: 支付金额,单位元
type: number
business_order:
$ref: '#/definitions/models.PaymentBusinessOrder'
allOf:
- $ref: '#/definitions/models.PaymentBusinessOrder'
description: 关联的业务订单信息
payment_no:
description: 支付单号
@@ -2222,7 +2264,8 @@ definitions:
description: 即将过期数量(30天内即将过期的授权码,且未被锁定)
type: integer
growth_rate:
$ref: '#/definitions/models.GrowthRate'
allOf:
- $ref: '#/definitions/models.GrowthRate'
description: 增长率(同比上月)
total_auth_codes:
description: 总授权码数量(所有授权码的累计总数)
@@ -2291,7 +2334,7 @@ info:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: Bearer token for authentication
description: 软件授权管理平台API文档
license:
name: MIT
url: https://opensource.org/licenses/MIT
@@ -2730,16 +2773,14 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
id:
type: string
invoice_no:
type: string
status:
type: string
type: object
properties:
id:
type: string
invoice_no:
type: string
status:
type: string
type: object
type: object
"400":
description: 请求参数无效
@@ -2806,6 +2847,66 @@ paths:
summary: 客户发票详情
tags:
- 发票管理
put:
consumes:
- application/json
description: 客户修改已被驳回的发票申请并重新提交
parameters:
- description: 发票ID
in: path
name: id
required: true
type: string
- description: 发票修改请求
in: body
name: request
required: true
schema:
$ref: '#/definitions/models.InvoiceUpdateRequest'
produces:
- application/json
responses:
"200":
description: 成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
properties:
id:
type: string
invoice_no:
type: string
status:
type: string
type: object
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"403":
description: 权限不足
schema:
$ref: '#/definitions/models.ErrorResponse'
"404":
description: 发票不存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 客户修改发票申请
tags:
- 发票管理
/api/cu/invoices/{id}/download:
get:
consumes:
@@ -2897,31 +2998,27 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
token:
type: string
user:
allOf:
- type: object
- properties:
customer_id:
type: string
email:
type: string
id:
type: string
phone:
type: string
real_name:
type: string
status:
type: string
user_role:
type: string
type: object
type: object
properties:
token:
type: string
user:
properties:
customer_id:
type: string
email:
type: string
id:
type: string
phone:
type: string
real_name:
type: string
status:
type: string
user_role:
type: string
type: object
type: object
type: object
"400":
description: 请求参数无效
@@ -3021,22 +3118,20 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
expire_time:
type: string
id:
type: string
order_no:
type: string
payment_no:
type: string
payment_url:
type: string
total_amount:
type: number
type: object
properties:
expire_time:
type: string
id:
type: string
order_no:
type: string
payment_no:
type: string
payment_url:
type: string
total_amount:
type: number
type: object
type: object
"400":
description: 请求参数无效
@@ -3118,15 +3213,15 @@ paths:
schema:
$ref: '#/definitions/models.APIResponse'
"400":
description: ""
description: Bad Request
"401":
description: ""
description: Unauthorized
"403":
description: ""
description: Forbidden
"404":
description: ""
description: Not Found
"409":
description: ""
description: Conflict
security:
- BearerAuth: []
summary: 删除订单
@@ -3160,22 +3255,20 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
expire_time:
type: string
order_id:
type: string
order_no:
type: string
payment_no:
type: string
payment_url:
type: string
total_amount:
type: number
type: object
properties:
expire_time:
type: string
order_id:
type: string
order_no:
type: string
payment_no:
type: string
payment_url:
type: string
total_amount:
type: number
type: object
type: object
"400":
description: 订单状态不允许继续支付
@@ -3209,14 +3302,12 @@ paths:
name: request
required: true
schema:
allOf:
- type: object
- properties:
license_count:
type: integer
package_id:
type: string
type: object
properties:
license_count:
type: integer
package_id:
type: string
type: object
produces:
- application/json
responses:
@@ -3227,20 +3318,18 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
discount_description:
type: string
discount_rate:
type: number
license_count:
type: integer
total_amount:
type: number
unit_price:
type: number
type: object
properties:
discount_description:
type: string
discount_rate:
type: number
license_count:
type: integer
total_amount:
type: number
unit_price:
type: number
type: object
type: object
"400":
description: 请求参数无效
@@ -3305,20 +3394,18 @@ paths:
- properties:
data:
items:
allOf:
- type: object
- properties:
id:
type: string
max_devices:
type: integer
name:
type: string
price:
type: number
type:
type: string
type: object
properties:
id:
type: string
max_devices:
type: integer
name:
type: string
price:
type: number
type:
type: string
type: object
type: array
type: object
summary: 获取套餐列表
@@ -3572,31 +3659,27 @@ paths:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
allOf:
- type: object
- properties:
token:
type: string
user:
allOf:
- type: object
- properties:
customer_id:
type: string
email:
type: string
id:
type: string
phone:
type: string
real_name:
type: string
status:
type: string
user_role:
type: string
type: object
type: object
properties:
token:
type: string
user:
properties:
customer_id:
type: string
email:
type: string
id:
type: string
phone:
type: string
real_name:
type: string
status:
type: string
user_role:
type: string
type: object
type: object
type: object
"400":
description: 请求参数无效
@@ -6196,6 +6279,7 @@ schemes:
- https
securityDefinitions:
BearerAuth:
description: Bearer token for authentication
in: header
name: Authorization
type: apiKey
@@ -200,3 +200,54 @@ func (h *CuInvoiceHandler) DownloadInvoice(c *gin.Context) {
// 重定向到文件URL
c.Redirect(http.StatusFound, fileURL)
}
// UpdateInvoice 客户修改发票申请
// @Summary 客户修改发票申请
// @Description 客户修改已被驳回的发票申请并重新提交
// @Tags 发票管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "发票ID"
// @Param request body models.InvoiceUpdateRequest true "发票修改请求"
// @Success 200 {object} models.APIResponse{data=object{id=string,invoice_no=string,status=string}} "成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 403 {object} models.ErrorResponse "权限不足"
// @Failure 404 {object} models.ErrorResponse "发票不存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/cu/invoices/{id} [put]
func (h *CuInvoiceHandler) UpdateInvoice(c *gin.Context) {
lang := middleware.GetLanguage(c)
invoiceID := c.Param("id")
var req models.InvoiceUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: "900001",
Message: i18n.GetI18nErrorMessage("900001", lang),
Timestamp: getCurrentTimestamp(),
})
return
}
invoice, err := h.invoiceService.UpdateInvoice(c.Request.Context(), invoiceID, getCuUserID(c), &req)
if err != nil {
handleI18nError(c, err, lang)
return
}
response := invoice.ToResponse()
result := map[string]interface{}{
"id": response.ID,
"invoice_no": response.InvoiceNo,
"status": response.Status,
}
c.JSON(http.StatusOK, models.APIResponse{
Code: "000000",
Message: i18n.GetI18nErrorMessage("000000", lang),
Data: result,
Timestamp: getCurrentTimestamp(),
})
}
+1
View File
@@ -288,6 +288,7 @@ func SetupRouter() *gin.Engine {
cuAuth.POST("/invoices", cuInvoiceHandler.CreateInvoice)
cuAuth.GET("/invoices", cuInvoiceHandler.GetUserInvoices)
cuAuth.GET("/invoices/:id", cuInvoiceHandler.GetUserInvoiceDetail)
cuAuth.PUT("/invoices/:id", cuInvoiceHandler.UpdateInvoice)
cuAuth.GET("/invoices/summary", cuInvoiceHandler.GetUserInvoiceSummary)
cuAuth.GET("/invoices/:id/download", cuInvoiceHandler.DownloadInvoice)
}
@@ -14,6 +14,16 @@ type InvoiceCreateRequest struct {
Remark *string `json:"remark,omitempty"` // 备注,可选
}
// InvoiceUpdateRequest 客户端修改发票请求结构
type InvoiceUpdateRequest struct {
InvoiceType string `json:"invoice_type" binding:"required,oneof=personal enterprise vat_special"` // 发票类型,必填,personal-个人/enterprise-企业普票/vat_special-增值税专用发票
ReceiverEmail string `json:"receiver_email" binding:"required,email"` // 收票邮箱,必填,格式为邮箱地址
Title string `json:"title" binding:"required,max=200"` // 发票抬头,必填,最多200字符
TaxpayerID *string `json:"taxpayer_id,omitempty"` // 纳税人识别号,企业发票必填,个人发票可为空
Content string `json:"content" binding:"omitempty,max=200"` // 开票内容,非必填,最多200字符
Remark *string `json:"remark,omitempty"` // 备注,可选
}
// InvoiceListRequest 发票列表查询请求结构
// 注:apply_date 支持 YYYY-MM-DD 或 RFC3339 时间范围 "start,end"。
type InvoiceListRequest struct {
@@ -17,6 +17,7 @@ type CuInvoiceRepository interface {
// ========== 通用工具方法 ==========
Create(invoice *models.Invoice) error
Update(invoice *models.Invoice) error
GetByID(id string) (*models.Invoice, error)
CheckOrderInvoiceExists(orderID string) (bool, error)
}
@@ -33,6 +34,12 @@ func (r *cuInvoiceRepository) Create(invoice *models.Invoice) error {
return r.db.Create(invoice).Error
}
func (r *cuInvoiceRepository) Update(invoice *models.Invoice) error {
// 使用 Select 指定更新字段,确保可以清空某些字段(虽然此处目前主要是业务更新和状态重置)
// 或者直接使用 Save 更新全量字段
return r.db.Save(invoice).Error
}
func (r *cuInvoiceRepository) GetByID(id string) (*models.Invoice, error) {
var invoice models.Invoice
err := r.db.Where("id = ? AND deleted_at IS NULL", id).First(&invoice).Error
@@ -21,6 +21,7 @@ type CuInvoiceService interface {
GetUserInvoices(ctx context.Context, cuUserID string, req *models.InvoiceListRequest) (*models.InvoiceListResponse, error)
GetInvoiceSummary(ctx context.Context, cuUserID string) (*models.InvoiceSummaryResponse, error)
DownloadInvoice(ctx context.Context, invoiceID, cuUserID string) (string, error)
UpdateInvoice(ctx context.Context, invoiceID, cuUserID string, req *models.InvoiceUpdateRequest) (*models.Invoice, error)
}
type cuInvoiceService struct {
@@ -175,6 +176,54 @@ func (s *cuInvoiceService) DownloadInvoice(ctx context.Context, invoiceID, cuUse
return *invoice.InvoiceFileURL, nil
}
func (s *cuInvoiceService) UpdateInvoice(ctx context.Context, invoiceID, cuUserID string, req *models.InvoiceUpdateRequest) (*models.Invoice, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 获取原发票
invoice, err := s.repo.GetByID(invoiceID)
if err != nil {
return nil, i18n.NewI18nError("700001", lang)
}
// 权限校验
if invoice.CuUserID != cuUserID {
return nil, i18n.NewI18nError("700001", lang)
}
// 状态校验:只有被驳回的发票可以修改
if invoice.Status != models.InvoiceStatusRejected {
return nil, i18n.NewI18nError("700004", lang)
}
// 业务逻辑校验:发票类型和纳税人识别号
if req.InvoiceType != models.InvoiceTypePersonal && (req.TaxpayerID == nil || *req.TaxpayerID == "") {
return nil, i18n.NewI18nError("700004", lang)
}
// 更新字段
invoice.InvoiceType = req.InvoiceType
invoice.Title = req.Title
invoice.TaxpayerID = req.TaxpayerID
invoice.Content = req.Content
invoice.ReceiverEmail = req.ReceiverEmail
invoice.Remark = req.Remark
// 重置状态和驳回信息
invoice.Status = models.InvoiceStatusPending
invoice.RejectReason = nil
invoice.Suggestion = nil
invoice.RejectedAt = nil
invoice.RejectedBy = nil
// 更新数据库
err = s.repo.Update(invoice)
if err != nil {
return nil, err
}
return invoice, nil
}
// ========== 私有辅助方法 ==========
// fillDisplayFields 填充响应结构的多语言显示字段