Browse Source

ADD:服务类型、服务内容

zoie 9 months ago
parent
commit
8ad6f96b6a

+ 14 - 0
conf/app.conf

@@ -47,3 +47,17 @@ Qiniu_AccessKey = -8ezB_d-8-eUFTMvhOGbGzgeQRPeKQnaQ3DBcUxo
 Qiniu_SecretKey = KFhkYxTAJ2ZPN3ZS3euTsfWk8-C92rKgkhAMkDRN
 Qiniu_BUCKET = baozhida-erp
 Qiniu_Url = https://erposs.baozhida.cn/
+
+# 日志配置
+# 0-控制台输出 1-文件输出 2-文件和控制台输出
+adapter_type = 0
+# 文件最多保存多少天
+maxdays = 7
+# 日志级别 (0-紧急 1-报警 2-严重错误 3-错误 4-警告 5-注意 6-信息 7-调试)
+level = 7
+# SQL日志级别 (1-静音 2-错误 3-警告 4-信息). 注意: sql日志只在level大于等于5级别才会输出。
+sqlloglevel = 4
+# 慢SQL阈值(毫秒)。慢SQL会在sqlloglevel大于等于3时输出。
+slow_threshold = 200
+# 每个文件保存的最大行数
+maxlines = 10000

+ 238 - 0
controllers/ContractReview.go

