Files
yunzerwebsiteallinone/go/controllers/backend_crm_pipeline_common.go
T

248 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"fmt"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
beego "github.com/beego/beego/v2/server/web"
)
// CRM 业务管线(线索/商机/项目/回访)公共辅助方法。
// related_type 约定:1=线索 2=商机 3=项目
// pipelineClaims 解析租户端后台 JWT。
func pipelineClaims(c *beego.Controller) (*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" && claims.UserType != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
// pipelineOk 统一成功响应。
func pipelineOk(c *beego.Controller, data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// pipelineErr 统一错误响应。
func pipelineErr(c *beego.Controller, httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
// pipelineTenantID 返回当前租户ID字符串。
func pipelineTenantID(claims *jwtutil.Claims) string {
return fmt.Sprintf("%d", claims.TenantId)
}
// pipelineUID 返回当前用户ID字符串。
func pipelineUID(claims *jwtutil.Claims) string {
return fmt.Sprintf("%d", claims.UserID)
}
// parsePipelineDateTime 解析时间:支持 "2006-01-02 15:04:05"、"2006-01-02T15:04:05"、"2006-01-02"。
func parsePipelineDateTime(s string) *time.Time {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
layouts := []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02T15:04",
"2006-01-02 15:04",
"2006-01-02",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, s, time.Local); err == nil {
return &t
}
}
return nil
}
// parsePipelineDate 解析日期(仅取年月日,返回当天零点)。
func parsePipelineDate(s string) *time.Time {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
if t, err := time.ParseInLocation("2006-01-02", s, time.Local); err == nil {
return &t
}
return parsePipelineDateTime(s)
}
// crmWriteLog 写入 CRM 操作日志(失败静默忽略,不影响主流程)。
func crmWriteLog(tenantID string, relatedType int8, relatedID uint64, action, content string, claims *jwtutil.Claims) {
if models.Orm == nil || relatedID == 0 {
return
}
operatorID, operatorName := "", ""
if claims != nil {
operatorID = pipelineUID(claims)
operatorName = claims.Username
}
log := models.TenantCrmOperateLog{
TenantID: tenantID,
RelatedType: relatedType,
RelatedID: relatedID,
Action: action,
Content: content,
OperatorID: operatorID,
OperatorName: operatorName,
CreateTime: time.Now(),
}
_, _ = models.Orm.Insert(&log)
}
// relatedTypeText 关联类型文案。
func relatedTypeText(t int8) string {
switch t {
case 1:
return "线索"
case 2:
return "商机"
case 3:
return "项目"
default:
return "未知"
}
}
// mobileInMobiles 判断手机号是否已存在于 mobiles(JSON 数组或普通字符串)中。
func mobileInMobiles(mobile, mobiles string) bool {
mobile = strings.TrimSpace(mobile)
if mobile == "" || strings.TrimSpace(mobiles) == "" {
return false
}
if firstMobile(mobiles) == mobile {
return true
}
return strings.Contains(mobiles, mobile)
}
// mergeCompanyContact 合并过程库联系人到正式库联系人:仅补齐正式库空缺字段,不覆盖已有数据。
func mergeCompanyContact(dst *models.ErpCompanyContact, src models.TenantCrmEntityContact) {
changed := false
if dst.Wechat == "" && strings.TrimSpace(src.Wechat) != "" {
dst.Wechat = strings.TrimSpace(src.Wechat)
changed = true
}
if dst.QQ == "" && strings.TrimSpace(src.QQ) != "" {
dst.QQ = strings.TrimSpace(src.QQ)
changed = true
}
if dst.Email == "" && strings.TrimSpace(src.Email) != "" {
dst.Email = strings.TrimSpace(src.Email)
changed = true
}
if dst.Position == "" && strings.TrimSpace(src.Position) != "" {
dst.Position = strings.TrimSpace(src.Position)
changed = true
}
if strings.TrimSpace(dst.Mobiles) == "" && strings.TrimSpace(src.Mobile) != "" {
dst.Mobiles = buildMobiles(src.Mobile)
changed = true
}
if dst.Remark == "" && strings.TrimSpace(src.Remark) != "" {
dst.Remark = strings.TrimSpace(src.Remark)
changed = true
}
if src.IsPrimary == 1 && dst.IsPrimary != 1 {
dst.IsPrimary = 1
changed = true
}
if changed {
dst.UpdateTime = time.Now()
_, _ = models.Orm.Update(dst)
}
}
// syncContactsToCompany 把线索/商机过程库(yz_tenant_crm_entity_contact)的联系人
// 去重同步到正式公司联系人库(yz_backend_contact_company,company_type=customer)。
// 去重键:company_type + company_id + name + mobile;命中则补齐字段,否则新增。
// 返回 (新增数, 合并数)。
func syncContactsToCompany(tenantID string, customerID uint64, contacts []models.TenantCrmEntityContact) (int, int) {
if models.Orm == nil || customerID == 0 || len(contacts) == 0 {
return 0, 0
}
inserted, merged := 0, 0
seen := map[string]bool{}
for _, src := range contacts {
name := strings.TrimSpace(src.ContactName)
if name == "" {
continue
}
mobile := strings.TrimSpace(src.Mobile)
// 本次待同步集合内去重(同名同手机只处理一次)
key := name + "|" + mobile
if seen[key] {
continue
}
seen[key] = true
var existingList []models.ErpCompanyContact
_, _ = models.Orm.QueryTable(new(models.ErpCompanyContact)).
Filter("tenant_id", tenantID).
Filter("company_type", "customer").
Filter("company_id", customerID).
Filter("name", name).
Filter("delete_time__isnull", true).
All(&existingList)
matched := false
for i := range existingList {
// 有手机号时必须手机号匹配;无手机号时按同名视为同一人
if mobile == "" || mobileInMobiles(mobile, existingList[i].Mobiles) {
mergeCompanyContact(&existingList[i], src)
merged++
matched = true
break
}
}
if matched {
continue
}
row := models.ErpCompanyContact{
TenantID: tenantID,
CompanyType: "customer",
CompanyID: customerID,
Name: name,
Phone: "",
Mobiles: buildMobiles(mobile),
Wechat: strings.TrimSpace(src.Wechat),
QQ: strings.TrimSpace(src.QQ),
Email: strings.TrimSpace(src.Email),
Position: strings.TrimSpace(src.Position),
IsPrimary: src.IsPrimary,
Status: 1,
Remark: strings.TrimSpace(src.Remark),
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := models.Orm.Insert(&row); err == nil {
inserted++
}
}
return inserted, merged
}