工单可以关联客户
This commit is contained in:
@@ -1,11 +1,511 @@
|
||||
package routers
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
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),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user