ai_scheduler/internal/biz/advice_client.go

285 lines
8.0 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 biz
import (
"ai_scheduler/internal/data/mongo_model"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
"errors"
"fmt"
"time"
"context"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
type AdviceClientBiz struct {
AdvicerClientMongo *mongo_model.AdvicerClientMongo
mongo *pkg.Mongo
}
func NewAdviceClientBiz(
advicerClientMongo *mongo_model.AdvicerClientMongo,
mongo *pkg.Mongo,
) *AdviceClientBiz {
return &AdviceClientBiz{
AdvicerClientMongo: advicerClientMongo,
mongo: mongo,
}
}
func (a *AdviceClientBiz) Add(ctx context.Context, param *entitys.AdvicerClientAddReq) (id interface{}, err error) {
// AI 参与开关:默认开启
aiEnabled := true
if param.AiEnabled != nil {
aiEnabled = *param.AiEnabled
}
res, err := a.mongo.Co(a.AdvicerClientMongo).InsertOne(ctx, &mongo_model.AdvicerClientMongo{
ProjectId: param.ProjectId,
AdvicerId: param.AdvicerId,
Wxid: param.Wxid,
AppId: param.AppId,
AiEnabled: aiEnabled,
Profile: param.Profile,
LastUpdateTime: time.Now(),
})
if err != nil {
return nil, err
}
return res.InsertedID, err
}
func (a *AdviceClientBiz) Update(ctx context.Context, param *entitys.AdvicerrClientUpdateReq) (err error) {
filter := bson.M{}
if len(param.Id) == 0 {
return errors.New("ID不能为空")
}
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
// 读取旧记录,保留回调/等级评估自动维护的字段,避免人工编辑资料时被覆盖
var old mongo_model.AdvicerClientMongo
if err = a.mongo.Co(a.AdvicerClientMongo).FindOne(ctx, filter).Decode(&old); err != nil {
return fmt.Errorf("客户不存在: %w", err)
}
// AI 参与开关:未传保持原值
aiEnabled := old.AiEnabled
if param.AiEnabled != nil {
aiEnabled = *param.AiEnabled
}
update := bson.M{
"$set": &mongo_model.AdvicerClientMongo{
ProjectId: param.ProjectId,
AdvicerId: param.AdvicerId,
Wxid: old.Wxid,
AppId: old.AppId,
ClientLevel: old.ClientLevel,
Mission: old.Mission,
LevelReason: old.LevelReason,
LevelUpdateAt: old.LevelUpdateAt,
LastContactAt: old.LastContactAt,
LastClientMsgAt: old.LastClientMsgAt,
LastReplyAt: old.LastReplyAt,
MsgCount: old.MsgCount,
AiEnabled: aiEnabled,
LastIterateAt: old.LastIterateAt,
Profile: param.Profile,
LastUpdateTime: time.Now(),
},
}
res := a.mongo.Co(a.AdvicerClientMongo).FindOneAndUpdate(ctx, filter, update)
return res.Err()
}
func (a *AdviceClientBiz) List(ctx context.Context, param *entitys.AdvicerClientListReq) (list []mongo_model.AdvicerClientItem, err error) {
filter := bson.M{}
// 1. advicer_id 条件
if param.AdvicerId != 0 {
filter["advicerId"] = param.AdvicerId
}
if param.ProjectId != 0 {
filter["projectId"] = param.ProjectId
}
// 2. _id 条件
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return nil, fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
cursor, err := a.mongo.Co(a.AdvicerClientMongo).Find(ctx, filter)
if err != nil {
return nil, err
}
// 遍历结果
for cursor.Next(ctx) {
var advicerVersion mongo_model.AdvicerClientItem
if err := cursor.Decode(&advicerVersion); err != nil {
return nil, err
}
list = append(list, advicerVersion)
}
if err := cursor.Err(); err != nil {
return nil, err
}
return list, err
}
func (a *AdviceClientBiz) Del(ctx context.Context, param *entitys.AdvicerClientDelReq) (err error) {
filter := bson.M{}
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
_, err = a.mongo.Co(a.AdvicerClientMongo).DeleteOne(ctx, filter)
return err
}
func (a *AdviceClientBiz) Info(ctx context.Context, param *entitys.AdvicerClientInfoReq) (info mongo_model.AdvicerClientMongo, err error) {
filter := bson.M{}
if len(param.Id) != 0 {
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return info, fmt.Errorf("ID转换失败: %w", err)
}
filter["_id"] = objectID
}
res := a.mongo.Co(a.AdvicerClientMongo).FindOne(ctx, filter)
if res.Err() != nil {
return info, res.Err()
}
if err = res.Decode(&info); err != nil {
return info, err
}
return
}
// Bind 客户绑定微信(wxid 用于回调消息关联到该客户)
func (a *AdviceClientBiz) Bind(ctx context.Context, param *entitys.AdvicerClientBindReq) (err error) {
if len(param.Id) == 0 {
return errors.New("ID不能为空")
}
if len(param.Wxid) == 0 {
return errors.New("wxid不能为空")
}
objectID, err := primitive.ObjectIDFromHex(param.Id)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
set := bson.M{"wxid": param.Wxid, "lastUpdateTime": time.Now()}
if len(param.AppId) != 0 {
set["appId"] = param.AppId
}
_, err = a.mongo.Co(a.AdvicerClientMongo).UpdateOne(ctx, bson.M{"_id": objectID}, bson.M{"$set": set})
return err
}
// FindByWxid 按客户微信 wxid 查询客户(回调链按消息来源定位客户)
func (a *AdviceClientBiz) FindByWxid(ctx context.Context, wxid string) (info mongo_model.AdvicerClientItem, found bool, err error) {
if len(wxid) == 0 {
return info, false, errors.New("wxid不能为空")
}
res := a.mongo.Co(a.AdvicerClientMongo).FindOne(ctx, bson.M{"wxid": wxid})
if res.Err() != nil {
if errors.Is(res.Err(), mongo.ErrNoDocuments) {
return info, false, nil
}
return info, false, res.Err()
}
if err = res.Decode(&info); err != nil {
return info, false, err
}
return info, true, nil
}
// EnsureByWxid 按客户 wxid 获取画像记录;不存在则用传入的项目/销售/appId 自动建档(仅元数据 + 空画像栏目)。
// 供「画像更新」按钮与定时提醒/成交线索按 wxid 单选时复用;返回 Item 与是否新建。
func (a *AdviceClientBiz) EnsureByWxid(ctx context.Context, wxid string, projectId, advicerId int32, appId string) (item *mongo_model.AdvicerClientItem, created bool, err error) {
if len(wxid) == 0 {
return nil, false, errors.New("wxid不能为空")
}
if found, ok, e := a.FindByWxid(ctx, wxid); e != nil {
return nil, false, e
} else if ok {
return &found, false, nil
}
aiEnabled := true
if _, e := a.Add(ctx, &entitys.AdvicerClientAddReq{
ProjectId: projectId,
AdvicerId: advicerId,
Wxid: wxid,
AppId: appId,
AiEnabled: &aiEnabled,
Profile: map[string]interface{}{},
}); e != nil {
return nil, false, e
}
found, ok, e := a.FindByWxid(ctx, wxid)
if e != nil {
return nil, false, e
}
if !ok {
return nil, false, errors.New("客户建档失败")
}
return &found, true, nil
}
// UpdateMission 回写客户当前对话任务(mission)到画像,供客户列表展示;仅更新 mission 字段。
func (a *AdviceClientBiz) UpdateMission(ctx context.Context, clientIdHex, mission string) error {
if len(clientIdHex) == 0 {
return nil
}
objectID, err := primitive.ObjectIDFromHex(clientIdHex)
if err != nil {
return fmt.Errorf("ID转换失败: %w", err)
}
_, err = a.mongo.Co(a.AdvicerClientMongo).UpdateOne(ctx, bson.M{"_id": objectID},
bson.M{"$set": bson.M{"mission": mission}})
return err
}
// TouchInteraction 更新客户互动时间与消息数(回调链针对已绑定客户调用)
// direction: customer 客户发来(更新 lastClientMsgAt);self/ai 我方回复(更新 lastReplyAt)
func (a *AdviceClientBiz) TouchInteraction(ctx context.Context, wxid string, direction string) (err error) {
if len(wxid) == 0 {
return nil
}
now := time.Now()
set := bson.M{
"lastContactAt": now,
}
if direction == mongo_model.WxMsgDirectionCustomer {
set["lastClientMsgAt"] = now
} else {
set["lastReplyAt"] = now
}
update := bson.M{
"$set": set,
"$inc": bson.M{"msgCount": 1},
}
_, err = a.mongo.Co(a.AdvicerClientMongo).UpdateOne(ctx, bson.M{"wxid": wxid}, update)
return err
}