@@ -0,0 +1,238 @@
+package controllers
+
+import (
+	"ERP_storage/Nats/NatsServer"
+	"ERP_storage/dto"
+	"ERP_storage/models/Account"
+	"ERP_storage/services"
+	userlibs "git.baozhida.cn/ERP_libs/User"
+	"git.baozhida.cn/ERP_libs/lib"
+	beego "github.com/beego/beego/v2/server/web"
+	"math"
+	"strconv"
+)
+
+type ContractReviewController struct {
+	beego.Controller
+	User userlibs.User
+}
+
+func (c *ContractReviewController) Prepare() {
+	c.User = *Account.User_r
+}
+
+// 服务类型
+func (c *ContractReviewController) ServiceType_List() {
+	s := services.ServiceType{}
+	reqData := dto.ServiceTypePageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceType_Add() {
+	s := services.ServiceType{}
+
+	reqData := dto.ServiceTypeInsertReq{}
+
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+	Id, err := s.Insert(&reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "添加失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务类型", "添加", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Id}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceType_Edit() {
+	s := services.ServiceType{}
+	reqData := dto.ServiceTypeUpdateReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Update(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "修改失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务类型", "修改", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceType_Del() {
+	s := services.ServiceType{}
+	reqData := dto.ServiceTypeDeleteReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Delete(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务类型", "删除", strconv.Itoa(reqData.T_id))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+
+// 服务内容
+func (c *ContractReviewController) ServiceItem_List() {
+	s := services.ServiceItem{}
+	reqData := dto.ServiceItemPageReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	R_List, R_cnt := s.GetPage(&reqData)
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Num = R_cnt
+	r_jsons.Data = R_List
+	r_jsons.Page = reqData.Page
+	r_jsons.Page_size = int(math.Ceil(float64(R_cnt) / float64(reqData.PageSize)))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceItem_Add() {
+	s := services.ServiceItem{}
+
+	reqData := dto.ServiceItemInsertReq{}
+
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+	Id, err := s.Insert(&reqData)
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "添加失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务内容", "添加", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Id}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceItem_Edit() {
+	s := services.ServiceItem{}
+	reqData := dto.ServiceItemUpdateReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Update(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 203, Msg: "修改失败"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务内容", "修改", reqData)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}
+func (c *ContractReviewController) ServiceItem_Del() {
+	s := services.ServiceItem{}
+	reqData := dto.ServiceItemDeleteReq{}
+	if err := c.ParseForm(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "参数错误"}
+		c.ServeJSON()
+		return
+	}
+	if err := Validate(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: err.Error()}
+		c.ServeJSON()
+		return
+	}
+
+	if err := s.Delete(&reqData); err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "删除失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	NatsServer.AddUserLogs(c.User.T_uuid, "服务内容", "删除", strconv.Itoa(reqData.T_id))
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!"}
+	c.ServeJSON()
+	return
+}

+ 34 - 0
controllers/base_controller.go

@@ -0,0 +1,34 @@
+package controllers
+
+import (
+	vd "github.com/bytedance/go-tagexpr/v2/validator"
+)
+
+func Validate(reqData interface{}) error {
+
+	//valueType := reflect.ValueOf(reqData).Elem().Type() // 获取结构体类型
+	//newStruct := reflect.New(valueType).Interface()     // 创建新的结构体
+	//
+	//v := validation.Validation{}
+	//status, err := v.Valid(newStruct)
+	//if err != nil {
+	//	return err
+	//}
+	//if !status {
+	//	for _, err := range v.Errors {
+	//		// 获取 newStruct 指向的真实结构体的类型
+	//		structType := reflect.Indirect(reflect.ValueOf(newStruct)).Type()
+	//		field, _ := structType.FieldByName(err.Field)
+	//		alias := field.Tag.Get("alias")
+	//		message := strings.Replace(err.Message, err.Field, alias, 1)
+	//		return errors.New(message)
+	//	}
+	//}
+	//
+	//reflect.ValueOf(reqData).Elem().Set(reflect.ValueOf(newStruct).Elem())
+	//return nil
+	if err := vd.Validate(reqData); err != nil {
+		return err
+	}
+	return nil
+}

+ 1 - 0
controllers/test.go

@@ -0,0 +1 @@
+package controllers

+ 75 - 0
dto/ServiceItem.go

@@ -0,0 +1,75 @@
+package dto
+
+import (
+	models "ERP_storage/models/ContractReview"
+)
+
+// ServiceItemPageReq 列表或者搜索使用结构体
+type ServiceItemPageReq struct {
+	Pagination        `search:"-"`
+	T_name            string `form:"T_name" search:"type:contains;column:t_name;table:service_item"`                       // 服务内容
+	T_service_type_id int    `form:"T_service_type_id" search:"type:contains;column:T_service_type_id;table:service_item"` // 服务类型id
+}
+
+func (m *ServiceItemPageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// ServiceItemInsertReq 增使用的结构体
+type ServiceItemInsertReq struct {
+	T_name            string `form:"T_name"  vd:"len($)>0;msg:'服务内容不能为空'"`        // 服务内容
+	T_service_type_id int    `form:"T_service_type_id" vd:"$>0;msg:'服务类型id不能为空'"` // 服务类型id
+	T_sale_type       string `form:"T_sale_type"`                                 // 销售类型
+	T_model           string `form:"T_model"`                                     // 型号
+	T_spec            string `form:"T_spec"`                                      // 规格
+}
+
+func (s *ServiceItemInsertReq) Generate(model *models.ServiceItem) {
+	model.T_name = s.T_name
+	model.T_service_type_id = s.T_service_type_id
+	model.T_sale_type = s.T_sale_type
+	model.T_model = s.T_model
+	model.T_spec = s.T_spec
+	model.T_State = 1
+}
+
+// ServiceItemUpdateReq 改使用的结构体
+type ServiceItemUpdateReq struct {
+	T_id              int    `form:"T_id"  example:"1"`
+	T_name            string `form:"T_name"  vd:"len($)>0;msg:'服务内容不能为空'"`        // 服务内容
+	T_service_type_id int    `form:"T_service_type_id" vd:"$>0;msg:'服务类型id不能为空'"` // 服务类型id
+	T_sale_type       string `form:"T_sale_type"`                                 // 销售类型
+	T_model           string `form:"T_model"`                                     // 型号
+	T_spec            string `form:"T_spec"`                                      // 规格
+}
+
+func (s *ServiceItemUpdateReq) Generate(model *models.ServiceItem) {
+	model.Id = s.T_id
+	model.T_name = s.T_name
+	model.T_service_type_id = s.T_service_type_id
+	model.T_sale_type = s.T_sale_type
+	model.T_model = s.T_model
+	model.T_spec = s.T_spec
+}
+
+func (s *ServiceItemUpdateReq) GetId() interface{} {
+	return s.T_id
+}
+
+// ServiceItemGetReq 获取单个的结构体
+type ServiceItemGetReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *ServiceItemGetReq) GetId() interface{} {
+	return s.T_id
+}
+
+// ServiceItemDeleteReq 删除的结构体
+type ServiceItemDeleteReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *ServiceItemDeleteReq) GetId() interface{} {
+	return s.T_id
+}

+ 58 - 0
dto/ServiceType.go

@@ -0,0 +1,58 @@
+package dto
+
+import (
+	models "ERP_storage/models/ContractReview"
+)
+
+// ServiceTypePageReq 列表或者搜索使用结构体
+type ServiceTypePageReq struct {
+	Pagination `search:"-"`
+	T_name     string `form:"T_name" search:"type:contains;column:t_name;table:service_type" example:""` // 名称
+}
+
+func (m *ServiceTypePageReq) GetNeedSearch() interface{} {
+	return *m
+}
+
+// ServiceTypeInsertReq 增使用的结构体
+type ServiceTypeInsertReq struct {
+	T_name string `form:"T_name" example:"服务类型" valid:"MinSize(1)"` //服务类型
+}
+
+func (s *ServiceTypeInsertReq) Generate(model *models.ServiceType) {
+	model.T_name = s.T_name
+	model.T_State = 1
+}
+
+// ServiceTypeUpdateReq 改使用的结构体
+type ServiceTypeUpdateReq struct {
+	T_id   int    `form:"T_id"  example:"1"`
+	T_name string `form:"T_name"  example:"规格"` // 规格
+}
+
+func (s *ServiceTypeUpdateReq) Generate(model *models.ServiceType) {
+	model.Id = s.T_id
+	model.T_name = s.T_name
+}
+
+func (s *ServiceTypeUpdateReq) GetId() interface{} {
+	return s.T_id
+}
+
+// ServiceTypeGetReq 获取单个的结构体
+type ServiceTypeGetReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *ServiceTypeGetReq) GetId() interface{} {
+	return s.T_id
+}
+
+// ServiceTypeDeleteReq 删除的结构体
+type ServiceTypeDeleteReq struct {
+	T_id int `form:"T_id"`
+}
+
+func (s *ServiceTypeDeleteReq) GetId() interface{} {
+	return s.T_id
+}

+ 22 - 0
dto/error.go

@@ -0,0 +1,22 @@
+package dto
+
+import (
+	"errors"
+	"github.com/go-sql-driver/mysql"
+)
+
+var (
+	GetFailedErr    = errors.New("查询失败")
+	CreateFailedErr = errors.New("添加失败")
+	UpdateFailedErr = errors.New("更新失败")
+	DeleteFailedErr = errors.New("删除失败")
+	GetNotFoundErr  = errors.New("数据不存在")
+)
+
+func IsUniqueIndexErr(err error) bool {
+	uniqueErr := &mysql.MySQLError{}
+	if ok := errors.As(err, &uniqueErr); ok {
+		return uniqueErr.Number == 1062
+	}
+	return false
+}

+ 20 - 0
dto/pagination.go

@@ -0,0 +1,20 @@
+package dto
+
+type Pagination struct {
+	Page     int `form:"page" example:"1"`    // 页数
+	PageSize int `form:"page_z" example:"10"` // 每页条数
+}
+
+func (m *Pagination) GetPageIndex() int {
+	if m.Page <= 0 {
+		m.Page = 1
+	}
+	return m.Page
+}
+
+func (m *Pagination) GetPageSize() int {
+	if m.PageSize <= 0 {
+		m.PageSize = 10
+	}
+	return m.PageSize
+}

+ 105 - 0
dto/search.go

@@ -0,0 +1,105 @@
+package dto
+
+import (
+	"ERP_storage/dto/search"
+	"fmt"
+	"gorm.io/gorm"
+	"strings"
+)
+
+var (
+	Source string
+	Driver string
+	DBName string
+)
+
+type GeneralDelDto struct {
+	Id  int   `uri:"id" json:"id" validate:"required"`
+	Ids []int `json:"ids"`
+}
+
+func (g GeneralDelDto) GetIds() []int {
+	ids := make([]int, 0)
+	if g.Id != 0 {
+		ids = append(ids, g.Id)
+	}
+	if len(g.Ids) > 0 {
+		for _, id := range g.Ids {
+			if id > 0 {
+				ids = append(ids, id)
+			}
+		}
+	} else {
+		if g.Id > 0 {
+			ids = append(ids, g.Id)
+		}
+	}
+	if len(ids) <= 0 {
+		//方式全部删除
+		ids = append(ids, 0)
+	}
+	return ids
+}
+
+type GeneralGetDto struct {
+	Id int `uri:"id" json:"id" validate:"required"`
+}
+
+func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		condition := &search.GormCondition{
+			GormPublic: search.GormPublic{},
+			Join:       make([]*search.GormJoin, 0),
+		}
+		search.ResolveSearchQuery(Driver, q, condition)
+		for _, join := range condition.Join {
+			if join == nil {
+				continue
+			}
+			db = db.Joins(join.JoinOn)
+			for k, v := range join.Where {
+				db = db.Where(k, v...)
+			}
+			for k, v := range join.Or {
+				db = db.Or(k, v...)
+			}
+			for _, o := range join.Order {
+				db = db.Order(o)
+			}
+		}
+		for k, v := range condition.Where {
+			db = db.Where(k, v...)
+		}
+
+		for k, v := range condition.Or {
+			db = db.Or(k, v...)
+		}
+		var orContains string
+		for k, v := range condition.OrContains {
+			orContains += fmt.Sprintf(" OR %v '%v'", k, v[0])
+		}
+		if len(orContains) > 0 {
+			db = db.Where(strings.TrimLeft(orContains, " OR"))
+		}
+		for _, o := range condition.Order {
+			db = db.Order(o)
+		}
+		return db
+	}
+}
+
+func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		offset := (pageIndex - 1) * pageSize
+		if offset < 0 {
+			offset = 0
+		}
+		return db.Offset(offset).Limit(pageSize)
+	}
+}
+
+func WithNormalState() func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		return db.Where("t__state = 1")
+	}
+}

