package maycur import ( "encoding/json" "fmt" "hongze/hz_crm_api/models" "hongze/hz_crm_api/models/system" "hongze/hz_crm_api/services/alarm_msg" "hongze/hz_crm_api/utils" "strconv" "strings" "time" ) // CompanyProfilePageListReq 客户档案分页列表请求体 type CompanyProfilePageListReq struct { ReferenceDataBizCode string `description:"档案编码" json:"referenceDataBizCode"` Offset int `description:"偏移量" json:"offset"` PageSize int `description:"每页数据量" json:"pageSize"` } // CompanyProfilePageListResp 客户档案分页列表响应体 type CompanyProfilePageListResp struct { Code string `description:"状态码"` Message string `description:"返回信息"` Data CompanyProfilePageListData `description:"返回数据"` Success bool `description:"是否成功"` } // CompanyProfilePageListData 客户档案分页列表数据 type CompanyProfilePageListData struct { Total int `description:"数据总量" json:"total"` Offset int `description:"偏移量" json:"offset"` PageSize int `description:"每页数据量" json:"pageSize"` List []struct { BizCode string `description:"选项编码" json:"bizCode"` Name string `description:"公司名称" json:"name"` NameDisplay string `description:"公司名称-JSON(包含所有语言的名称文案)" json:"nameDisplay"` Enabled bool `description:"是否启用" json:"enabled"` ParentCode string `description:"父级选项编码/顶级档案编码" json:"parentCode"` } `description:"列表数据" json:"list"` HasNextPage bool `description:"是否还有下一页" json:"hasNextPage"` } // CurlCompanyProfilePageList 请求客户档案分页列表接口 func CurlCompanyProfilePageList(req CompanyProfilePageListReq) (companyData CompanyProfilePageListData, err error) { defer func() { if err != nil { utils.FileLog.Error("CurlCompanyProfilePageList ErrMsg: %s", err.Error()) go alarm_msg.SendAlarmMsg(fmt.Sprintf("每刻报销-获取客户档案分页列表失败, ErrMsg: %s", err.Error()), 3) } }() if req.PageSize == 0 { req.PageSize = 50 } // 请求 url := fmt.Sprintf("%s%s", utils.MayCurBaseUrl, ApiCompanyProfilePageListUrl) reqByte, e := json.Marshal(req) if e != nil { err = fmt.Errorf("request json marshal err: %s", e.Error()) return } body, e := buildHttpRequest(url, "POST", string(reqByte), true) if e != nil { err = fmt.Errorf("curl api err: %s", e.Error()) return } // 响应 resp := new(CompanyProfilePageListResp) if e = json.Unmarshal(body, &resp); e != nil { err = fmt.Errorf("resp json unmarshal err: %s", e.Error()) return } if resp == nil { err = fmt.Errorf("resp nil") return } if resp.Code != ApiSuccessCode { err = fmt.Errorf("resp err message: %s", resp.Message) return } companyData = resp.Data return } // DownloadCompanyProfile 同步每刻报销客户档案数据到CRM // 每刻没提供debug环境, 所以在CRM这里做个备份... func DownloadCompanyProfile() (err error) { defer func() { if err != nil { fmt.Println(err.Error()) } }() // 清理掉本地的档案, 每刻的档案和本地的档案可能会因为每刻系统的删除操作而产生重复数据, 即使这边用BizCode去重也可能会 profileOB := new(models.MaycurCompanyProfile) if e := profileOB.Clear(); e != nil { err = fmt.Errorf("清理本地客户档案失败, Err: %s", e.Error()) return } // 分页请求 var req CompanyProfilePageListReq req.ReferenceDataBizCode = CompanyProfileCode if e := sliceDownloadCompanyProfile(req); e != nil { err = fmt.Errorf("分页获取客户档案列表失败, Err: %s", e.Error()) return } return } // sliceDownloadCompanyProfile 分片同步客户档案 func sliceDownloadCompanyProfile(req CompanyProfilePageListReq) (err error) { // 请求 req.ReferenceDataBizCode = CompanyProfileCode if req.PageSize <= 0 { req.PageSize = 100 } fmt.Println("offset: ", req.Offset, "pageSize: ", req.PageSize) data, e := CurlCompanyProfilePageList(req) if e != nil { err = fmt.Errorf("请求客户档案接口失败, Err: %s", e.Error()) return } list := data.List listLen := len(list) if listLen == 0 { return } // 去重-不知道为啥下一页有时候会出现跟这一页一样的某一条数据 profileOB := new(models.MaycurCompanyProfile) profileCond := `` profilePars := make([]interface{}, 0) profileList, e := profileOB.GetItemsByCondition(profileCond, profilePars, []string{}, "") if e != nil { err = fmt.Errorf("获取客户档案列表失败, Err: %s", e.Error()) return } profileMap := make(map[string]*models.MaycurCompanyProfile) for _, p := range profileList { profileMap[p.BizCode] = p } nowTime := time.Now().Local() newProfiles := make([]*models.MaycurCompanyProfile, 0) updateProfiles := make([]*models.MaycurCompanyProfile, 0) for _, v := range list { if profileMap[v.BizCode] != nil { continue } // 新增 enabled := 0 if v.Enabled { enabled = 1 } newProfiles = append(newProfiles, &models.MaycurCompanyProfile{ BizCode: v.BizCode, Name: v.Name, NameDisplay: v.NameDisplay, Enabled: enabled, ParentCode: v.ParentCode, CreateTime: nowTime, ModifyTime: nowTime, }) } // 新增及更新 updateCols := []string{"Name", "NameDisplay", "Enabled", "ParentCode", "ModifyTime"} if e = profileOB.MultiCreateAndUpdate(newProfiles, updateProfiles, updateCols); e != nil { err = fmt.Errorf("新增/更新客户档案失败, Err: %s", e.Error()) return } // 递归请求下一页 fmt.Println(data.HasNextPage) if !data.HasNextPage { return } time.Sleep(300 * time.Millisecond) req.Offset += listLen err = sliceDownloadCompanyProfile(req) return } // ImportCompanyProfileReq 批量导入客户档案请求体 type ImportCompanyProfileReq struct { BizCode string `description:"" json:"bizCode"` ReferenceDataDetails []ImportCompanyProfileReferenceData `description:"" json:"referenceDataDetails"` } // ImportCompanyProfileReferenceData 批量导入客户档案数据 type ImportCompanyProfileReferenceData struct { Name string `description:"选项名称, 选填, 三填一" json:"name"` NameEn string `description:"选项名称(英文), 选填, 三填一" json:"nameEn"` NameZh string `description:"选项名称(中文), 选填, 三填一" json:"nameZh"` BizCode string `description:"选项编码(全局唯一), 必填" json:"bizCode"` Enabled bool `description:"是否启用, 必填" json:"enabled"` ReferenceDataAuthz []*ReferenceDataAuth `description:"选项可见性, 选填, 空数组为清除, 不填维持原数据" json:"referenceDataAuthz"` } // ReferenceDataAuth 客户档案可见性权限 type ReferenceDataAuth struct { BizCode string `description:"业务编码" json:"bizCode"` Type string `description:"STAFF-人员; DEPARTMENT-部门; LEGAL_ENTITY-公司抬头" json:"type"` IncludeChild bool `description:"是否包含子部门(type为DEPARTMENT时有效)" json:"includeChild"` } // ImportCompanyProfileResp 批量导入客户档案响应体 type ImportCompanyProfileResp struct { Success bool `description:"是否成功返回" json:"success"` ErrorMsg string `description:"错误信息" json:"errorMsg"` Data interface{} `description:"不知道是要返回个啥数据..." json:"data"` } // CurlImportCompanyProfile 请求批量导入客户档案接口 func CurlImportCompanyProfile(req ImportCompanyProfileReq) (success bool, err error) { defer func() { if err != nil { fmt.Println(err.Error()) } }() // 请求 url := fmt.Sprintf("%s%s", utils.MayCurBaseUrl, ApiImportCompanyProfileUrl) reqByte, e := json.Marshal(req) if e != nil { err = fmt.Errorf("request json marshal err: %s", e.Error()) return } utils.FileLog.Info("CurlImportCompanyProfile req: %s", string(reqByte)) body, e := buildHttpRequest(url, "POST", string(reqByte), true) if e != nil { err = fmt.Errorf("curl api err: %s", e.Error()) return } // 响应 resp := new(ImportCompanyProfileResp) if e = json.Unmarshal(body, &resp); e != nil { err = fmt.Errorf("resp json unmarshal err: %s, body: %s", e.Error(), string(body)) return } if !resp.Success { err = fmt.Errorf("resp err body: %s", string(body)) return } success = true return } // buildCompanyProfileCode 自定义客户档案编号 func buildCompanyProfileCode() string { str := fmt.Sprintf("HZCP%v", time.Now().UnixNano()/1e6) r := utils.GetRandStringNoSpecialChar(6) return strings.ToUpper(strings.Replace(str, str[11:17], r, 1)) } // GetImportCompanyData 获取导入的客户档案数据 func GetImportCompanyData() (importsData []ImportCompanyProfileReferenceData, err error) { // 查询现有的客户档案列表 profileOB := new(models.MaycurCompanyProfile) profiles, e := profileOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "") if e != nil { err = fmt.Errorf("获取本地客户档案失败, Err: %s", e.Error()) return } profileNameVal := make(map[string]*models.MaycurCompanyProfile) for _, v := range profiles { profileNameVal[v.Name] = v } // 查询所有试用、正式、永续、冻结客户(包含了FICC和私募, 可能与公募部分客户存在重合) localComps, e := models.GetMaycurSyncCompanyListWithSeller() if e != nil { err = fmt.Errorf("获取需要同步的客户列表失败, Err: %s", e.Error()) return } localCompNameVal := make(map[string]*models.MaycurSyncCompanyListWithSeller) for _, v := range localComps { localCompNameVal[v.CompanyName] = v } // 本地员工及员工上级组map employeeGroupMap, _ := getEmployeeGroupMapFromList() // Test //for k, em := range employeeGroupMap { // str := `` // for _, v := range em { // str += fmt.Sprintf(" %d-%s", v.AdminId, v.RealName) // } // utils.FileLog.Info("seller-%d: %s", k, str) //} // 可见所有客户的用户组 groupAuth := new(ReferenceDataAuth) groupAuth.BizCode = AuthGroupCode groupAuth.Type = "USER_GROUP" importCompsVal := make(map[string]ImportCompanyProfileReferenceData) // FICC、私募客户 for _, c := range localComps { var v ImportCompanyProfileReferenceData bizArr := make(map[string]bool) // STAFF去重 // 用户组均可见 authArr := make([]*ReferenceDataAuth, 0) authArr = append(authArr, groupAuth) // 销售及上级可见性 sellerIdStr := c.SellerIds if sellerIdStr != "" { sellerIdArr := strings.Split(sellerIdStr, ",") for _, s := range sellerIdArr { sid, _ := strconv.Atoi(s) // 匹配销售对应的销售组, 若有相应数据则加入可见性 admins := employeeGroupMap[sid] if admins != nil && len(admins) > 0 { for _, a := range admins { if a.EmployeeId == "" { continue } if bizArr[a.EmployeeId] { continue } authArr = append(authArr, &ReferenceDataAuth{ BizCode: a.EmployeeId, Type: "STAFF", }) bizArr[a.EmployeeId] = true } } } } // 正式客户共享-销售可见性, 仅被共享的销售, 不加上级 if c.IsShare == 1 && c.ShareSellerId > 0 { shareAdmins := employeeGroupMap[c.ShareSellerId] if shareAdmins != nil && len(shareAdmins) > 0 { for _, sa := range shareAdmins { if sa.AdminId == c.ShareSellerId && !bizArr[sa.EmployeeId] { authArr = append(authArr, &ReferenceDataAuth{ BizCode: sa.EmployeeId, Type: "STAFF", }) break } } } } v.ReferenceDataAuthz = authArr // 更新/新增 v.Enabled = true local := profileNameVal[c.CompanyName] if local != nil { v.Name = local.Name v.BizCode = local.BizCode } if local == nil { v.Name = c.CompanyName v.BizCode = buildCompanyProfileCode() } importCompsVal[v.Name] = v } // 公募客户 publicCompOB := new(models.MaycurPublicOfferingCompany) publicComps, e := publicCompOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "") if e != nil { err = fmt.Errorf("获取公募客户列表失败, Err: %s", e.Error()) return } // 公募销售对应的组长(多个) publicLeaders, e := GetPublicOfferingSaleLeader() if e != nil { err = fmt.Errorf("获取公募销售组长信息失败, Err: %s", e.Error()) return } // 员工工号-此处包括禁用的 employeeOB := new(system.Admin) employeeCond := ` AND employee_id <> ""` employeePars := make([]interface{}, 0) employees, e := employeeOB.GetItemsByCondition(employeeCond, employeePars, []string{}, "") if e != nil { err = fmt.Errorf("获取员工列表失败, Err: %s", e.Error()) return } empNameVal := make(map[string]*system.Admin) for _, emp := range employees { empNameVal[emp.RealName] = emp } // 公募客户数据 for _, p := range publicComps { // 销售可见性 ps := strings.Split(p.SaleNames, ",") if len(ps) == 0 { continue } bizArr := make(map[string]bool) for _, s := range ps { // 销售 emp := empNameVal[s] if emp != nil && emp.EmployeeId != "" && !bizArr[emp.EmployeeId] { bizArr[emp.EmployeeId] = true } // 组长 ls := publicLeaders[s] if len(ls) > 0 { for _, l := range ls { le := empNameVal[l] if le != nil && le.EmployeeId != "" && !bizArr[le.EmployeeId] { bizArr[le.EmployeeId] = true } } } } // 是否与ficc、私募的客户有交集 shared := importCompsVal[p.Name] if shared.Name != "" && shared.BizCode != "" { // 公共客户-销售可见性追加进去 localBiz := make(map[string]bool) for _, s := range shared.ReferenceDataAuthz { localBiz[s.BizCode] = true } for b := range bizArr { if localBiz[b] { continue } shared.ReferenceDataAuthz = append(shared.ReferenceDataAuthz, &ReferenceDataAuth{ BizCode: b, Type: "STAFF", }) } importCompsVal[p.Name] = shared } else { var v ImportCompanyProfileReferenceData v.Enabled = true authArr := make([]*ReferenceDataAuth, 0) authArr = append(authArr, groupAuth) for b := range bizArr { authArr = append(authArr, &ReferenceDataAuth{ BizCode: b, Type: "STAFF", }) } v.ReferenceDataAuthz = authArr // 是否是已有的客户档案 origin := profileNameVal[p.Name] if origin != nil { v.Name = origin.Name v.BizCode = origin.BizCode } if origin == nil { v.Name = p.Name v.BizCode = buildCompanyProfileCode() } importCompsVal[v.Name] = v } } // 遍历每刻档案, 生成导入数据 for _, f := range profiles { val, ok := importCompsVal[f.Name] if ok { val.BizCode = f.BizCode importsData = append(importsData, val) continue } // 不应导入的客户, 仅用户组可见, 禁启用随原数据 var o ImportCompanyProfileReferenceData o.Name = f.Name o.BizCode = f.BizCode o.Enabled = false if f.Enabled == 1 { o.Enabled = true } o.ReferenceDataAuthz = make([]*ReferenceDataAuth, 0) o.ReferenceDataAuthz = append(o.ReferenceDataAuthz, groupAuth) importsData = append(importsData, o) } // Test //testIdArr := []string{"0041"} // 亓策 //testData := make([]ImportCompanyProfileReferenceData, 0) //for _, d := range importData { // has := false // for _, r := range d.ReferenceDataAuthz { // if utils.InArrayByStr(testIdArr, r.BizCode) { // has = true // break // } // } // if !has { // continue // } // testData = append(testData, d) //} return } // ImportCompanyProfile 导入客户档案 func ImportCompanyProfile(importData []ImportCompanyProfileReferenceData) (err error) { // 控制每秒最多请求5次接口 chanData := make(chan ImportCompanyProfileReferenceData, 50) sendDone := make(chan bool, 1) importDone := make(chan bool, 1) importErr := make(chan error, 1) sendClose := make(chan bool, 1) // 分批 go func() { defer func() { utils.FileLog.Info("分批协程关闭") }() for _, d := range importData { select { case chanData <- d: case <-sendClose: return } } time.Sleep(5 * time.Second) // 分批完成后暂缓5s是为了防止倒数第二次请求接口响应时间过长, 导致接收丢失 sendDone <- true }() // 接收并导入 go func() { receive := 0 times := 0 defer func() { utils.FileLog.Info("导入协程关闭 -> 最终接收: %d -> 最终请求次数: %d", receive, times) }() batch := make([]ImportCompanyProfileReferenceData, 0) end := false for { select { case v := <-chanData: receive += 1 //fmt.Printf("第%d次接收\n", receive) batch = append(batch, v) // 每50条数据请求一次, 每刻接口请求单次最大50条 if len(batch) == 50 { times += 1 if e := SliceImportCompanyProfile(batch); e != nil { sendClose <- true importErr <- e return } // 间隔0.3秒, 每刻有接口频率限制 time.Sleep(300 * time.Millisecond) batch = make([]ImportCompanyProfileReferenceData, 0) } case <-sendDone: // 传输完毕, 执行最后一次 if len(batch) > 0 { times += 1 if e := SliceImportCompanyProfile(batch); e != nil { sendClose <- true importErr <- e return } } end = true } if end { importDone <- true return } } }() select { case <-importDone: utils.FileLog.Info("导入成功") case e := <-importErr: err = fmt.Errorf("批量导入客户档案失败, Err: %s", e.Error()) } return } // SliceImportCompanyProfile 分批导入客户档案 func SliceImportCompanyProfile(dataDetails []ImportCompanyProfileReferenceData) (err error) { if len(dataDetails) == 0 { return } var req ImportCompanyProfileReq req.BizCode = CompanyProfileCode req.ReferenceDataDetails = dataDetails // Test //b, e := json.Marshal(req) //if e != nil { // err = fmt.Errorf("request json marshal err: %s", e.Error()) // return //} //utils.FileLog.Info("CurlImportCompanyProfile req: %s", string(b)) //return success, e := CurlImportCompanyProfile(req) if e != nil { return e } if !success { return fmt.Errorf("导入失败") } return } // CompanyProfileDetailResp 客户档案-详情响应体 type CompanyProfileDetailResp struct { Code string `description:"状态码"` Message string `description:"返回信息"` Data struct { BizCode string `description:"选项编码" json:"bizCode"` Name string `description:"公司名称" json:"name"` NameDisplay string `description:"公司名称-JSON(包含所有语言的名称文案)" json:"nameDisplay"` Enabled bool `description:"是否启用" json:"enabled"` ParentCode string `description:"父级选项编码/顶级档案编码" json:"parentCode"` ReferenceAuthzs []CompanyProfileReferenceAuthzs } `description:"详情数据"` ErrorCode string `description:"错误码" json:"errorCode"` Success bool `description:"是否成功"` } // CompanyProfileReferenceAuthzs 客户档案-可见性 type CompanyProfileReferenceAuthzs struct { AuthzType string `description:"授权范围类型-详见接口文档" json:"authzType"` AuthzCode string `description:"授权码-员工工号/部门编码等" json:"authzCode"` AuthzName string `description:"授权名称" json:"authzName"` IncludeChildDept bool `description:"是否包含子节点" json:"includeChildDept"` } // UpdateLocalCompanyProfileAuth 更新本地客户档案可见性 func UpdateLocalCompanyProfileAuth() (err error) { defer func() { if err != nil { fmt.Println(err.Error()) } }() profileOB := new(models.MaycurCompanyProfile) profileCond := ` AND enabled = ?` profilePars := make([]interface{}, 0) profilePars = append(profilePars, 1) profiles, e := profileOB.GetItemsByCondition(profileCond, profilePars, []string{}, "") if e != nil { err = fmt.Errorf("获取客户档案列表失败, Err: %s", e.Error()) return } total := len(profiles) var count, updated int for _, v := range profiles { count += 1 if count > 5 { fmt.Printf("总数: %d, 已更新: %d\n", total, updated) time.Sleep(500 * time.Millisecond) count = 1 } par := fmt.Sprintf(`/api/openapi/reference/data/detail/%s/%s`, CompanyProfileCode, v.BizCode) url := fmt.Sprintf("%s%s", utils.MayCurBaseUrl, par) body, e := buildHttpRequest(url, "GET", ``, true) if e != nil { err = fmt.Errorf("获取客户档案详情失败, Err: %s", e.Error()) return } var resp CompanyProfileDetailResp if e = json.Unmarshal(body, &resp); e != nil { err = fmt.Errorf("客户档案详情Unmarshal, Err: %s", e.Error()) return } if resp.Code != ApiSuccessCode || !resp.Success { utils.FileLog.Info("忽略客户%s -> body: %s", v.BizCode, string(body)) continue } b, e := json.Marshal(resp.Data.ReferenceAuthzs) if e != nil { err = fmt.Errorf("客户档案可见性Marshal, Err: %s", e.Error()) return } v.Authzs = string(b) v.ModifyTime = time.Now().Local() if e = v.Update([]string{"Authzs", "ModifyTime"}); e != nil { err = fmt.Errorf("更新本地客户档案可见性失败, Err: %s", e.Error()) return } updated += 1 } fmt.Printf("总数: %d, 已更新: %d\n", total, updated) return } // RecoverCompanyProfile 恢复客户档案可见性(没有测试服, 还原用的...) func RecoverCompanyProfile() (err error) { defer func() { if err != nil { fmt.Println(err.Error()) } }() profileOB := new(models.MaycurCompanyProfile) profiles, e := profileOB.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "") if e != nil { err = fmt.Errorf("获取客户档案列表失败, Err: %s", e.Error()) return } if len(profiles) == 0 { return } var importsData []ImportCompanyProfileReferenceData for _, p := range profiles { var v ImportCompanyProfileReferenceData v.Name = p.Name v.BizCode = p.BizCode if p.Enabled == 1 { v.Enabled = true } v.ReferenceDataAuthz = nil if p.Authzs != "" && p.Authzs != "null" { var authzs []CompanyProfileReferenceAuthzs if e := json.Unmarshal([]byte(p.Authzs), &authzs); e != nil { err = fmt.Errorf(" Authzs Unmarshal Err: %s", e.Error()) return } v.ReferenceDataAuthz = make([]*ReferenceDataAuth, 0) for _, a := range authzs { v.ReferenceDataAuthz = append(v.ReferenceDataAuthz, &ReferenceDataAuth{ BizCode: a.AuthzCode, Type: a.AuthzType, IncludeChild: a.IncludeChildDept, }) } } importsData = append(importsData, v) } // 导入 utils.FileLog.Info("导入数据长度: %d", len(importsData)) if e := ImportCompanyProfile(importsData); e != nil { err = fmt.Errorf("导入失败, Err: %s", e.Error()) return } return }