批量更新功能
This commit is contained in:
@@ -69,6 +69,99 @@ func buildMobiles(mobile string) string {
|
||||
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)
|
||||
@@ -221,6 +314,118 @@ func clearOtherPrimary(tenantID, companyType string, companyID, excludeID uint64
|
||||
_, _ = 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()
|
||||
@@ -259,6 +464,7 @@ func (c *BackendCrmContactController) List() {
|
||||
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)
|
||||
@@ -301,7 +507,9 @@ func (c *BackendCrmContactController) Add() {
|
||||
ContactName string `json:"contact_name"`
|
||||
Gender int8 `json:"gender"`
|
||||
Mobile string `json:"mobile"`
|
||||
Phone string `json:"phone"`
|
||||
// 手机号可登记多个(有的人有多个号码):优先使用,缺省时回退单个 mobile
|
||||
Mobiles []string `json:"mobiles"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Wechat string `json:"wechat"`
|
||||
QQ string `json:"qq"`
|
||||
@@ -335,8 +543,8 @@ func (c *BackendCrmContactController) Add() {
|
||||
CompanyID: p.RelatedID,
|
||||
Name: strings.TrimSpace(p.ContactName),
|
||||
Gender: p.Gender,
|
||||
Phone: strings.TrimSpace(p.Phone), // 座机
|
||||
Mobiles: buildMobiles(p.Mobile), // 手机号(JSON数组)
|
||||
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),
|
||||
@@ -381,7 +589,9 @@ func (c *BackendCrmContactController) Edit() {
|
||||
ContactName string `json:"contact_name"`
|
||||
Gender int8 `json:"gender"`
|
||||
Mobile string `json:"mobile"`
|
||||
Phone string `json:"phone"`
|
||||
// 手机号可登记多个(有的人有多个号码):优先使用,缺省时回退单个 mobile
|
||||
Mobiles []string `json:"mobiles"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Wechat string `json:"wechat"`
|
||||
QQ string `json:"qq"`
|
||||
@@ -430,8 +640,8 @@ func (c *BackendCrmContactController) Edit() {
|
||||
contact.CompanyID = companyID
|
||||
contact.Name = strings.TrimSpace(p.ContactName)
|
||||
contact.Gender = p.Gender
|
||||
contact.Phone = strings.TrimSpace(p.Phone) // 座机
|
||||
contact.Mobiles = buildMobiles(p.Mobile) // 手机号(JSON数组)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user