+ 34 - 0
dto/search/README.md

@@ -0,0 +1,34 @@
+## Search库
+
+|type|描述|query示例|
+|:---|:---|:---|
+|exact/iexact|等于|status=1|
+|contains/icontanins|包含|name=n|
+|gt/gte|大于/大于等于|age=18|
+|lt/lte|小于/小于等于|age=18|
+|startswith/istartswith|以…起始|content=hell|
+|endswith/iendswith|以…结束|content=world|
+|in|in查询|status[]=0&status[]=1|
+|isnull|isnull查询|startTime=1|
+|order|排序|sort=asc/sort=desc|
+
+e.g.
+```
+type ApplicationQuery struct {
+	Id       string    `search:"type:icontains;column:id;table:receipt" form:"id"`
+	Domain   string    `search:"type:icontains;column:domain;table:receipt" form:"domain"`
+	Version  string    `search:"type:exact;column:version;table:receipt" form:"version"`
+	Status   []int     `search:"type:in;column:status;table:receipt" form:"status"`
+	Start    time.Time `search:"type:gte;column:created_at;table:receipt" form:"start"`
+	End      time.Time `search:"type:lte;column:created_at;table:receipt" form:"end"`
+	TestJoin `search:"type:left;on:id:receipt_id;table:receipt_goods;join:receipts"`
+	ApplicationOrder
+}
+type ApplicationOrder struct {
+	IdOrder string `search:"type:order;column:id;table:receipt" form"id_order"`
+}
+
+type TestJoin struct {
+	PaymentAccount string `search:"type:icontains;column:payment_account;table:receipts" form:"payment_account"`
+}
+```

+ 117 - 0
dto/search/condition.go

@@ -0,0 +1,117 @@
+package search
+
+import "strings"
+
+type Condition interface {
+	SetWhere(k string, v []interface{})
+	SetOr(k string, v []interface{})
+	SetOrContains(k string, v []interface{})
+	SetOrder(k string)
+	SetJoinOn(t, on string) Condition
+}
+
+type GormCondition struct {
+	GormPublic
+	Join []*GormJoin
+}
+
+type GormPublic struct {
+	Where      map[string][]interface{}
+	Order      []string
+	Or         map[string][]interface{}
+	OrContains map[string][]interface{}
+}
+
+type GormJoin struct {
+	Type   string
+	JoinOn string
+	GormPublic
+}
+
+func (e *GormJoin) SetJoinOn(t, on string) Condition {
+	return nil
+}
+
+func (e *GormPublic) SetWhere(k string, v []interface{}) {
+	if e.Where == nil {
+		e.Where = make(map[string][]interface{})
+	}
+	e.Where[k] = v
+}
+
+func (e *GormPublic) SetOr(k string, v []interface{}) {
+	if e.Or == nil {
+		e.Or = make(map[string][]interface{})
+	}
+	e.Or[k] = v
+}
+func (e *GormPublic) SetOrContains(k string, v []interface{}) {
+	if e.OrContains == nil {
+		e.OrContains = make(map[string][]interface{})
+	}
+	e.OrContains[k] = v
+}
+
+func (e *GormPublic) SetOrder(k string) {
+	if e.Order == nil {
+		e.Order = make([]string, 0)
+	}
+	e.Order = append(e.Order, k)
+}
+
+func (e *GormCondition) SetJoinOn(t, on string) Condition {
+	if e.Join == nil {
+		e.Join = make([]*GormJoin, 0)
+	}
+	join := &GormJoin{
+		Type:       t,
+		JoinOn:     on,
+		GormPublic: GormPublic{},
+	}
+	e.Join = append(e.Join, join)
+	return join
+}
+
+type resolveSearchTag struct {
+	Type   string
+	Column string
+	Table  string
+	On     []string
+	Join   string
+}
+
+// makeTag 解析search的tag标签
+func makeTag(tag string) *resolveSearchTag {
+	r := &resolveSearchTag{}
+	tags := strings.Split(tag, ";")
+	var ts []string
+	for _, t := range tags {
+		ts = strings.Split(t, ":")
+		if len(ts) == 0 {
+			continue
+		}
+		switch ts[0] {
+		case "type":
+			if len(ts) > 1 {
+				r.Type = ts[1]
+			}
+		case "column":
+			if len(ts) > 1 {
+				r.Column = ts[1]
+			}
+		case "table":
+			if len(ts) > 1 {
+				r.Table = ts[1]
+			}
+		case "on":
+			if len(ts) > 1 {
+				r.On = ts[1:]
+			}
+		case "join":
+			if len(ts) > 1 {
+				r.Join = ts[1]
+			}
+		}
+	}
+	return r
+}

