706 lines
21 KiB
Go
706 lines
21 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"server/models"
|
||
"server/pkg/jwtutil"
|
||
|
||
"github.com/beego/beego/v2/client/orm"
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
// BackendCrmContactController CRM 联系人管理(客户 / 供应商的对接人)
|
||
//
|
||
// 数据复用已有的「公司联系人」表 models.ErpCompanyContact:
|
||
// - related_type(1=客户,2=供应商) <-> company_type(customer/supplier)
|
||
// - related_id <-> company_id
|
||
// - contact_name <-> name
|
||
// - mobile(手机号) <-> mobiles(JSON数组,取第一个)
|
||
// - phone(座机) <-> phone
|
||
// - wechat / qq / dingtalk / home_address / status <-> 同名字段
|
||
//
|
||
// 这样 CRM 联系人与 ERP 公司联系人是同一份数据,不会分裂。
|
||
type BackendCrmContactController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
// crmContactDTO 返回给前端的联系人结构
|
||
type crmContactDTO struct {
|
||
ID uint64 `json:"id"`
|
||
RelatedType int `json:"related_type"`
|
||
RelatedID uint64 `json:"related_id"`
|
||
RelatedName string `json:"related_name"`
|
||
ContactName string `json:"contact_name"`
|
||
Gender int8 `json:"gender"`
|
||
Mobile string `json:"mobile"` // 手机号(mobiles 数组第一个)
|
||
Mobiles string `json:"mobiles"` // 手机号原始 JSON 数组
|
||
Phone string `json:"phone"` // 座机
|
||
Email string `json:"email"`
|
||
Wechat string `json:"wechat"` // 微信
|
||
QQ string `json:"qq"` // QQ
|
||
Dingtalk string `json:"dingtalk"` // 钉钉
|
||
Department string `json:"department"`
|
||
Position string `json:"position"`
|
||
HomeAddress string `json:"home_address"` // 家庭住址
|
||
IsPrimary int8 `json:"is_primary"`
|
||
Remark string `json:"remark"`
|
||
Status int8 `json:"status"` // 1-在职 0-离职
|
||
CreateTime time.Time `json:"create_time"`
|
||
UpdateTime time.Time `json:"update_time"`
|
||
}
|
||
|
||
// buildMobiles 将单个手机号转为 mobiles 的 JSON 数组字符串
|
||
func buildMobiles(mobile string) string {
|
||
mobile = strings.TrimSpace(mobile)
|
||
if mobile == "" {
|
||
return ""
|
||
}
|
||
arr := []string{mobile}
|
||
b, err := json.Marshal(arr)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
// buildMobilesFromList 将多个手机号去空、去重后转为 mobiles 的 JSON 数组字符串(最多 5 个,与 ERP 通讯录一致)
|
||
func buildMobilesFromList(mobiles []string) string {
|
||
cleaned := make([]string, 0, len(mobiles))
|
||
seen := map[string]bool{}
|
||
for _, m := range mobiles {
|
||
m = strings.TrimSpace(m)
|
||
if m == "" || seen[m] {
|
||
continue
|
||
}
|
||
seen[m] = true
|
||
cleaned = append(cleaned, m)
|
||
if len(cleaned) >= 5 {
|
||
break
|
||
}
|
||
}
|
||
if len(cleaned) == 0 {
|
||
return ""
|
||
}
|
||
b, err := json.Marshal(cleaned)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return string(b)
|
||
}
|
||
|
||
// normalizeMobiles 归一请求中的手机号:
|
||
// 优先取 mobiles(数组,CRM 联系人支持登记多个手机号,如一人多号),
|
||
// 缺省时回退单个 mobile(兼容线索转化 / 合同签约人自动建档等旧调用方)。
|
||
func normalizeMobiles(mobiles []string, mobile string) string {
|
||
if len(mobiles) == 0 {
|
||
return buildMobiles(mobile)
|
||
}
|
||
return buildMobilesFromList(mobiles)
|
||
}
|
||
|
||
// splitMobiles 把手机号文本(JSON 数组 / 逗号等分隔符拼接)拆成号码列表(去空)
|
||
func splitMobiles(mobiles string) []string {
|
||
mobiles = strings.TrimSpace(mobiles)
|
||
if mobiles == "" {
|
||
return nil
|
||
}
|
||
var arr []string
|
||
if err := json.Unmarshal([]byte(mobiles), &arr); err == nil {
|
||
out := make([]string, 0, len(arr))
|
||
for _, m := range arr {
|
||
if m = strings.TrimSpace(m); m != "" {
|
||
out = append(out, m)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
parts := strings.FieldsFunc(mobiles, func(r rune) bool {
|
||
switch r {
|
||
case ',', ',', '、', ';', ';', ' ', '/', '|':
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
out := make([]string, 0, len(parts))
|
||
for _, p := range parts {
|
||
if p = strings.TrimSpace(p); p != "" {
|
||
out = append(out, p)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// mobilesFromPayload 解析请求体中的手机号字段,兼容多种入参形式:
|
||
// - JSON 数组:["138…","139…"](新前端)
|
||
// - JSON 字符串:'["138…"]' 或 '138…,139…'(旧调用方)
|
||
// - 缺省:回退单个 mobile / phone 字段
|
||
//
|
||
// 统一返回 mobiles 的 JSON 数组字符串(去空去重,最多 5 个)。
|
||
func mobilesFromPayload(raw json.RawMessage, fallback string) string {
|
||
if len(raw) > 0 {
|
||
text := strings.TrimSpace(string(raw))
|
||
if text != "" && text != "null" {
|
||
var arr []string
|
||
if err := json.Unmarshal(raw, &arr); err == nil {
|
||
return buildMobilesFromList(arr)
|
||
}
|
||
var s string
|
||
if err := json.Unmarshal(raw, &s); err == nil {
|
||
if strings.TrimSpace(s) == "" {
|
||
return ""
|
||
}
|
||
return buildMobilesFromList(splitMobiles(s))
|
||
}
|
||
}
|
||
}
|
||
return buildMobiles(fallback)
|
||
}
|
||
|
||
// firstMobile 从 mobiles(JSON 数组或普通字符串)中取出第一个手机号
|
||
func firstMobile(mobiles string) string {
|
||
mobiles = strings.TrimSpace(mobiles)
|
||
if mobiles == "" {
|
||
return ""
|
||
}
|
||
var arr []string
|
||
if err := json.Unmarshal([]byte(mobiles), &arr); err == nil {
|
||
for _, m := range arr {
|
||
if strings.TrimSpace(m) != "" {
|
||
return strings.TrimSpace(m)
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
// 非 JSON,按原样返回
|
||
return mobiles
|
||
}
|
||
|
||
func crmContactCompanyType(relatedType int) string {
|
||
if relatedType == 2 {
|
||
return "supplier"
|
||
}
|
||
return "customer"
|
||
}
|
||
|
||
func crmContactRelatedType(companyType string) int {
|
||
if companyType == "supplier" {
|
||
return 2
|
||
}
|
||
return 1
|
||
}
|
||
|
||
// crmContactAtoi 将查询参数安全转为 int,非法值返回 0
|
||
func crmContactAtoi(s string) int {
|
||
n, _ := strconv.Atoi(strings.TrimSpace(s))
|
||
return n
|
||
}
|
||
|
||
func (c *BackendCrmContactController) contactClaims() (*jwtutil.Claims, error) {
|
||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||
if auth == "" {
|
||
return nil, fmt.Errorf("未登录")
|
||
}
|
||
parts := strings.SplitN(auth, " ", 2)
|
||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||
return nil, fmt.Errorf("认证信息格式错误")
|
||
}
|
||
claims, err := jwtutil.ParseToken(parts[1])
|
||
if err != nil {
|
||
return nil, fmt.Errorf("无效的token")
|
||
}
|
||
if claims.UserType != "backend" {
|
||
return nil, fmt.Errorf("无权访问")
|
||
}
|
||
return claims, nil
|
||
}
|
||
|
||
func (c *BackendCrmContactController) crmContactJsonErr(httpStatus, bizCode int, msg string) {
|
||
c.Ctx.Output.SetStatus(httpStatus)
|
||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendCrmContactController) crmContactOk(data interface{}) {
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// toCrmContactDTO 模型转 DTO,names 为关联对象名称映射(key: "customer:123")
|
||
func toCrmContactDTO(row models.ErpCompanyContact, names map[string]string) crmContactDTO {
|
||
relatedType := crmContactRelatedType(row.CompanyType)
|
||
key := fmt.Sprintf("%s:%d", row.CompanyType, row.CompanyID)
|
||
return crmContactDTO{
|
||
ID: row.ID,
|
||
RelatedType: relatedType,
|
||
RelatedID: row.CompanyID,
|
||
RelatedName: names[key],
|
||
ContactName: row.Name,
|
||
Gender: row.Gender,
|
||
Mobile: firstMobile(row.Mobiles),
|
||
Mobiles: row.Mobiles,
|
||
Phone: row.Phone,
|
||
Email: row.Email,
|
||
Wechat: row.Wechat,
|
||
QQ: row.QQ,
|
||
Dingtalk: row.Dingtalk,
|
||
Department: row.Department,
|
||
Position: row.Position,
|
||
HomeAddress: row.HomeAddress,
|
||
IsPrimary: row.IsPrimary,
|
||
Remark: row.Remark,
|
||
Status: row.Status,
|
||
CreateTime: row.CreateTime,
|
||
UpdateTime: row.UpdateTime,
|
||
}
|
||
}
|
||
|
||
// loadRelatedNames 批量查询关联的客户/供应商名称,避免逐条查询
|
||
func loadRelatedNames(tenantID string, rows []models.ErpCompanyContact) map[string]string {
|
||
names := map[string]string{}
|
||
if len(rows) == 0 || models.Orm == nil {
|
||
return names
|
||
}
|
||
customerIDs := make([]uint64, 0)
|
||
supplierIDs := make([]uint64, 0)
|
||
for _, r := range rows {
|
||
if r.CompanyType == "supplier" {
|
||
supplierIDs = append(supplierIDs, r.CompanyID)
|
||
} else {
|
||
customerIDs = append(customerIDs, r.CompanyID)
|
||
}
|
||
}
|
||
if len(customerIDs) > 0 {
|
||
var list []models.TenantCrmCustomer
|
||
if _, err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
|
||
Filter("tenant_id", tenantID).
|
||
Filter("id__in", customerIDs).
|
||
All(&list); err == nil {
|
||
for _, r := range list {
|
||
names[fmt.Sprintf("customer:%d", r.ID)] = r.CustomerName
|
||
}
|
||
}
|
||
}
|
||
if len(supplierIDs) > 0 {
|
||
var list []models.TenantCrmSupplier
|
||
if _, err := models.Orm.QueryTable(new(models.TenantCrmSupplier)).
|
||
Filter("tenant_id", tenantID).
|
||
Filter("id__in", supplierIDs).
|
||
All(&list); err == nil {
|
||
for _, r := range list {
|
||
names[fmt.Sprintf("supplier:%d", r.ID)] = r.SupplierName
|
||
}
|
||
}
|
||
}
|
||
return names
|
||
}
|
||
|
||
// clearOtherPrimary 将同一公司下的其他联系人取消主联系人,保证只有一个主联系人
|
||
func clearOtherPrimary(tenantID, companyType string, companyID, excludeID uint64) {
|
||
if models.Orm == nil || companyID == 0 {
|
||
return
|
||
}
|
||
qs := models.Orm.QueryTable(new(models.ErpCompanyContact)).
|
||
Filter("tenant_id", tenantID).
|
||
Filter("company_type", companyType).
|
||
Filter("company_id", companyID).
|
||
Filter("delete_time__isnull", true).
|
||
Exclude("id", excludeID)
|
||
_, _ = qs.Update(map[string]interface{}{"is_primary": 0})
|
||
}
|
||
|
||
// isMobileNumber 粗略识别手机号(11 位、1 开头,忽略空格 / 横线等分隔符);
|
||
// 非手机号按座机写入 phone 字段,保证联系人资料里手机号 / 座机分列正确。
|
||
func isMobileNumber(phone string) bool {
|
||
digits := strings.Map(func(r rune) rune {
|
||
if r >= '0' && r <= '9' {
|
||
return r
|
||
}
|
||
return -1
|
||
}, phone)
|
||
return len(digits) == 11 && digits[0] == '1'
|
||
}
|
||
|
||
// crmContactCompanyExists 关联企业(客户 / 供应商)在当前租户下是否存在且未删除
|
||
func crmContactCompanyExists(tenantID, companyType string, companyID uint64) bool {
|
||
if models.Orm == nil || companyID == 0 {
|
||
return false
|
||
}
|
||
if companyType == "supplier" {
|
||
cnt, err := models.Orm.QueryTable(new(models.TenantCrmSupplier)).
|
||
Filter("id", companyID).Filter("tenant_id", tenantID).
|
||
Filter("delete_time__isnull", true).Count()
|
||
return err == nil && cnt > 0
|
||
}
|
||
cnt, err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
|
||
Filter("id", companyID).Filter("tenant_id", tenantID).
|
||
Filter("delete_time__isnull", true).Count()
|
||
return err == nil && cnt > 0
|
||
}
|
||
|
||
// contactPhoneMatched 联系人是否已登记该电话(手机号数组或座机任一命中)
|
||
func contactPhoneMatched(row models.ErpCompanyContact, phone string) bool {
|
||
if strings.TrimSpace(row.Phone) == phone {
|
||
return true
|
||
}
|
||
var mobiles []string
|
||
if err := json.Unmarshal([]byte(strings.TrimSpace(row.Mobiles)), &mobiles); err == nil {
|
||
for _, m := range mobiles {
|
||
if strings.TrimSpace(m) == phone {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// crmEnsureCompanyContact 确保企业通讯录(联系人)中已有该对接人:
|
||
// - 同名或同电话已存在时不再重复建档,仅在原联系人缺电话时补全;
|
||
// - 不存在时按「姓名 + 电话」建档,其余资料留空,后续可在联系人管理中补全;
|
||
// - 该企业还没有任何联系人时,默认把首个联系人设为主联系人。
|
||
func crmEnsureCompanyContact(tenantID, companyType string, companyID uint64, name, phone string) {
|
||
if models.Orm == nil || companyID == 0 {
|
||
return
|
||
}
|
||
name = strings.TrimSpace(name)
|
||
phone = strings.TrimSpace(phone)
|
||
if name == "" {
|
||
return
|
||
}
|
||
var rows []models.ErpCompanyContact
|
||
if _, err := models.Orm.QueryTable(new(models.ErpCompanyContact)).
|
||
Filter("tenant_id", tenantID).
|
||
Filter("company_type", companyType).
|
||
Filter("company_id", companyID).
|
||
Filter("delete_time__isnull", true).
|
||
All(&rows); err != nil && err != orm.ErrNoRows {
|
||
return
|
||
}
|
||
for i := range rows {
|
||
row := rows[i]
|
||
sameName := strings.EqualFold(strings.TrimSpace(row.Name), name)
|
||
samePhone := phone != "" && contactPhoneMatched(row, phone)
|
||
if !sameName && !samePhone {
|
||
continue
|
||
}
|
||
// 已存在:仅在原记录没有电话时补全,不覆盖其他已填资料
|
||
if phone != "" && strings.TrimSpace(row.Mobiles) == "" && strings.TrimSpace(row.Phone) == "" {
|
||
up := map[string]interface{}{"update_time": time.Now()}
|
||
if isMobileNumber(phone) {
|
||
up["mobiles"] = buildMobiles(phone)
|
||
} else {
|
||
up["phone"] = phone
|
||
}
|
||
_, _ = models.Orm.QueryTable(new(models.ErpCompanyContact)).
|
||
Filter("id", row.ID).Update(up)
|
||
}
|
||
return
|
||
}
|
||
|
||
now := time.Now()
|
||
contact := models.ErpCompanyContact{
|
||
TenantID: tenantID,
|
||
CompanyType: companyType,
|
||
CompanyID: companyID,
|
||
Name: name,
|
||
Status: 1,
|
||
Remark: "由合同签约信息自动写入,可在联系人中补全其他资料",
|
||
CreateTime: now,
|
||
UpdateTime: now,
|
||
}
|
||
if len(rows) == 0 {
|
||
contact.IsPrimary = 1
|
||
}
|
||
if phone != "" {
|
||
if isMobileNumber(phone) {
|
||
contact.Mobiles = buildMobiles(phone)
|
||
} else {
|
||
contact.Phone = phone
|
||
}
|
||
}
|
||
_, _ = models.Orm.Insert(&contact)
|
||
}
|
||
|
||
// List GET /backend/crm/contact/list?related_type=1&related_id=7&keyword=&is_primary=
|
||
func (c *BackendCrmContactController) List() {
|
||
claims, err := c.contactClaims()
|
||
if err != nil {
|
||
c.crmContactJsonErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
|
||
page, _ := c.GetInt("page", 1)
|
||
pageSize, _ := c.GetInt("pageSize", 20)
|
||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||
relatedTypeStr := strings.TrimSpace(c.GetString("related_type"))
|
||
relatedIDStr := strings.TrimSpace(c.GetString("related_id"))
|
||
primaryStr := strings.TrimSpace(c.GetString("is_primary"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 100 {
|
||
pageSize = 20
|
||
}
|
||
|
||
tenantID := fmt.Sprintf("%d", claims.TenantId)
|
||
cond := orm.NewCondition().
|
||
And("tenant_id", tenantID).
|
||
And("delete_time__isnull", true)
|
||
|
||
if relatedTypeStr != "" {
|
||
cond = cond.And("company_type", crmContactCompanyType(crmContactAtoi(relatedTypeStr)))
|
||
}
|
||
if relatedIDStr != "" && relatedIDStr != "0" {
|
||
cond = cond.And("company_id", relatedIDStr)
|
||
}
|
||
if primaryStr != "" {
|
||
cond = cond.And("is_primary", crmContactAtoi(primaryStr))
|
||
}
|
||
if keyword != "" {
|
||
kw := orm.NewCondition().
|
||
Or("name__contains", keyword).
|
||
Or("mobiles__contains", keyword).
|
||
Or("phone__contains", keyword).
|
||
Or("email__contains", keyword)
|
||
cond = cond.AndCond(kw)
|
||
}
|
||
|
||
qs := models.Orm.QueryTable(new(models.ErpCompanyContact)).SetCond(cond)
|
||
total, _ := qs.Count()
|
||
var rows []models.ErpCompanyContact
|
||
if total > 0 {
|
||
_, _ = qs.OrderBy("-is_primary", "-update_time").
|
||
Offset((page - 1) * pageSize).
|
||
Limit(pageSize).
|
||
All(&rows)
|
||
}
|
||
|
||
names := loadRelatedNames(tenantID, rows)
|
||
list := make([]crmContactDTO, 0, len(rows))
|
||
for _, r := range rows {
|
||
list = append(list, toCrmContactDTO(r, names))
|
||
}
|
||
|
||
c.crmContactOk(map[string]interface{}{
|
||
"list": list,
|
||
"total": total,
|
||
"page": page,
|
||
"pageSize": pageSize,
|
||
})
|
||
}
|
||
|
||
// Add POST /backend/crm/contact/add
|
||
func (c *BackendCrmContactController) Add() {
|
||
claims, err := c.contactClaims()
|
||
if err != nil {
|
||
c.crmContactJsonErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
var p struct {
|
||
RelatedType int `json:"related_type"`
|
||
RelatedID uint64 `json:"related_id"`
|
||
ContactName string `json:"contact_name"`
|
||
Gender int8 `json:"gender"`
|
||
Mobile string `json:"mobile"`
|
||
// 手机号可登记多个(有的人有多个号码):优先使用,缺省时回退单个 mobile
|
||
Mobiles []string `json:"mobiles"`
|
||
Phone string `json:"phone"`
|
||
Email string `json:"email"`
|
||
Wechat string `json:"wechat"`
|
||
QQ string `json:"qq"`
|
||
Dingtalk string `json:"dingtalk"`
|
||
Department string `json:"department"`
|
||
Position string `json:"position"`
|
||
HomeAddress string `json:"home_address"`
|
||
IsPrimary int8 `json:"is_primary"`
|
||
Status int8 `json:"status"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||
if err := json.Unmarshal(raw, &p); err != nil {
|
||
c.crmContactJsonErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
if strings.TrimSpace(p.ContactName) == "" {
|
||
c.crmContactJsonErr(400, 400, "联系人姓名不能为空")
|
||
return
|
||
}
|
||
if p.RelatedID == 0 {
|
||
c.crmContactJsonErr(400, 400, "请选择关联对象")
|
||
return
|
||
}
|
||
|
||
companyType := crmContactCompanyType(p.RelatedType)
|
||
now := time.Now()
|
||
contact := models.ErpCompanyContact{
|
||
TenantID: fmt.Sprintf("%d", claims.TenantId),
|
||
CompanyType: companyType,
|
||
CompanyID: p.RelatedID,
|
||
Name: strings.TrimSpace(p.ContactName),
|
||
Gender: p.Gender,
|
||
Phone: strings.TrimSpace(p.Phone), // 座机
|
||
Mobiles: normalizeMobiles(p.Mobiles, p.Mobile), // 手机号(支持多个,JSON数组)
|
||
Email: strings.TrimSpace(p.Email),
|
||
Wechat: strings.TrimSpace(p.Wechat),
|
||
QQ: strings.TrimSpace(p.QQ),
|
||
Dingtalk: strings.TrimSpace(p.Dingtalk),
|
||
Department: strings.TrimSpace(p.Department),
|
||
Position: strings.TrimSpace(p.Position),
|
||
HomeAddress: strings.TrimSpace(p.HomeAddress),
|
||
IsPrimary: p.IsPrimary,
|
||
Status: 1,
|
||
Remark: p.Remark,
|
||
CreateTime: now,
|
||
UpdateTime: now,
|
||
}
|
||
// 新增联系人默认在职
|
||
if p.Status == 0 {
|
||
contact.Status = 1
|
||
} else {
|
||
contact.Status = p.Status
|
||
}
|
||
id, err := models.Orm.Insert(&contact)
|
||
if err != nil {
|
||
c.crmContactJsonErr(500, 500, "新增失败: "+err.Error())
|
||
return
|
||
}
|
||
if p.IsPrimary == 1 {
|
||
clearOtherPrimary(fmt.Sprintf("%d", claims.TenantId), companyType, p.RelatedID, uint64(id))
|
||
}
|
||
c.crmContactOk(map[string]interface{}{"id": id})
|
||
}
|
||
|
||
// Edit POST /backend/crm/contact/edit
|
||
func (c *BackendCrmContactController) Edit() {
|
||
claims, err := c.contactClaims()
|
||
if err != nil {
|
||
c.crmContactJsonErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
var p struct {
|
||
ID uint64 `json:"id"`
|
||
RelatedType int `json:"related_type"`
|
||
RelatedID uint64 `json:"related_id"`
|
||
ContactName string `json:"contact_name"`
|
||
Gender int8 `json:"gender"`
|
||
Mobile string `json:"mobile"`
|
||
// 手机号可登记多个(有的人有多个号码):优先使用,缺省时回退单个 mobile
|
||
Mobiles []string `json:"mobiles"`
|
||
Phone string `json:"phone"`
|
||
Email string `json:"email"`
|
||
Wechat string `json:"wechat"`
|
||
QQ string `json:"qq"`
|
||
Dingtalk string `json:"dingtalk"`
|
||
Department string `json:"department"`
|
||
Position string `json:"position"`
|
||
HomeAddress string `json:"home_address"`
|
||
IsPrimary int8 `json:"is_primary"`
|
||
Status int8 `json:"status"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||
if err := json.Unmarshal(raw, &p); err != nil {
|
||
c.crmContactJsonErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
if p.ID == 0 {
|
||
c.crmContactJsonErr(400, 400, "缺少ID")
|
||
return
|
||
}
|
||
if strings.TrimSpace(p.ContactName) == "" {
|
||
c.crmContactJsonErr(400, 400, "联系人姓名不能为空")
|
||
return
|
||
}
|
||
|
||
tenantID := fmt.Sprintf("%d", claims.TenantId)
|
||
var contact models.ErpCompanyContact
|
||
if err := models.Orm.QueryTable(new(models.ErpCompanyContact)).
|
||
Filter("id", p.ID).
|
||
Filter("tenant_id", tenantID).
|
||
Filter("delete_time__isnull", true).
|
||
One(&contact); err != nil {
|
||
c.crmContactJsonErr(404, 404, "联系人未找到")
|
||
return
|
||
}
|
||
|
||
// 允许调整关联对象;未传则保持原值
|
||
companyType := contact.CompanyType
|
||
companyID := contact.CompanyID
|
||
if p.RelatedID > 0 {
|
||
companyType = crmContactCompanyType(p.RelatedType)
|
||
companyID = p.RelatedID
|
||
}
|
||
|
||
contact.CompanyType = companyType
|
||
contact.CompanyID = companyID
|
||
contact.Name = strings.TrimSpace(p.ContactName)
|
||
contact.Gender = p.Gender
|
||
contact.Phone = strings.TrimSpace(p.Phone) // 座机
|
||
contact.Mobiles = normalizeMobiles(p.Mobiles, p.Mobile) // 手机号(支持多个,JSON数组)
|
||
contact.Email = strings.TrimSpace(p.Email)
|
||
contact.Wechat = strings.TrimSpace(p.Wechat)
|
||
contact.QQ = strings.TrimSpace(p.QQ)
|
||
contact.Dingtalk = strings.TrimSpace(p.Dingtalk)
|
||
contact.Department = strings.TrimSpace(p.Department)
|
||
contact.Position = strings.TrimSpace(p.Position)
|
||
contact.HomeAddress = strings.TrimSpace(p.HomeAddress)
|
||
contact.IsPrimary = p.IsPrimary
|
||
contact.Status = p.Status
|
||
contact.Remark = p.Remark
|
||
contact.UpdateTime = time.Now()
|
||
|
||
if _, err := models.Orm.Update(&contact); err != nil {
|
||
c.crmContactJsonErr(500, 500, "更新失败: "+err.Error())
|
||
return
|
||
}
|
||
if p.IsPrimary == 1 {
|
||
clearOtherPrimary(tenantID, companyType, companyID, contact.ID)
|
||
}
|
||
c.crmContactOk(map[string]interface{}{"id": contact.ID})
|
||
}
|
||
|
||
// Delete POST /backend/crm/contact/delete 软删除
|
||
func (c *BackendCrmContactController) Delete() {
|
||
claims, err := c.contactClaims()
|
||
if err != nil {
|
||
c.crmContactJsonErr(401, 401, err.Error())
|
||
return
|
||
}
|
||
var p struct {
|
||
ID uint64 `json:"id"`
|
||
}
|
||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||
if err := json.Unmarshal(raw, &p); err != nil {
|
||
c.crmContactJsonErr(400, 400, "参数错误")
|
||
return
|
||
}
|
||
if p.ID == 0 {
|
||
c.crmContactJsonErr(400, 400, "缺少ID")
|
||
return
|
||
}
|
||
|
||
now := time.Now()
|
||
num, err := models.Orm.QueryTable(new(models.ErpCompanyContact)).
|
||
Filter("id", p.ID).
|
||
Filter("tenant_id", fmt.Sprintf("%d", claims.TenantId)).
|
||
Filter("delete_time__isnull", true).
|
||
Update(map[string]interface{}{
|
||
"delete_time": now,
|
||
"update_time": now,
|
||
})
|
||
if err != nil {
|
||
c.crmContactJsonErr(500, 500, "删除失败: "+err.Error())
|
||
return
|
||
}
|
||
if num == 0 {
|
||
c.crmContactJsonErr(404, 404, "联系人未找到")
|
||
return
|
||
}
|
||
c.crmContactOk(nil)
|
||
}
|