package routers import ( "slices" "time" "ops/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) var ( customerUserGroup TabUserGroups customerAdmins []uint ) // TabCustomer 客户主表 type TabCustomer struct { ID uint `gorm:"primarykey" json:"id"` FirstName string `gorm:"size:100;comment:名" json:"first_name"` LastName string `gorm:"size:100;comment:姓" json:"last_name"` Title string `gorm:"size:20;comment:称呼:Unit/Mr/Ms" json:"title"` CreatedBy uint `gorm:"not null;comment:创建人ID" json:"created_by"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime" json:"created_at"` UpdatedAt *time.Time `gorm:"type:datetime;autoUpdateTime" json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"` } // TabCustomerPhone 客户电话绑定表 type TabCustomerPhone struct { ID uint `gorm:"primarykey" json:"id"` CustomerID uint `gorm:"not null;index;comment:关联客户ID" json:"customer_id"` Prefix string `gorm:"size:10;comment:地区前缀:86/852/853" json:"prefix"` Phone string `gorm:"size:50;comment:电话号码" json:"phone"` Label string `gorm:"size:20;comment:标签:mobile/work/home/other" json:"label"` IsPrimary bool `gorm:"default:false;comment:是否主号码" json:"is_primary"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime" json:"created_at"` } // TabCustomerEmail 客户邮箱绑定表 type TabCustomerEmail struct { ID uint `gorm:"primarykey" json:"id"` CustomerID uint `gorm:"not null;index;comment:关联客户ID" json:"customer_id"` Email string `gorm:"size:200;comment:邮箱地址" json:"email"` Label string `gorm:"size:20;comment:标签:work/personal/other" json:"label"` IsPrimary bool `gorm:"default:false;comment:是否主邮箱" json:"is_primary"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime" json:"created_at"` } // TabCustomerCompany 客户单位绑定表 type TabCustomerCompany struct { ID uint `gorm:"primarykey" json:"id"` CustomerID uint `gorm:"not null;index;comment:关联客户ID" json:"customer_id"` CompanyName string `gorm:"size:200;comment:单位名称" json:"company_name"` Department string `gorm:"size:100;comment:部门" json:"department"` Position string `gorm:"size:100;comment:职位" json:"position"` IsPrimary bool `gorm:"default:false;comment:是否主单位" json:"is_primary"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime" json:"created_at"` } // TabCustomerLog 客户操作日志 type TabCustomerLog struct { ID uint `gorm:"primarykey"` CustomerID uint `gorm:"not null;index;comment:关联客户ID"` UserID uint `gorm:"not null;comment:操作人ID"` ActionType string `gorm:"size:50;not null;comment:操作类型:create/update/delete/query"` OldContent string `gorm:"type:text;comment:修改前内容(JSON)"` NewContent string `gorm:"type:text;comment:修改后内容(JSON)"` IP string `gorm:"size:50;comment:操作IP"` Remark string `gorm:"size:500;comment:备注"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:操作时间"` } type From_customer_add struct { FirstName string `json:"first_name"` LastName string `json:"last_name"` Title string `json:"title"` Phones []struct { Prefix string `json:"prefix"` Phone string `json:"phone"` Label string `json:"label"` IsPrimary bool `json:"is_primary"` } `json:"phones"` Emails []struct { Email string `json:"email"` Label string `json:"label"` IsPrimary bool `json:"is_primary"` } `json:"emails"` Companies []struct { CompanyName string `json:"company_name"` Department string `json:"department"` Position string `json:"position"` IsPrimary bool `json:"is_primary"` } `json:"companies"` } type From_customer_update struct { ID uint `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Title string `json:"title"` Phones []struct { Prefix string `json:"prefix"` Phone string `json:"phone"` Label string `json:"label"` IsPrimary bool `json:"is_primary"` } `json:"phones"` Emails []struct { Email string `json:"email"` Label string `json:"label"` IsPrimary bool `json:"is_primary"` } `json:"emails"` Companies []struct { CompanyName string `json:"company_name"` Department string `json:"department"` Position string `json:"position"` IsPrimary bool `json:"is_primary"` } `json:"companies"` } // CustomerUpdateAdminsCash 更新客户管理员缓存 func CustomerUpdateAdminsCash() { customerAdmins = nil customerAdmins = append(customerAdmins, 1) // id=1 系统管理员默认拥有所有权限 var binds []TabUserGroupBinds models.DB.Where("group_id = ?", customerUserGroup.ID).Find(&binds) for _, item := range binds { if !slices.Contains(customerAdmins, item.UserID) { customerAdmins = append(customerAdmins, item.UserID) } } } // customerAdminCheck 检查用户是否为客户管理员 func customerAdminCheck(userID uint) bool { return slices.Contains(customerAdmins, userID) } // canModifyCustomer 判断是否有权限修改/删除客户(创建者或管理员) func canModifyCustomer(userID, creatorUserID uint) bool { if slices.Contains(customerAdmins, userID) { return true } return userID == creatorUserID } // ApiCustomerInit 初始化客户模块 func ApiCustomerInit() { // 自动创建 customer_admin 用户组 models.DB.Where("name = ?", "customer_admin").FirstOrCreate(&customerUserGroup, TabUserGroups{ Name: "customer_admin", Type: "usergroup", }) // 自动迁移客户相关表 models.DB.AutoMigrate( &TabCustomer{}, &TabCustomerPhone{}, &TabCustomerEmail{}, &TabCustomerCompany{}, &TabCustomerLog{}, ) CustomerUpdateAdminsCash() } func ApiCustomer(r *gin.RouterGroup) { // POST /add - 新增客户(需 customer_admin 权限) r.POST("/add", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } if !customerAdminCheck(user.ID) { ReturnJson(ctx, "permission_denied", nil) return } var params From_customer_add if err := decodeJSON(data, ¶ms); err != nil { ReturnJson(ctx, "parameErr", nil) return } // 创建客户 customer := TabCustomer{ FirstName: params.FirstName, LastName: params.LastName, Title: params.Title, CreatedBy: user.ID, } if err := models.DB.Create(&customer).Error; err != nil { ReturnJson(ctx, "dbErr", nil) return } // 写入电话 for _, p := range params.Phones { models.DB.Create(&TabCustomerPhone{ CustomerID: customer.ID, Prefix: p.Prefix, Phone: p.Phone, Label: p.Label, IsPrimary: p.IsPrimary, }) } // 写入邮箱 for _, e := range params.Emails { models.DB.Create(&TabCustomerEmail{ CustomerID: customer.ID, Email: e.Email, Label: e.Label, IsPrimary: e.IsPrimary, }) } // 写入单位 for _, c := range params.Companies { models.DB.Create(&TabCustomerCompany{ CustomerID: customer.ID, CompanyName: c.CompanyName, Department: c.Department, Position: c.Position, IsPrimary: c.IsPrimary, }) } // 写日志 models.DB.Create(&TabCustomerLog{ CustomerID: customer.ID, UserID: user.ID, ActionType: "create", IP: ctx.ClientIP(), }) ReturnJson(ctx, "apiOK", gin.H{"id": customer.ID}) }) // POST /update - 编辑客户(创建者或管理员可操作) r.POST("/update", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } var params From_customer_update if err := decodeJSON(data, ¶ms); err != nil { ReturnJson(ctx, "parameErr", nil) return } if params.ID == 0 { ReturnJson(ctx, "parameErr", nil) return } // 查找客户 var customer TabCustomer if err := models.DB.Unscoped().First(&customer, params.ID).Error; err != nil { ReturnJson(ctx, "parameErr", nil) return } // 权限校验:只有创建者或管理员可以修改 if !canModifyCustomer(user.ID, customer.CreatedBy) { ReturnJson(ctx, "no_permission", nil) return } // 更新主表 models.DB.Model(&customer).Updates(map[string]interface{}{ "first_name": params.FirstName, "last_name": params.LastName, "title": params.Title, }) // 重建绑定表:删除旧的,写入新的 models.DB.Where("customer_id = ?", customer.ID).Delete(&TabCustomerPhone{}) for _, p := range params.Phones { models.DB.Create(&TabCustomerPhone{ CustomerID: customer.ID, Prefix: p.Prefix, Phone: p.Phone, Label: p.Label, IsPrimary: p.IsPrimary, }) } models.DB.Where("customer_id = ?", customer.ID).Delete(&TabCustomerEmail{}) for _, e := range params.Emails { models.DB.Create(&TabCustomerEmail{ CustomerID: customer.ID, Email: e.Email, Label: e.Label, IsPrimary: e.IsPrimary, }) } models.DB.Where("customer_id = ?", customer.ID).Delete(&TabCustomerCompany{}) for _, c := range params.Companies { models.DB.Create(&TabCustomerCompany{ CustomerID: customer.ID, CompanyName: c.CompanyName, Department: c.Department, Position: c.Position, IsPrimary: c.IsPrimary, }) } // 写日志 models.DB.Create(&TabCustomerLog{ CustomerID: customer.ID, UserID: user.ID, ActionType: "update", OldContent: "update", // 简化处理 IP: ctx.ClientIP(), }) ReturnJson(ctx, "apiOK", nil) }) // POST /delete - 软删除客户(创建者或管理员可操作) r.POST("/delete", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } type Req struct { ID uint `json:"id"` } var req Req if err := decodeJSON(data, &req); err != nil || req.ID == 0 { ReturnJson(ctx, "parameErr", nil) return } var customer TabCustomer if err := models.DB.First(&customer, req.ID).Error; err != nil { ReturnJson(ctx, "parameErr", nil) return } // 权限校验:只有创建者或管理员可以删除 if !canModifyCustomer(user.ID, customer.CreatedBy) { ReturnJson(ctx, "no_permission", nil) return } models.DB.Delete(&customer) // 写日志 models.DB.Create(&TabCustomerLog{ CustomerID: customer.ID, UserID: user.ID, ActionType: "delete", IP: ctx.ClientIP(), }) ReturnJson(ctx, "apiOK", nil) }) // POST /list - 客户列表(登录用户可读) r.POST("/list", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } type Req struct { Page int `json:"page"` PageSize int `json:"page_size"` Search string `json:"search"` } var req Req if err := decodeJSON(data, &req); err != nil { req.Page = 1 req.PageSize = 20 } if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } offset := (req.Page - 1) * req.PageSize // 子查询:获取每个客户的主电话/主邮箱/主单位用于搜索和展示 query := models.DB.Model(&TabCustomer{}) if req.Search != "" { search := "%" + req.Search + "%" query = query.Where( models.DB.Where("first_name LIKE ? OR last_name LIKE ?", search, search). Or("id IN (?)", models.DB.Table("tab_customer_phones").Select("customer_id").Where("phone LIKE ?", search)). Or("id IN (?)", models.DB.Table("tab_customer_emails").Select("customer_id").Where("email LIKE ?", search)). Or("id IN (?)", models.DB.Table("tab_customer_companies").Select("customer_id").Where("company_name LIKE ?", search)), ) } var total int64 query.Count(&total) var customers []TabCustomer query.Order("id DESC").Offset(offset).Limit(req.PageSize).Find(&customers) // 组装列表数据(含主电话/主邮箱/主单位 + 编辑权限) type CustomerListItem struct { TabCustomer PrimaryPhone string `json:"primary_phone"` PrimaryEmail string `json:"primary_email"` PrimaryCompany string `json:"primary_company"` Edit bool `json:"edit"` } var list []CustomerListItem for _, c := range customers { item := CustomerListItem{ TabCustomer: c, Edit: canModifyCustomer(user.ID, c.CreatedBy), } var phone TabCustomerPhone if err := models.DB.Where("customer_id = ? AND is_primary = ?", c.ID, true).First(&phone).Error; err == nil { item.PrimaryPhone = phone.Phone } else if err := models.DB.Where("customer_id = ?", c.ID).First(&phone).Error; err == nil { item.PrimaryPhone = phone.Phone } var email TabCustomerEmail if err := models.DB.Where("customer_id = ? AND is_primary = ?", c.ID, true).First(&email).Error; err == nil { item.PrimaryEmail = email.Email } else if err := models.DB.Where("customer_id = ?", c.ID).First(&email).Error; err == nil { item.PrimaryEmail = email.Email } var company TabCustomerCompany if err := models.DB.Where("customer_id = ? AND is_primary = ?", c.ID, true).First(&company).Error; err == nil { item.PrimaryCompany = company.CompanyName } else if err := models.DB.Where("customer_id = ?", c.ID).First(&company).Error; err == nil { item.PrimaryCompany = company.CompanyName } list = append(list, item) } ReturnJson(ctx, "apiOK", gin.H{ "customers": list, "total": total, "page": req.Page, "page_size": req.PageSize, }) }) // POST /get - 获取客户详情(登录用户可读) r.POST("/get", func(ctx *gin.Context) { isAuth, user, data := AuthenticationAuthority(ctx) if !isAuth { ReturnJson(ctx, "userNoLogin", nil) return } type Req struct { ID uint `json:"id"` } var req Req if err := decodeJSON(data, &req); err != nil || req.ID == 0 { ReturnJson(ctx, "parameErr", nil) return } var customer TabCustomer if err := models.DB.First(&customer, req.ID).Error; err != nil { ReturnJson(ctx, "parameErr", nil) return } // 获取电话列表 var phones []TabCustomerPhone models.DB.Where("customer_id = ?", req.ID).Find(&phones) // 获取邮箱列表 var emails []TabCustomerEmail models.DB.Where("customer_id = ?", req.ID).Find(&emails) // 获取单位列表 var companies []TabCustomerCompany models.DB.Where("customer_id = ?", req.ID).Find(&companies) // 写查询日志 // models.DB.Create(&TabCustomerLog{ // CustomerID: req.ID, // ActionType: "query", // IP: ctx.ClientIP(), // }) ReturnJson(ctx, "apiOK", gin.H{ "customer": customer, "phones": phones, "emails": emails, "companies": companies, "canModify": canModifyCustomer(user.ID, customer.CreatedBy), }) }) }