+ 103 - 0
dto/search/query.go

@@ -0,0 +1,103 @@
+package search
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+const (
+	// FromQueryTag tag标记
+	FromQueryTag = "search"
+	// OrderDefaultTag tag标记
+	DefaultTag = "default"
+	// Mysql 数据库标识
+	Mysql = "mysql"
+	// Postgres 数据库标识
+	Postgres = "postgres"
+)
+
+// ResolveSearchQuery 解析
+/**
+ * 	exact / iexact 等于
+ * 	contains / icontains 包含
+ *	gt / gte 大于 / 大于等于
+ *	lt / lte 小于 / 小于等于
+ *	startswith / istartswith 以…起始
+ *	endswith / iendswith 以…结束
+ *	in
+ *	isnull
+ *  order 排序		e.g. order[key]=desc     order[key]=asc
+ */
+func ResolveSearchQuery(driver string, q interface{}, condition Condition) {
+	qType := reflect.TypeOf(q)
+	qValue := reflect.ValueOf(q)
+	var tag string
+	var ok bool
+	var t *resolveSearchTag
+	for i := 0; i < qType.NumField(); i++ {
+		tag, ok = "", false
+		tag, ok = qType.Field(i).Tag.Lookup(FromQueryTag)
+		if !ok {
+			//递归调用
+			ResolveSearchQuery(driver, qValue.Field(i).Interface(), condition)
+			continue
+		}
+		switch tag {
+		case "-":
+			continue
+		}
+
+		defaultTag, defaultOk := qType.Field(i).Tag.Lookup(DefaultTag)
+
+		if qValue.Field(i).IsZero() && !defaultOk {
+			continue
+		}
+		t = makeTag(tag)
+		//解析
+		switch t.Type {
+		case "left":
+			//左关联
+			join := condition.SetJoinOn(t.Type, fmt.Sprintf(
+				"left join `%s` on `%s`.`%s` = `%s`.`%s`",
+				t.Join,
+				t.Join,
+				t.On[0],
+				t.Table,
+				t.On[1],
+			))
+			ResolveSearchQuery(driver, qValue.Field(i).Interface(), join)
+		case "exact", "iexact":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` = ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "contains":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
+		case "orcontains":
+			condition.SetOrContains(fmt.Sprintf("`%s`.`%s` like ", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String() + "%"})
+		case "gt":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` > ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "gte":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` >= ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "lt":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` < ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "lte":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` <= ?", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "startswith":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{qValue.Field(i).String() + "%"})
+		case "endswith":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` like ?", t.Table, t.Column), []interface{}{"%" + qValue.Field(i).String()})
+		case "in":
+			condition.SetWhere(fmt.Sprintf("`%s`.`%s` in (?)", t.Table, t.Column), []interface{}{qValue.Field(i).Interface()})
+		case "isnull":
+			if !(qValue.Field(i).IsZero() && qValue.Field(i).IsNil()) {
+				condition.SetWhere(fmt.Sprintf("`%s`.`%s` isnull", t.Table, t.Column), make([]interface{}, 0))
+			}
+		case "order":
+			switch strings.ToLower(qValue.Field(i).String()) {
+			case "desc", "asc":
+				condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, qValue.Field(i).String()))
+			default:
+				condition.SetOrder(fmt.Sprintf("`%s`.`%s` %s", t.Table, t.Column, defaultTag))
+			}
+		}
+	}
+}

+ 51 - 0
dto/search/query_test.go

@@ -0,0 +1,51 @@
+package search
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+type ApplicationQuery struct {
+	Id       string    `search:"type:icontains;column:id;table:receipt" form:"id"`
+	Domain   string    `search:"type:icontains;column:domain;table:receipt" form:"domain"`
+	Version  string    `search:"type:exact;column:version;table:receipt" form:"version"`
+	Status   []int     `search:"type:in;column:status;table:receipt" form:"status"`
+	Start    time.Time `search:"type:gte;column:created_at;table:receipt" form:"start"`
+	End      time.Time `search:"type:lte;column:created_at;table:receipt" form:"end"`
+	TestJoin `search:"type:left;on:id:receipt_id;table:receipt_goods;join:receipts"`
+	NotNeed  string `search:"-"`
+	ApplicationOrder
+}
+
+type ApplicationOrder struct {
+	IdOrder string `search:"type:order;column:id;table:receipt" form:"id_order"`
+}
+
+type TestJoin struct {
+	PaymentAccount string `search:"type:icontains;column:payment_account;table:receipts" form:"payment_account"`
+}
+
+func TestResolveSearchQuery(t *testing.T) {
+	// Only pass t into top-level Convey calls
+	Convey("Given some integer with a starting value", t, func() {
+		d := ApplicationQuery{
+			Id:               "aaa",
+			Domain:           "bbb",
+			Version:          "ccc",
+			Status:           []int{1, 2},
+			Start:            time.Now().Add(-8 * time.Hour),
+			End:              time.Now(),
+			ApplicationOrder: ApplicationOrder{IdOrder: "desc"},
+			TestJoin:         TestJoin{PaymentAccount: "1212"},
+		}
+		condition := &GormCondition{
+			GormPublic: GormPublic{},
+			Join:       make([]*GormJoin, 0),
+		}
+		ResolveSearchQuery("mysql", d, condition)
+		fmt.Println(condition)
+	})
+}

