feat: 增加创建客户接口

This commit is contained in:
wood911010 committed 2025-08-16 16:07:43 +08:00
1 parent 311ed26284
commit d20f79782f
11 files changed
+607 -6

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ system:
errors:
# Success status
"000000": "Success"
"000001": "Customer created successfully"
# Authentication module (10xxxx)
auth:
+1
View File
@@ -5,6 +5,7 @@ system:
errors:
# 成功状态
"000000": "成功"
"000001": "客户创建成功"
# 认证模块 (10xxxx)
auth:
+158
View File
@@ -159,6 +159,79 @@ const docTemplate = `{
}
}
}
},
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "创建新的客户记录,自动生成客户编码",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "创建客户",
"parameters": [
{
"description": "客户信息",
"name": "customer",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerCreateRequest"
}
}
],
"responses": {
"200": {
"description": "创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"409": {
"description": "客户已存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/customers/{id}": {
@@ -633,6 +706,91 @@ const docTemplate = `{
}
}
},
"models.CustomerCreateRequest": {
"type": "object",
"required": [
"contact_person",
"customer_level",
"customer_name",
"customer_type",
"status"
],
"properties": {
"address": {
"description": "地址,可选",
"type": "string",
"maxLength": 500
},
"company_size": {
"description": "企业规模,可选",
"type": "string",
"enum": [
"small",
"medium",
"large",
"enterprise"
]
},
"contact_person": {
"description": "联系人姓名,必填",
"type": "string",
"maxLength": 100
},
"contact_title": {
"description": "联系人职位,可选",
"type": "string",
"maxLength": 100
},
"customer_level": {
"description": "客户等级,必填",
"type": "string",
"enum": [
"normal",
"vip",
"enterprise",
"strategic"
]
},
"customer_name": {
"description": "客户名称,必填",
"type": "string",
"maxLength": 200
},
"customer_type": {
"description": "客户类型,必填",
"type": "string",
"enum": [
"individual",
"enterprise",
"government",
"education"
]
},
"description": {
"description": "描述,可选",
"type": "string",
"maxLength": 1000
},
"email": {
"description": "邮箱,可选",
"type": "string",
"maxLength": 255
},
"phone": {
"description": "电话,可选",
"type": "string",
"maxLength": 20
},
"status": {
"description": "状态,必填",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.CustomerListItem": {
"type": "object",
"properties": {
+158
View File
@@ -156,6 +156,79 @@
}
}
}
},
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "创建新的客户记录,自动生成客户编码",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"客户管理"
],
"summary": "创建客户",
"parameters": [
{
"description": "客户信息",
"name": "customer",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.CustomerCreateRequest"
}
}
],
"responses": {
"200": {
"description": "创建成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/models.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Customer"
}
}
}
]
}
},
"400": {
"description": "请求参数无效",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"401": {
"description": "未认证",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"409": {
"description": "客户已存在",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
},
"500": {
"description": "服务器内部错误",
"schema": {
"$ref": "#/definitions/models.ErrorResponse"
}
}
}
}
},
"/api/customers/{id}": {
@@ -630,6 +703,91 @@
}
}
},
"models.CustomerCreateRequest": {
"type": "object",
"required": [
"contact_person",
"customer_level",
"customer_name",
"customer_type",
"status"
],
"properties": {
"address": {
"description": "地址,可选",
"type": "string",
"maxLength": 500
},
"company_size": {
"description": "企业规模,可选",
"type": "string",
"enum": [
"small",
"medium",
"large",
"enterprise"
]
},
"contact_person": {
"description": "联系人姓名,必填",
"type": "string",
"maxLength": 100
},
"contact_title": {
"description": "联系人职位,可选",
"type": "string",
"maxLength": 100
},
"customer_level": {
"description": "客户等级,必填",
"type": "string",
"enum": [
"normal",
"vip",
"enterprise",
"strategic"
]
},
"customer_name": {
"description": "客户名称,必填",
"type": "string",
"maxLength": 200
},
"customer_type": {
"description": "客户类型,必填",
"type": "string",
"enum": [
"individual",
"enterprise",
"government",
"education"
]
},
"description": {
"description": "描述,可选",
"type": "string",
"maxLength": 1000
},
"email": {
"description": "邮箱,可选",
"type": "string",
"maxLength": 255
},
"phone": {
"description": "电话,可选",
"type": "string",
"maxLength": 20
},
"status": {
"description": "状态,必填",
"type": "string",
"enum": [
"active",
"disabled"
]
}
}
},
"models.CustomerListItem": {
"type": "object",
"properties": {
+111
View File
@@ -55,6 +55,73 @@ definitions:
updated_by:
type: string
type: object
models.CustomerCreateRequest:
properties:
address:
description: 地址,可选
maxLength: 500
type: string
company_size:
description: 企业规模,可选
enum:
- small
- medium
- large
- enterprise
type: string
contact_person:
description: 联系人姓名,必填
maxLength: 100
type: string
contact_title:
description: 联系人职位,可选
maxLength: 100
type: string
customer_level:
description: 客户等级,必填
enum:
- normal
- vip
- enterprise
- strategic
type: string
customer_name:
description: 客户名称,必填
maxLength: 200
type: string
customer_type:
description: 客户类型,必填
enum:
- individual
- enterprise
- government
- education
type: string
description:
description: 描述,可选
maxLength: 1000
type: string
email:
description: 邮箱,可选
maxLength: 255
type: string
phone:
description: 电话,可选
maxLength: 20
type: string
status:
description: 状态,必填
enum:
- active
- disabled
type: string
required:
- contact_person
- customer_level
- customer_name
- customer_type
- status
type: object
models.CustomerListItem:
properties:
contact_person:
@@ -289,6 +356,50 @@ paths:
summary: 查询客户列表
tags:
- 客户管理
post:
consumes:
- application/json
description: 创建新的客户记录,自动生成客户编码
parameters:
- description: 客户信息
in: body
name: customer
required: true
schema:
$ref: '#/definitions/models.CustomerCreateRequest'
produces:
- application/json
responses:
"200":
description: 创建成功
schema:
allOf:
- $ref: '#/definitions/models.APIResponse'
- properties:
data:
$ref: '#/definitions/models.Customer'
type: object
"400":
description: 请求参数无效
schema:
$ref: '#/definitions/models.ErrorResponse'
"401":
description: 未认证
schema:
$ref: '#/definitions/models.ErrorResponse'
"409":
description: 客户已存在
schema:
$ref: '#/definitions/models.ErrorResponse'
"500":
description: 服务器内部错误
schema:
$ref: '#/definitions/models.ErrorResponse'
security:
- BearerAuth: []
summary: 创建客户
tags:
- 客户管理
/api/customers/{id}:
get:
consumes:
@@ -59,7 +59,7 @@ func (h *CustomerHandler) GetCustomerList(c *gin.Context) {
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.customerService.GetCustomerList(ctx, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
@@ -121,7 +121,7 @@ func (h *CustomerHandler) GetCustomer(c *gin.Context) {
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.customerService.GetCustomer(ctx, id)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
@@ -152,4 +152,66 @@ func (h *CustomerHandler) GetCustomer(c *gin.Context) {
Message: successMessage,
Data: data,
})
}
}
// CreateCustomer 创建客户
// @Summary 创建客户
// @Description 创建新的客户记录,自动生成客户编码
// @Tags 客户管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param customer body models.CustomerCreateRequest true "客户信息"
// @Success 200 {object} models.APIResponse{data=models.Customer} "创建成功"
// @Failure 400 {object} models.ErrorResponse "请求参数无效"
// @Failure 401 {object} models.ErrorResponse "未认证"
// @Failure 409 {object} models.ErrorResponse "客户已存在"
// @Failure 500 {object} models.ErrorResponse "服务器内部错误"
// @Router /api/customers [post]
func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
var req models.CustomerCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900001", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// 设置语言到Context中
ctx := middleware.WithLanguage(c.Request.Context(), c)
data, err := h.customerService.CreateCustomer(ctx, &req)
if err != nil {
// 错误已经在Service层完全包装好了,直接使用
var i18nErr *i18n.I18nError
if errors.As(err, &i18nErr) {
c.JSON(i18nErr.HttpCode, models.ErrorResponse{
Code: i18nErr.Code,
Message: i18nErr.Message,
Timestamp: time.Now().Format(time.RFC3339),
})
} else {
// 兜底:理论上不应该到这里,因为Service层应该返回I18nError
lang := middleware.GetLanguage(c)
status, errCode, message := i18n.NewI18nErrorResponse("900004", lang)
c.JSON(status, models.ErrorResponse{
Code: errCode,
Message: message,
Timestamp: time.Now().Format(time.RFC3339),
})
}
return
}
lang := middleware.GetLanguage(c)
successMessage := i18n.GetErrorMessage("000001", lang) // 使用客户创建成功的消息
c.JSON(http.StatusOK, models.APIResponse{
Code: 0,
Message: successMessage,
Data: data,
})
}
+1
View File
@@ -75,6 +75,7 @@ func SetupRouter() *gin.Engine {
// 客户管理
auth.GET("/customers", customerHandler.GetCustomerList)
auth.GET("/customers/:id", customerHandler.GetCustomer)
auth.POST("/customers", customerHandler.CreateCustomer)
// 枚举管理
auth.GET("/enums", enumHandler.GetAllEnums)
+15
View File
@@ -104,3 +104,18 @@ type CustomerListResponse struct {
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// CustomerCreateRequest 创建客户请求结构
type CustomerCreateRequest struct {
CustomerName string `json:"customer_name" binding:"required,max=200"` // 客户名称,必填
CustomerType string `json:"customer_type" binding:"required,oneof=individual enterprise government education"` // 客户类型,必填
ContactPerson string `json:"contact_person" binding:"required,max=100"` // 联系人姓名,必填
ContactTitle *string `json:"contact_title" binding:"omitempty,max=100"` // 联系人职位,可选
Email *string `json:"email" binding:"omitempty,email,max=255"` // 邮箱,可选
Phone *string `json:"phone" binding:"omitempty,max=20"` // 电话,可选
Address *string `json:"address" binding:"omitempty,max=500"` // 地址,可选
CompanySize *string `json:"company_size" binding:"omitempty,oneof=small medium large enterprise"` // 企业规模,可选
CustomerLevel string `json:"customer_level" binding:"required,oneof=normal vip enterprise strategic"` // 客户等级,必填
Status string `json:"status" binding:"required,oneof=active disabled"` // 状态,必填
Description *string `json:"description" binding:"omitempty,max=1000"` // 描述,可选
}
@@ -131,10 +131,54 @@ func (r *customerRepository) GetCustomerByID(ctx context.Context, id string) (*m
// CreateCustomer 创建客户
func (r *customerRepository) CreateCustomer(ctx context.Context, customer *models.Customer) error {
if err := r.db.Create(customer).Error; err != nil {
return fmt.Errorf("failed to create customer: %w", err)
// 使用事务确保数据一致性
return r.db.Transaction(func(tx *gorm.DB) error {
// 生成客户编码
customerCode, err := r.generateCustomerCode(tx)
if err != nil {
return fmt.Errorf("failed to generate customer code: %w", err)
}
customer.CustomerCode = customerCode
// 创建客户
if err := tx.Create(customer).Error; err != nil {
return fmt.Errorf("failed to create customer: %w", err)
}
return nil
})
}
// generateCustomerCode 生成客户编码
func (r *customerRepository) generateCustomerCode(tx *gorm.DB) (string, error) {
currentYear := time.Now().Year()
// 查询或创建当年的序列号记录
var sequence models.CustomerCodeSequence
err := tx.Where("year = ?", currentYear).First(&sequence).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
// 创建新年份的序列号记录
sequence = models.CustomerCodeSequence{
Year: currentYear,
SequenceNumber: 1,
}
if err := tx.Create(&sequence).Error; err != nil {
return "", fmt.Errorf("failed to create sequence: %w", err)
}
} else {
return "", fmt.Errorf("failed to query sequence: %w", err)
}
} else {
// 更新序列号
sequence.SequenceNumber++
if err := tx.Save(&sequence).Error; err != nil {
return "", fmt.Errorf("failed to update sequence: %w", err)
}
}
return nil
// 生成客户编码:CUS-YYYY-NNNN
customerCode := fmt.Sprintf("CUS-%d-%04d", currentYear, sequence.SequenceNumber)
return customerCode, nil
}
// UpdateCustomer 更新客户信息
@@ -3,6 +3,7 @@ package service
import (
"context"
"errors"
"strings"
"license-manager/internal/models"
"license-manager/internal/repository"
@@ -84,6 +85,54 @@ func (s *customerService) fillDisplayFields(item *models.CustomerListItem, lang
item.StatusDisplay = i18n.GetEnumMessage("customer_status", item.Status, lang)
}
// CreateCustomer 创建客户
func (s *customerService) CreateCustomer(ctx context.Context, req *models.CustomerCreateRequest) (*models.Customer, error) {
lang := pkgcontext.GetLanguageFromContext(ctx)
// 业务逻辑:参数验证
if req == nil {
return nil, i18n.NewI18nError("900001", lang)
}
// 获取当前用户ID(从Context中获取)
// TODO: 实现用户上下文获取,这里暂时使用硬编码
currentUserID := "admin_uuid" // 实际应该从JWT token中获取
// 构建客户实体
customer := &models.Customer{
CustomerName: req.CustomerName,
CustomerType: req.CustomerType,
ContactPerson: req.ContactPerson,
ContactTitle: req.ContactTitle,
Email: req.Email,
Phone: req.Phone,
Address: req.Address,
CompanySize: req.CompanySize,
CustomerLevel: req.CustomerLevel,
Status: req.Status,
Description: req.Description,
CreatedBy: currentUserID,
// CustomerCode 将在Repository层自动生成
// CreatedAt, UpdatedAt 将在模型的BeforeCreate中自动设置
}
// 委托给Repository层进行数据创建
if err := s.customerRepo.CreateCustomer(ctx, customer); err != nil {
// 根据错误类型包装为完整的I18nError
if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "unique") {
return nil, i18n.NewI18nError("200002", lang) // 客户已存在
}
// 数据库相关错误
return nil, i18n.NewI18nError("900004", lang, err.Error())
}
// 填充多语言显示字段
s.fillCustomerDisplayFields(customer, lang)
return customer, nil
}
// fillCustomerDisplayFields 填充完整客户模型的多语言显示字段
func (s *customerService) fillCustomerDisplayFields(customer *models.Customer, lang string) {
customer.CustomerTypeDisplay = i18n.GetEnumMessage("customer_type", customer.CustomerType, lang)
+1
View File
@@ -22,6 +22,7 @@ type SystemService interface {
type CustomerService interface {
GetCustomerList(ctx context.Context, req *models.CustomerListRequest) (*models.CustomerListResponse, error)
GetCustomer(ctx context.Context, id string) (*models.Customer, error)
CreateCustomer(ctx context.Context, req *models.CustomerCreateRequest) (*models.Customer, error)
}
// EnumService 枚举服务接口