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
+708 -251

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 ```bash
# 使用默认配置 # 使用默认配置
go run cmd/main.go cd cmd
go run .
# 指定配置文件 # 指定配置文件
cd cmd
go run . -config configs/config.dev.yaml go run . -config configs/config.dev.yaml
``` ```
+193 -129
View File
@@ -1,17 +1,10 @@
// Package swagger GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // Code generated by swaggo/swag. DO NOT EDIT.
// This file was generated by swaggo/swag
package swagger package swagger
import ( import "github.com/swaggo/swag"
"bytes"
"encoding/json"
"strings"
"text/template"
"github.com/swaggo/swag" const docTemplate = `{
)
var doc = `{
"schemes": {{ marshal .Schemes }}, "schemes": {{ marshal .Schemes }},
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
@@ -737,11 +730,6 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@@ -755,8 +743,6 @@ var doc = `{
} }
} }
} }
]
}
} }
} }
] ]
@@ -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": { "/api/cu/invoices/{id}/download": {
@@ -1011,22 +1094,12 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"token": { "token": {
"type": "string" "type": "string"
}, },
"user": { "user": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"customer_id": { "customer_id": {
@@ -1052,13 +1125,9 @@ var doc = `{
} }
} }
} }
]
} }
} }
} }
]
}
}
} }
] ]
} }
@@ -1220,11 +1289,6 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"expire_time": { "expire_time": {
@@ -1247,8 +1311,6 @@ var doc = `{
} }
} }
} }
]
}
} }
} }
] ]
@@ -1306,11 +1368,6 @@ var doc = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"license_count": { "license_count": {
@@ -1321,8 +1378,6 @@ var doc = `{
} }
} }
} }
]
}
} }
], ],
"responses": { "responses": {
@@ -1337,11 +1392,6 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"discount_description": { "discount_description": {
@@ -1361,8 +1411,6 @@ var doc = `{
} }
} }
} }
]
}
} }
} }
] ]
@@ -1543,19 +1591,19 @@ var doc = `{
} }
}, },
"400": { "400": {
"description": "" "description": "Bad Request"
}, },
"401": { "401": {
"description": "" "description": "Unauthorized"
}, },
"403": { "403": {
"description": "" "description": "Forbidden"
}, },
"404": { "404": {
"description": "" "description": "Not Found"
}, },
"409": { "409": {
"description": "" "description": "Conflict"
} }
} }
} }
@@ -1609,11 +1657,6 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"expire_time": { "expire_time": {
@@ -1636,8 +1679,6 @@ var doc = `{
} }
} }
} }
]
}
} }
} }
] ]
@@ -1697,11 +1738,6 @@ var doc = `{
"data": { "data": {
"type": "array", "type": "array",
"items": { "items": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@@ -1721,8 +1757,6 @@ var doc = `{
} }
} }
} }
]
}
} }
} }
} }
@@ -2128,22 +2162,12 @@ var doc = `{
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"token": { "token": {
"type": "string" "type": "string"
}, },
"user": { "user": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"customer_id": { "customer_id": {
@@ -2169,13 +2193,9 @@ var doc = `{
} }
} }
} }
]
} }
} }
} }
]
}
}
} }
] ]
} }
@@ -6506,7 +6526,11 @@ var doc = `{
}, },
"customer_info": { "customer_info": {
"description": "客户信息(仅在详情接口返回)", "description": "客户信息(仅在详情接口返回)",
"allOf": [
{
"$ref": "#/definitions/models.CustomerInfoForAuthCode" "$ref": "#/definitions/models.CustomerInfoForAuthCode"
}
]
}, },
"customer_name": { "customer_name": {
"description": "客户名称", "description": "客户名称",
@@ -6815,8 +6839,12 @@ var doc = `{
"properties": { "properties": {
"new_authorization_code": { "new_authorization_code": {
"description": "新生成的授权码信息", "description": "新生成的授权码信息",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem" "$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
} }
]
}
} }
}, },
"models.AuthorizationCodeShareResponseItem": { "models.AuthorizationCodeShareResponseItem": {
@@ -7133,6 +7161,10 @@ var doc = `{
"description": "订单过期时间", "description": "订单过期时间",
"type": "string" "type": "string"
}, },
"has_invoice_applied": {
"description": "是否已申请发票(根据 invoices 表判断)",
"type": "boolean"
},
"id": { "id": {
"description": "订单ID", "description": "订单ID",
"type": "string" "type": "string"
@@ -7528,7 +7560,11 @@ var doc = `{
}, },
"authorization_stats": { "authorization_stats": {
"description": "授权统计信息(仅在详情接口返回)", "description": "授权统计信息(仅在详情接口返回)",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationStats" "$ref": "#/definitions/models.AuthorizationStats"
}
]
}, },
"company_size": { "company_size": {
"type": "string" "type": "string"
@@ -7853,11 +7889,19 @@ var doc = `{
"properties": { "properties": {
"period": { "period": {
"description": "时间段信息", "description": "时间段信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod" "$ref": "#/definitions/models.TrendPeriod"
}
]
}, },
"summary": { "summary": {
"description": "汇总信息", "description": "汇总信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary" "$ref": "#/definitions/models.TrendSummary"
}
]
}, },
"trend_data": { "trend_data": {
"description": "趋势数据", "description": "趋势数据",
@@ -7893,7 +7937,11 @@ var doc = `{
}, },
"authorization_info": { "authorization_info": {
"description": "授权信息", "description": "授权信息",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationInfo" "$ref": "#/definitions/models.AuthorizationInfo"
}
]
}, },
"device_info": { "device_info": {
"description": "设备信息", "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": { "models.InvoiceUploadResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -8717,7 +8806,11 @@ var doc = `{
}, },
"authorization_code": { "authorization_code": {
"description": "关联字段(用于查询时的JOIN)", "description": "关联字段(用于查询时的JOIN)",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCode" "$ref": "#/definitions/models.AuthorizationCode"
}
]
}, },
"authorization_code_id": { "authorization_code_id": {
"type": "string" "type": "string"
@@ -9303,7 +9396,11 @@ var doc = `{
}, },
"business_order": { "business_order": {
"description": "关联的业务订单信息", "description": "关联的业务订单信息",
"allOf": [
{
"$ref": "#/definitions/models.PaymentBusinessOrder" "$ref": "#/definitions/models.PaymentBusinessOrder"
}
]
}, },
"payment_no": { "payment_no": {
"description": "支付单号", "description": "支付单号",
@@ -9418,7 +9515,11 @@ var doc = `{
}, },
"growth_rate": { "growth_rate": {
"description": "增长率(同比上月)", "description": "增长率(同比上月)",
"allOf": [
{
"$ref": "#/definitions/models.GrowthRate" "$ref": "#/definitions/models.GrowthRate"
}
]
}, },
"total_auth_codes": { "total_auth_codes": {
"description": "总授权码数量(所有授权码的累计总数)", "description": "总授权码数量(所有授权码的累计总数)",
@@ -9512,6 +9613,7 @@ var doc = `{
}, },
"securityDefinitions": { "securityDefinitions": {
"BearerAuth": { "BearerAuth": {
"description": "Bearer token for authentication",
"type": "apiKey", "type": "apiKey",
"name": "Authorization", "name": "Authorization",
"in": "header" "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 // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = swaggerInfo{ var SwaggerInfo = &swag.Spec{
Version: "1.0", Version: "1.0",
Host: "", Host: "",
BasePath: "/", BasePath: "/",
Schemes: []string{"http", "https"}, Schemes: []string{"http", "https"},
Title: "License Manager API", Title: "License Manager API",
Description: "Bearer token for authentication", Description: "软件授权管理平台API文档",
} InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
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()
} }
func init() { func init() {
swag.Register("swagger", &s{}) swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
} }
+185 -76
View File
@@ -5,7 +5,7 @@
], ],
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "Bearer token for authentication", "description": "软件授权管理平台API文档",
"title": "License Manager API", "title": "License Manager API",
"termsOfService": "http://swagger.io/terms/", "termsOfService": "http://swagger.io/terms/",
"contact": { "contact": {
@@ -726,11 +726,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@@ -744,8 +739,6 @@
} }
} }
} }
]
}
} }
} }
] ]
@@ -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": { "/api/cu/invoices/{id}/download": {
@@ -1000,22 +1090,12 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"token": { "token": {
"type": "string" "type": "string"
}, },
"user": { "user": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"customer_id": { "customer_id": {
@@ -1041,13 +1121,9 @@
} }
} }
} }
]
} }
} }
} }
]
}
}
} }
] ]
} }
@@ -1209,11 +1285,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"expire_time": { "expire_time": {
@@ -1236,8 +1307,6 @@
} }
} }
} }
]
}
} }
} }
] ]
@@ -1295,11 +1364,6 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"license_count": { "license_count": {
@@ -1310,8 +1374,6 @@
} }
} }
} }
]
}
} }
], ],
"responses": { "responses": {
@@ -1326,11 +1388,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"discount_description": { "discount_description": {
@@ -1350,8 +1407,6 @@
} }
} }
} }
]
}
} }
} }
] ]
@@ -1532,19 +1587,19 @@
} }
}, },
"400": { "400": {
"description": "" "description": "Bad Request"
}, },
"401": { "401": {
"description": "" "description": "Unauthorized"
}, },
"403": { "403": {
"description": "" "description": "Forbidden"
}, },
"404": { "404": {
"description": "" "description": "Not Found"
}, },
"409": { "409": {
"description": "" "description": "Conflict"
} }
} }
} }
@@ -1598,11 +1653,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"expire_time": { "expire_time": {
@@ -1625,8 +1675,6 @@
} }
} }
} }
]
}
} }
} }
] ]
@@ -1686,11 +1734,6 @@
"data": { "data": {
"type": "array", "type": "array",
"items": { "items": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@@ -1710,8 +1753,6 @@
} }
} }
} }
]
}
} }
} }
} }
@@ -2117,22 +2158,12 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"token": { "token": {
"type": "string" "type": "string"
}, },
"user": { "user": {
"allOf": [
{
"type": "object"
},
{
"type": "object", "type": "object",
"properties": { "properties": {
"customer_id": { "customer_id": {
@@ -2158,13 +2189,9 @@
} }
} }
} }
]
} }
} }
} }
]
}
}
} }
] ]
} }
@@ -6495,7 +6522,11 @@
}, },
"customer_info": { "customer_info": {
"description": "客户信息(仅在详情接口返回)", "description": "客户信息(仅在详情接口返回)",
"allOf": [
{
"$ref": "#/definitions/models.CustomerInfoForAuthCode" "$ref": "#/definitions/models.CustomerInfoForAuthCode"
}
]
}, },
"customer_name": { "customer_name": {
"description": "客户名称", "description": "客户名称",
@@ -6804,8 +6835,12 @@
"properties": { "properties": {
"new_authorization_code": { "new_authorization_code": {
"description": "新生成的授权码信息", "description": "新生成的授权码信息",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCodeShareResponseItem" "$ref": "#/definitions/models.AuthorizationCodeShareResponseItem"
} }
]
}
} }
}, },
"models.AuthorizationCodeShareResponseItem": { "models.AuthorizationCodeShareResponseItem": {
@@ -7122,6 +7157,10 @@
"description": "订单过期时间", "description": "订单过期时间",
"type": "string" "type": "string"
}, },
"has_invoice_applied": {
"description": "是否已申请发票(根据 invoices 表判断)",
"type": "boolean"
},
"id": { "id": {
"description": "订单ID", "description": "订单ID",
"type": "string" "type": "string"
@@ -7517,7 +7556,11 @@
}, },
"authorization_stats": { "authorization_stats": {
"description": "授权统计信息(仅在详情接口返回)", "description": "授权统计信息(仅在详情接口返回)",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationStats" "$ref": "#/definitions/models.AuthorizationStats"
}
]
}, },
"company_size": { "company_size": {
"type": "string" "type": "string"
@@ -7842,11 +7885,19 @@
"properties": { "properties": {
"period": { "period": {
"description": "时间段信息", "description": "时间段信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendPeriod" "$ref": "#/definitions/models.TrendPeriod"
}
]
}, },
"summary": { "summary": {
"description": "汇总信息", "description": "汇总信息",
"allOf": [
{
"$ref": "#/definitions/models.TrendSummary" "$ref": "#/definitions/models.TrendSummary"
}
]
}, },
"trend_data": { "trend_data": {
"description": "趋势数据", "description": "趋势数据",
@@ -7882,7 +7933,11 @@
}, },
"authorization_info": { "authorization_info": {
"description": "授权信息", "description": "授权信息",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationInfo" "$ref": "#/definitions/models.AuthorizationInfo"
}
]
}, },
"device_info": { "device_info": {
"description": "设备信息", "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": { "models.InvoiceUploadResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -8706,7 +8802,11 @@
}, },
"authorization_code": { "authorization_code": {
"description": "关联字段(用于查询时的JOIN)", "description": "关联字段(用于查询时的JOIN)",
"allOf": [
{
"$ref": "#/definitions/models.AuthorizationCode" "$ref": "#/definitions/models.AuthorizationCode"
}
]
}, },
"authorization_code_id": { "authorization_code_id": {
"type": "string" "type": "string"
@@ -9292,7 +9392,11 @@
}, },
"business_order": { "business_order": {
"description": "关联的业务订单信息", "description": "关联的业务订单信息",
"allOf": [
{
"$ref": "#/definitions/models.PaymentBusinessOrder" "$ref": "#/definitions/models.PaymentBusinessOrder"
}
]
}, },
"payment_no": { "payment_no": {
"description": "支付单号", "description": "支付单号",
@@ -9407,7 +9511,11 @@
}, },
"growth_rate": { "growth_rate": {
"description": "增长率(同比上月)", "description": "增长率(同比上月)",
"allOf": [
{
"$ref": "#/definitions/models.GrowthRate" "$ref": "#/definitions/models.GrowthRate"
}
]
}, },
"total_auth_codes": { "total_auth_codes": {
"description": "总授权码数量(所有授权码的累计总数)", "description": "总授权码数量(所有授权码的累计总数)",
@@ -9501,6 +9609,7 @@
}, },
"securityDefinitions": { "securityDefinitions": {
"BearerAuth": { "BearerAuth": {
"description": "Bearer token for authentication",
"type": "apiKey", "type": "apiKey",
"name": "Authorization", "name": "Authorization",
"in": "header" "in": "header"
+129 -45
View File
@@ -109,7 +109,8 @@ definitions:
description: 客户ID description: 客户ID
type: string type: string
customer_info: customer_info:
$ref: '#/definitions/models.CustomerInfoForAuthCode' allOf:
- $ref: '#/definitions/models.CustomerInfoForAuthCode'
description: 客户信息(仅在详情接口返回) description: 客户信息(仅在详情接口返回)
customer_name: customer_name:
description: 客户名称 description: 客户名称
@@ -337,7 +338,8 @@ definitions:
models.AuthorizationCodeShareResponse: models.AuthorizationCodeShareResponse:
properties: properties:
new_authorization_code: new_authorization_code:
$ref: '#/definitions/models.AuthorizationCodeShareResponseItem' allOf:
- $ref: '#/definitions/models.AuthorizationCodeShareResponseItem'
description: 新生成的授权码信息 description: 新生成的授权码信息
type: object type: object
models.AuthorizationCodeShareResponseItem: models.AuthorizationCodeShareResponseItem:
@@ -567,6 +569,9 @@ definitions:
expired_at: expired_at:
description: 订单过期时间 description: 订单过期时间
type: string type: string
has_invoice_applied:
description: 是否已申请发票(根据 invoices 表判断)
type: boolean
id: id:
description: 订单ID description: 订单ID
type: string type: string
@@ -860,7 +865,8 @@ definitions:
address: address:
type: string type: string
authorization_stats: authorization_stats:
$ref: '#/definitions/models.AuthorizationStats' allOf:
- $ref: '#/definitions/models.AuthorizationStats'
description: 授权统计信息(仅在详情接口返回) description: 授权统计信息(仅在详情接口返回)
company_size: company_size:
type: string type: string
@@ -1094,10 +1100,12 @@ definitions:
models.DashboardAuthorizationTrendResponse: models.DashboardAuthorizationTrendResponse:
properties: properties:
period: period:
$ref: '#/definitions/models.TrendPeriod' allOf:
- $ref: '#/definitions/models.TrendPeriod'
description: 时间段信息 description: 时间段信息
summary: summary:
$ref: '#/definitions/models.TrendSummary' allOf:
- $ref: '#/definitions/models.TrendSummary'
description: 汇总信息 description: 汇总信息
trend_data: trend_data:
description: 趋势数据 description: 趋势数据
@@ -1122,7 +1130,8 @@ definitions:
description: 激活时间 description: 激活时间
type: string type: string
authorization_info: authorization_info:
$ref: '#/definitions/models.AuthorizationInfo' allOf:
- $ref: '#/definitions/models.AuthorizationInfo'
description: 授权信息 description: 授权信息
device_info: device_info:
additionalProperties: true additionalProperties: true
@@ -1556,6 +1565,37 @@ definitions:
description: 发票总数 description: 发票总数
type: integer type: integer
type: object 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: models.InvoiceUploadResponse:
properties: properties:
file_url: file_url:
@@ -1725,7 +1765,8 @@ definitions:
activation_ip: activation_ip:
type: string type: string
authorization_code: authorization_code:
$ref: '#/definitions/models.AuthorizationCode' allOf:
- $ref: '#/definitions/models.AuthorizationCode'
description: 关联字段(用于查询时的JOIN) description: 关联字段(用于查询时的JOIN)
authorization_code_id: authorization_code_id:
type: string type: string
@@ -2139,7 +2180,8 @@ definitions:
description: 支付金额,单位元 description: 支付金额,单位元
type: number type: number
business_order: business_order:
$ref: '#/definitions/models.PaymentBusinessOrder' allOf:
- $ref: '#/definitions/models.PaymentBusinessOrder'
description: 关联的业务订单信息 description: 关联的业务订单信息
payment_no: payment_no:
description: 支付单号 description: 支付单号
@@ -2222,7 +2264,8 @@ definitions:
description: 即将过期数量(30天内即将过期的授权码,且未被锁定) description: 即将过期数量(30天内即将过期的授权码,且未被锁定)
type: integer type: integer
growth_rate: growth_rate:
$ref: '#/definitions/models.GrowthRate' allOf:
- $ref: '#/definitions/models.GrowthRate'
description: 增长率(同比上月) description: 增长率(同比上月)
total_auth_codes: total_auth_codes:
description: 总授权码数量(所有授权码的累计总数) description: 总授权码数量(所有授权码的累计总数)
@@ -2291,7 +2334,7 @@ info:
email: support@swagger.io email: support@swagger.io
name: API Support name: API Support
url: http://www.swagger.io/support url: http://www.swagger.io/support
description: Bearer token for authentication description: 软件授权管理平台API文档
license: license:
name: MIT name: MIT
url: https://opensource.org/licenses/MIT url: https://opensource.org/licenses/MIT
@@ -2730,9 +2773,7 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
id: id:
type: string type: string
invoice_no: invoice_no:
@@ -2806,6 +2847,66 @@ paths:
summary: 客户发票详情 summary: 客户发票详情
tags: 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: /api/cu/invoices/{id}/download:
get: get:
consumes: consumes:
@@ -2897,15 +2998,11 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
token: token:
type: string type: string
user: user:
allOf: properties:
- type: object
- properties:
customer_id: customer_id:
type: string type: string
email: email:
@@ -3021,9 +3118,7 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
expire_time: expire_time:
type: string type: string
id: id:
@@ -3118,15 +3213,15 @@ paths:
schema: schema:
$ref: '#/definitions/models.APIResponse' $ref: '#/definitions/models.APIResponse'
"400": "400":
description: "" description: Bad Request
"401": "401":
description: "" description: Unauthorized
"403": "403":
description: "" description: Forbidden
"404": "404":
description: "" description: Not Found
"409": "409":
description: "" description: Conflict
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 删除订单 summary: 删除订单
@@ -3160,9 +3255,7 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
expire_time: expire_time:
type: string type: string
order_id: order_id:
@@ -3209,9 +3302,7 @@ paths:
name: request name: request
required: true required: true
schema: schema:
allOf: properties:
- type: object
- properties:
license_count: license_count:
type: integer type: integer
package_id: package_id:
@@ -3227,9 +3318,7 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
discount_description: discount_description:
type: string type: string
discount_rate: discount_rate:
@@ -3305,9 +3394,7 @@ paths:
- properties: - properties:
data: data:
items: items:
allOf: properties:
- type: object
- properties:
id: id:
type: string type: string
max_devices: max_devices:
@@ -3572,15 +3659,11 @@ paths:
- $ref: '#/definitions/models.APIResponse' - $ref: '#/definitions/models.APIResponse'
- properties: - properties:
data: data:
allOf: properties:
- type: object
- properties:
token: token:
type: string type: string
user: user:
allOf: properties:
- type: object
- properties:
customer_id: customer_id:
type: string type: string
email: email:
@@ -6196,6 +6279,7 @@ schemes:
- https - https
securityDefinitions: securityDefinitions:
BearerAuth: BearerAuth:
description: Bearer token for authentication
in: header in: header
name: Authorization name: Authorization
type: apiKey type: apiKey
@@ -200,3 +200,54 @@ func (h *CuInvoiceHandler) DownloadInvoice(c *gin.Context) {
// 重定向到文件URL // 重定向到文件URL
c.Redirect(http.StatusFound, fileURL) 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.POST("/invoices", cuInvoiceHandler.CreateInvoice)
cuAuth.GET("/invoices", cuInvoiceHandler.GetUserInvoices) cuAuth.GET("/invoices", cuInvoiceHandler.GetUserInvoices)
cuAuth.GET("/invoices/:id", cuInvoiceHandler.GetUserInvoiceDetail) cuAuth.GET("/invoices/:id", cuInvoiceHandler.GetUserInvoiceDetail)
cuAuth.PUT("/invoices/:id", cuInvoiceHandler.UpdateInvoice)
cuAuth.GET("/invoices/summary", cuInvoiceHandler.GetUserInvoiceSummary) cuAuth.GET("/invoices/summary", cuInvoiceHandler.GetUserInvoiceSummary)
cuAuth.GET("/invoices/:id/download", cuInvoiceHandler.DownloadInvoice) cuAuth.GET("/invoices/:id/download", cuInvoiceHandler.DownloadInvoice)
} }
@@ -14,6 +14,16 @@ type InvoiceCreateRequest struct {
Remark *string `json:"remark,omitempty"` // 备注,可选 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 发票列表查询请求结构 // InvoiceListRequest 发票列表查询请求结构
// 注:apply_date 支持 YYYY-MM-DD 或 RFC3339 时间范围 "start,end"。 // 注:apply_date 支持 YYYY-MM-DD 或 RFC3339 时间范围 "start,end"。
type InvoiceListRequest struct { type InvoiceListRequest struct {
@@ -17,6 +17,7 @@ type CuInvoiceRepository interface {
// ========== 通用工具方法 ========== // ========== 通用工具方法 ==========
Create(invoice *models.Invoice) error Create(invoice *models.Invoice) error
Update(invoice *models.Invoice) error
GetByID(id string) (*models.Invoice, error) GetByID(id string) (*models.Invoice, error)
CheckOrderInvoiceExists(orderID string) (bool, error) CheckOrderInvoiceExists(orderID string) (bool, error)
} }
@@ -33,6 +34,12 @@ func (r *cuInvoiceRepository) Create(invoice *models.Invoice) error {
return r.db.Create(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) { func (r *cuInvoiceRepository) GetByID(id string) (*models.Invoice, error) {
var invoice models.Invoice var invoice models.Invoice
err := r.db.Where("id = ? AND deleted_at IS NULL", id).First(&invoice).Error 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) GetUserInvoices(ctx context.Context, cuUserID string, req *models.InvoiceListRequest) (*models.InvoiceListResponse, error)
GetInvoiceSummary(ctx context.Context, cuUserID string) (*models.InvoiceSummaryResponse, error) GetInvoiceSummary(ctx context.Context, cuUserID string) (*models.InvoiceSummaryResponse, error)
DownloadInvoice(ctx context.Context, invoiceID, cuUserID string) (string, 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 { type cuInvoiceService struct {
@@ -175,6 +176,54 @@ func (s *cuInvoiceService) DownloadInvoice(ctx context.Context, invoiceID, cuUse
return *invoice.InvoiceFileURL, nil 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 填充响应结构的多语言显示字段 // fillDisplayFields 填充响应结构的多语言显示字段