+ 12 - 5
go.mod

@@ -13,15 +13,21 @@ require (
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/vmihailenco/msgpack/v5 v5.3.5
 	github.com/xuri/excelize/v2 v2.7.1
+	gorm.io/driver/mysql v1.5.7
+	gorm.io/gorm v1.25.11
 )
 
 require (
-	baliance.com/gooxml v1.0.1 // indirect
+	github.com/andeya/ameda v1.5.3 // indirect
+	github.com/andeya/goutil v1.0.1 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bytedance/go-tagexpr/v2 v2.9.11 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/gomodule/redigo v2.0.0+incompatible // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/klauspost/compress v1.15.15 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 	github.com/minio/highwayhash v1.0.2 // indirect
@@ -31,6 +37,7 @@ require (
 	github.com/nats-io/jwt/v2 v2.3.0 // indirect
 	github.com/nats-io/nkeys v0.3.0 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
+	github.com/nyaruka/phonenumbers v1.4.0 // indirect
 	github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.14.0 // indirect
@@ -48,10 +55,10 @@ require (
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	golang.org/x/crypto v0.8.0 // indirect
 	golang.org/x/net v0.9.0 // indirect
-	golang.org/x/sync v0.1.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
 	golang.org/x/sys v0.7.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
-	google.golang.org/protobuf v1.28.1 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )

+ 32 - 3
go.sum

@@ -1,5 +1,3 @@
-baliance.com/gooxml v1.0.1 h1:fG5lmxmjEVFfbKQ2NuyCuU3hMuuOb5avh5a38SZNO1o=
-baliance.com/gooxml v1.0.1/go.mod h1:+gpUgmkAF4zCtwOFPNRLDAvpVRWoKs5EeQTSv/HYFnw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -43,6 +41,10 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
 github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
+github.com/andeya/ameda v1.5.3 h1:SvqnhQPZwwabS8HQTRGfJwWPl2w9ZIPInHAw9aE1Wlk=
+github.com/andeya/ameda v1.5.3/go.mod h1:FQDHRe1I995v6GG+8aJ7UIUToEmbdTJn/U26NCPIgXQ=
+github.com/andeya/goutil v1.0.1 h1:eiYwVyAnnK0dXU5FJsNjExkJW4exUGn/xefPt3k4eXg=
+github.com/andeya/goutil v1.0.1/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo=
 github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/beego/beego/v2 v2.0.7 h1:9KNnUM40tn3pbCOFfe6SJ1oOL0oTi/oBS/C/wCEdAXA=
@@ -54,6 +56,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/bytedance/go-tagexpr/v2 v2.9.11 h1:jJgmoDKPKacGl0llPYbYL/+/2N+Ng0vV0ipbnVssXHY=
+github.com/bytedance/go-tagexpr/v2 v2.9.11/go.mod h1:UAyKh4ZRLBPGsyTRFZoPqTni1TlojMdOJXQnEIPCX84=
 github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -134,6 +138,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
@@ -169,6 +175,10 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -225,6 +235,9 @@ github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV
 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
+github.com/nyaruka/phonenumbers v1.4.0 h1:ddhWiHnHCIX3n6ETDA58Zq5dkxkjlvgrDWM2OHHPCzU=
+github.com/nyaruka/phonenumbers v1.4.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@@ -304,10 +317,13 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
 github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
 github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
@@ -431,6 +447,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -491,8 +509,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
@@ -618,8 +639,11 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -643,6 +667,11 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
+gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
+gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 84 - 0
initialize/db.go

@@ -0,0 +1,84 @@
+package initialize
+
+import (
+	"ERP_storage/conf"
+	_ "github.com/go-sql-driver/mysql"
+
+	"fmt"
+	"log"
+	"time"
+
+	"gorm.io/gorm/logger"
+
+	"github.com/beego/beego/v2/core/logs"
+	beego "github.com/beego/beego/v2/server/web"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+)
+
+var DB *gorm.DB
+
+var Err error
+
+// 重写gorm日志的Writer
+type Writer struct {
+}
+
+func (w Writer) Printf(format string, args ...interface{}) {
+	// log.Infof(format, args...)
+	logs.Notice(format, args...)
+}
+
+// 设置psql
+func init() {
+	log.Println("连接数据库...")
+
+	sqlloglevel, err := beego.AppConfig.Int("sqlloglevel")
+	if err != nil {
+		log.Fatalf("无法获取sqlloglevel: %v", err)
+	}
+	slow_threshold, err := beego.AppConfig.Int("slow_threshold")
+	if err != nil {
+		log.Fatalf("无法获取slow_threshold: %v", err)
+	}
+
+	dataSource := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		conf.MysqlServer_Username,
+		conf.MysqlServer_Password,
+		conf.MysqlServer_UrlPort,
+		conf.MysqlServer_Database,
+	)
+
+	//设置gorm日志规则
+	newLogger := logger.New(
+		// log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer 单独设置grom日志输出
+		Writer{}, // beego日志info输出
+		logger.Config{
+			SlowThreshold:             time.Duration(slow_threshold) * time.Millisecond, // Slow SQL threshold
+			LogLevel:                  logger.LogLevel(sqlloglevel),                     // Log level
+			IgnoreRecordNotFoundError: true,                                             // Ignore ErrRecordNotFound error for logger
+			Colorful:                  true,                                             // Disable color
+		},
+	)
+
+	DB, Err = gorm.Open(mysql.Open(dataSource), &gorm.Config{
+		DisableForeignKeyConstraintWhenMigrating: true, // 禁用外键
+		Logger:                                   newLogger,
+		DisableAutomaticPing:                     true,
+	})
+
+	if Err != nil {
+		log.Fatalf("连接数据库失败: %v", Err)
+	}
+
+	SqlDB, err2 := DB.DB()
+	if err2 != nil {
+		log.Fatalf("连接数据库失败: %v", err2)
+	}
+
+	SqlDB.SetMaxIdleConns(conf.MysqlServer_MaxIdleConnections)
+	SqlDB.SetMaxOpenConns(conf.MysqlServer_MaxOpenConnections)
+	SqlDB.SetConnMaxLifetime(time.Hour)
+
+	log.Println("连接数据库完成...")
+}

+ 71 - 0
initialize/log.go

@@ -0,0 +1,71 @@
+package initialize
+
+import (
+	"encoding/json"
+	"github.com/beego/beego/v2/core/logs"
+	beego "github.com/beego/beego/v2/server/web"
+	"log"
+)
+
+func init() {
+	log.SetFlags(log.Lshortfile | log.Ltime | log.Ldate)
+	log.Println("系统日志初始化...")
+
+	maxdays, err := beego.AppConfig.Int("maxdays")
+	if err != nil {
+		log.Println("无法获取maxdays:", err)
+	}
+
+	level, err := beego.AppConfig.Int("level")
+	if err != nil {
+		log.Println("无法获取level:", err)
+	}
+
+	maxlines, err := beego.AppConfig.Int("maxlines")
+	if err != nil {
+		log.Println("无法获取maxlines:", err)
+	}
+
+	dataSource := &struct {
+		Filename string `json:"filename"`
+		Level    int    `json:"level"`
+		Maxlines int    `json:"maxlines"`
+		Maxsize  int    `json:"maxsize"`
+		Daily    bool   `json:"daily"`
+		Maxdays  int    `json:"maxdays"`
+		Color    bool   `json:"color"`
+	}{
+		Filename: "logs/logx/logx.log",
+		Level:    level,
+		Maxlines: maxlines,
+		Maxsize:  0,
+		Daily:    true,
+		Maxdays:  maxdays,
+		Color:    true,
+	}
+	dataSourceBytes, err := json.Marshal(dataSource)
+	if err != nil {
+		log.Println("无法创建dataSource:", err)
+	}
+	logs.SetLevel(level)
+	adapter_type, err := beego.AppConfig.Int("adapter_type")
+	if err != nil {
+		log.Println("无法获取adapter_type:", err)
+	}
+	// 日志输出选择
+	switch adapter_type {
+	case 0:
+		logs.SetLogger(logs.AdapterConsole)
+	case 1:
+		logs.Reset()
+		logs.SetLogger(logs.AdapterFile, string(dataSourceBytes))
+	case 2:
+		logs.SetLogger(logs.AdapterFile, string(dataSourceBytes))
+	default:
+		logs.SetLogger(logs.AdapterConsole)
+	}
+	// 是否记录日志的调用层级 默认是logs.SetLogFuncCallDepth(2)
+	logs.EnableFuncCallDepth(true)
+	logs.Async()
+	log.Println("系统日志初始化完成")
+}

+ 63 - 0
initialize/time.go

@@ -0,0 +1,63 @@
+package initialize
+
+import (
+	"database/sql/driver"
+	"fmt"
+	"time"
+)
+
+const timeFormat = "2006-01-02 15:04:05"
+const timezone = "Asia/Shanghai"
+
+// 全局定义
+type Time time.Time
+
+func (t Time) MarshalJSON() ([]byte, error) {
+	b := make([]byte, 0, len(timeFormat)+2)
+	if time.Time(t).IsZero() {
+		b = append(b, '"')
+		b = append(b, '"')
+		return b, nil
+	}
+
+	b = append(b, '"')
+	b = time.Time(t).AppendFormat(b, timeFormat)
+	b = append(b, '"')
+	return b, nil
+}
+
+func (t *Time) UnmarshalJSON(data []byte) (err error) {
+	now, err := time.ParseInLocation(`"`+timeFormat+`"`, string(data), time.Local)
+	*t = Time(now)
+	return
+}
+
+func (t Time) String() string {
+	if time.Time(t).IsZero() {
+		return ""
+	}
+	return time.Time(t).Format(timeFormat)
+}
+
+func (t Time) Local() time.Time {
+	loc, _ := time.LoadLocation(timezone)
+	return time.Time(t).In(loc)
+}
+
+func (t Time) Value() (driver.Value, error) {
+	var zeroTime time.Time
+	var ti = time.Time(t)
+	if ti.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return ti, nil
+}
+
+func (t *Time) Scan(v interface{}) error {
+	value, ok := v.(time.Time)
+	if ok {
+		*t = Time(value)
+		return nil
+	}
+	return fmt.Errorf("can not convert %v to timestamp", v)
+}

+ 1 - 0
main.go

@@ -5,6 +5,7 @@ import (
 	"ERP_storage/conf"
 	"ERP_storage/controllers"
 	"ERP_storage/logs"
+	_ "ERP_storage/models"
 	_ "ERP_storage/models/Account"
 	"ERP_storage/models/Basic"
 	_ "ERP_storage/models/Contract"

+ 26 - 0
models/ContractReview/ServiceItem.go

@@ -0,0 +1,26 @@
+package ContractReview
+
+import (
+	db "ERP_storage/initialize"
+)
+
+// 服务内容
+type ServiceItem struct {
+	Id                int    `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"` // 主键编码
+	T_name            string `json:"T_name" gorm:"size:128" `                             // 服务内容
+	T_service_type_id int    `json:"T_service_type_id" gorm:"size:128" `                  // 服务类型id
+	T_sale_type       string `json:"T_sale_type" gorm:"size:128"`                         // 销售类型
+	T_model           string `json:"T_model" gorm:"size:128"`                             // 型号
+	T_spec            string `json:"T_spec" gorm:"size:128"`                              // 规格
+
+	ServiceType ServiceTypeOmit `json:"ServiceType" gorm:"->;foreignkey:T_service_type_id;references:Id"` // 角色
+
+	T_State    int     `json:"T_State" gorm:"column:t__state;size(2);default(1)"`                        // 0 删除(伪删除)   1 正常
+	CreateTime db.Time `json:"CreateTime" gorm:"column:create_time;autoCreateTime;comment:创建时间"`     // 创建时间
+	UpdateTime db.Time `json:"UpdateTime" gorm:"column:update_time;autoUpdateTime;comment:最后更新时间"` // 最后更新时间
+
+}
+
+func (e *ServiceItem) TableName() string {
+	return "service_item"
+}

+ 28 - 0
models/ContractReview/ServiceType.go

@@ -0,0 +1,28 @@
+package ContractReview
+
+import (
+	db "ERP_storage/initialize"
+)
+
+// 服务类型
+type ServiceType struct {
+	Id         int     `json:"Id" gorm:"primaryKey;autoIncrement;comment:主键编码"`                    // 主键编码
+	T_name     string  `json:"T_name" gorm:"size:128" `                                            // 服务类型名称
+	T_State    int     `json:"T_State" gorm:"column:t__state;size(2);default(1)"`                  // 0 删除(伪删除)   1 正常
+	CreateTime db.Time `json:"CreateTime" gorm:"column:create_time;autoCreateTime;comment:创建时间"`   // 创建时间
+	UpdateTime db.Time `json:"UpdateTime" gorm:"column:update_time;autoUpdateTime;comment:最后更新时间"` // 最后更新时间
+
+}
+
+func (e *ServiceType) TableName() string {
+	return "service_type"
+}
+
+type ServiceTypeOmit struct {
+	Id     int    `json:"id,omitempty"`     // 主键编码
+	T_name string `json:"T_name,omitempty"` // 服务类型
+}
+
+func (e *ServiceTypeOmit) TableName() string {
+	return "service_type"
+}

+ 1 - 0
models/ContractReview/contractReview.go

@@ -0,0 +1 @@
+package ContractReview

+ 5 - 2
models/Stock/Device.go

@@ -142,10 +142,13 @@ func (dao *DeviceDaoImpl) AddOrUpdate_Device(r Device, T_type int) (id int64, er
 
 	r.T_remark = device.T_remark + T_remark
 	if len(r.T_project) > 0 {
-		r.T_project_log += r.T_project + "|"
+		r.T_project_log = device.T_project_log + r.T_project + "|"
+	} else {
+		r.T_project_log = device.T_project_log
+		r.T_project = device.T_project
 	}
 
-	_, err = dao.orm.Update(&r, "T_contract_number", "T_in_number", "T_out_number", "T_State", "T_remark", "T_project_log")
+	_, err = dao.orm.Update(&r, "T_contract_number", "T_in_number", "T_out_number", "T_State", "T_remark", "T_project", "T_project_log")
 	if err != nil {
 		logs.Error(lib.FuncName(), err)
 		return

+ 24 - 0
models/init.go

@@ -0,0 +1,24 @@
+package models
+
+import (
+	db "ERP_storage/initialize"
+	"ERP_storage/models/ContractReview"
+	_ "github.com/go-sql-driver/mysql"
+	"log"
+)
+
+func init() {
+	AutoMigrateDB()
+}
+
+func AutoMigrateDB() {
+	//自动迁移模式
+	err := db.DB.Set("gorm:table_options", "charset=utf8mb4").
+		AutoMigrate(
+			&ContractReview.ServiceType{},
+			&ContractReview.ServiceItem{},
+		)
+	if err != nil {
+		log.Fatalf("migrate db fail: %v", err)
+	}
+}

+ 34 - 0
routers/ContractReview.go

@@ -0,0 +1,34 @@
+package routers
+
+import (
+	"ERP_storage/controllers"
+	beego "github.com/beego/beego/v2/server/web"
+)
+
+func init() {
+	serviceType := beego.NewNamespace("/ServiceType",
+		beego.NSRouter("/List", &controllers.ContractReviewController{}, "*:ServiceType_List"), // 服务类型列表
+		beego.NSRouter("/Add", &controllers.ContractReviewController{}, "*:ServiceType_Add"),   // 添加服务类型
+		beego.NSRouter("/Edit", &controllers.ContractReviewController{}, "*:ServiceType_Edit"), // 编辑服务类型
+		beego.NSRouter("/Del", &controllers.ContractReviewController{}, "*:ServiceType_Del"),   // 删除服务类型
+	)
+	serviceItem := beego.NewNamespace("/ServiceItem",
+		beego.NSRouter("/List", &controllers.ContractReviewController{}, "*:ServiceItem_List"), // 服务内容列表
+		beego.NSRouter("/Add", &controllers.ContractReviewController{}, "*:ServiceItem_Add"),   // 添加服务内容
+		beego.NSRouter("/Edit", &controllers.ContractReviewController{}, "*:ServiceItem_Edit"), // 编辑服务内容
+		beego.NSRouter("/Del", &controllers.ContractReviewController{}, "*:ServiceItem_Del"),   // 删除服务内容
+	)
+	contractReview := beego.NewNamespace("/ContractReview") //beego.NSRouter("/List", &controllers.ContractReviewController{}, "*:Contract_List"),                 // 服务评审列表
+	//beego.NSRouter("/User_List", &controllers.ContractReviewController{}, "*:Contract_User_List"),       // 服务评审列表 - 销售人员
+	//beego.NSRouter("/Get", &controllers.ContractReviewController{}, "*:Contract_Get"),                   // 服务评审详情
+	//beego.NSRouter("/Approval", &controllers.ContractReviewController{}, "*:Contract_Approval"),         // 服务评审详情
+	//beego.NSRouter("/Add", &controllers.ContractReviewController{}, "*:Contract_Add"),                   // 添加服务评审
+	//beego.NSRouter("/Edit", &controllers.ContractReviewController{}, "*:Contract_Edit"),                 // 编辑服务评审
+	//beego.NSRouter("/Del", &controllers.ContractReviewController{}, "*:Contract_Del"),                   // 删除服务评审
+	//beego.NSRouter("/Product_List", &controllers.ContractReviewController{}, "*:Contract_Product_List"), // 服务评审产品列表
+	//beego.NSRouter("/Out_List", &controllers.ContractReviewController{}, "*:Contract_List_For_Out"),     // 服务评审产品列表
+	//beego.NSRouter("/Gen_Number", &controllers.ContractReviewController{}, "*:Contract_GenT_number"),    // 生成服务评审编号
+	//beego.NSRouter("/Stat", &controllers.ContractReviewController{}, "*:Contract_Stat"),                 // 统计服务评审金额
+
+	beego.AddNamespace(serviceType, serviceItem, contractReview)
+}

+ 111 - 0
services/ServiceItem.go

@@ -0,0 +1,111 @@
+package services
+
+import (
+	"ERP_storage/dto"
+	db "ERP_storage/initialize"
+	"ERP_storage/logs"
+	models "ERP_storage/models/ContractReview"
+	"errors"
+	"git.baozhida.cn/ERP_libs/lib"
+	"gorm.io/gorm"
+)
+
+type ServiceItem struct {
+}
+
+func (e *ServiceItem) GetPage(c *dto.ServiceItemPageReq) (list []models.ServiceItem, cnt int64) {
+
+	var err error
+	if c.GetPageSize() == 9999 {
+		err = db.DB.Model(&models.ServiceItem{}).
+			Scopes(
+				dto.MakeCondition(c.GetNeedSearch()),
+				dto.WithNormalState(),
+			).
+			Preload("ServiceType").
+			Find(&list).Limit(-1).Offset(-1).
+			Count(&cnt).Error
+	} else {
+		err = db.DB.Model(&models.ServiceItem{}).
+			Scopes(
+				dto.MakeCondition(c.GetNeedSearch()),
+				dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+				dto.WithNormalState(),
+			).
+			Preload("ServiceType").
+			Find(&list).Limit(-1).Offset(-1).
+			Count(&cnt).Error
+	}
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	return
+}
+
+func (e *ServiceItem) Get(Id int) (r models.ServiceItem, err error) {
+	err = db.DB.Scopes(dto.WithNormalState()).First(&r, Id).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+	return
+}
+
+// 添加
+func (e *ServiceItem) Insert(c *dto.ServiceItemInsertReq) (id int, err error) {
+	var data models.ServiceItem
+	err = db.DB.Scopes(dto.WithNormalState()).Where("t_name = ?", c.T_name).First(&data).Error
+	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return
+			}
+			id = data.Id
+			return
+		}
+		logs.Error("db error: %s", err)
+		return
+	}
+	id = data.Id
+	return
+}
+
+// 修改
+func (e *ServiceItem) Update(c *dto.ServiceItemUpdateReq) error {
+	var serviceItem = models.ServiceItem{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&serviceItem, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+	c.Generate(&serviceItem)
+	err = db.DB.Save(&serviceItem).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+	return nil
+
+}
+
+// 删除
+func (e *ServiceItem) Delete(c *dto.ServiceItemDeleteReq) error {
+	var serviceItem = models.ServiceItem{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&serviceItem, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return err
+	}
+	serviceItem.T_State = 0
+	err = db.DB.Save(&serviceItem).Error
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+	}
+	return err
+}

+ 121 - 0
services/ServiceType.go

@@ -0,0 +1,121 @@
+package services
+
+import (
+	"ERP_storage/dto"
+	db "ERP_storage/initialize"
+	"ERP_storage/logs"
+	models "ERP_storage/models/ContractReview"
+	"errors"
+	"git.baozhida.cn/ERP_libs/lib"
+	"gorm.io/gorm"
+)
+
+type ServiceType struct {
+}
+
+func (e *ServiceType) GetPage(c *dto.ServiceTypePageReq) (list []models.ServiceType, cnt int64) {
+
+	var err error
+	if c.GetPageSize() == 9999 {
+		err = db.DB.Model(&models.ServiceType{}).
+			Scopes(
+				dto.MakeCondition(c.GetNeedSearch()),
+				dto.WithNormalState(),
+			).
+			Find(&list).Limit(-1).Offset(-1).
+			Count(&cnt).Error
+	} else {
+		err = db.DB.Model(&models.ServiceType{}).
+			Scopes(
+				dto.MakeCondition(c.GetNeedSearch()),
+				dto.Paginate(c.GetPageSize(), c.GetPageIndex()),
+				dto.WithNormalState(),
+			).
+			Find(&list).Limit(-1).Offset(-1).
+			Count(&cnt).Error
+	}
+
+	if err != nil {
+		logs.Error("db error: %s ", err)
+		return
+	}
+	return
+}
+
+func (e *ServiceType) Get(Id int) (r models.ServiceType, err error) {
+	err = db.DB.Scopes(dto.WithNormalState()).First(&r, Id).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return
+	}
+	return
+}
+
+// 添加
+func (e *ServiceType) Insert(c *dto.ServiceTypeInsertReq) (id int, err error) {
+	var data models.ServiceType
+	err = db.DB.Scopes(dto.WithNormalState()).Where("t_name = ?", c.T_name).First(&data).Error
+	if err != nil {
+		// 没有则创建
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			c.Generate(&data)
+			err = db.DB.Create(&data).Error
+			if err != nil {
+				logs.Error("db error: %s", err)
+				return
+			}
+			id = data.Id
+			return
+		}
+		logs.Error("db error: %s", err)
+		return
+	}
+	id = data.Id
+	return
+}
+
+// 修改
+func (e *ServiceType) Update(c *dto.ServiceTypeUpdateReq) error {
+	var serviceType = models.ServiceType{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&serviceType, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+	c.Generate(&serviceType)
+	err = db.DB.Save(&serviceType).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.UpdateFailedErr
+	}
+	return nil
+
+}
+
+// 删除
+func (e *ServiceType) Delete(c *dto.ServiceTypeDeleteReq) error {
+	var serviceType = models.ServiceType{}
+	err := db.DB.Scopes(dto.WithNormalState()).First(&serviceType, c.GetId()).Error
+	if err != nil {
+		logs.Error("db error: %s", err)
+		return dto.GetNotFoundErr
+	}
+
+	var ServiceItemCount int64
+	err = db.DB.Model(&models.ServiceItem{}).
+		Scopes(dto.WithNormalState()).
+		Where("t_service_type_id = ?", serviceType.Id).
+		Count(&ServiceItemCount).Error
+
+	if ServiceItemCount > 0 {
+		return errors.New("存在已关联的服务内容,无法删除!")
+	}
+
+	serviceType.T_State = 0
+	err = db.DB.Save(&serviceType).Error
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return dto.DeleteFailedErr
+	}
+	return nil
+}