package service import ( "app.yhyue.com/moapp/jybase/common" "app.yhyue.com/moapp/jybase/date" "app.yhyue.com/moapp/jybase/encrypt" "encoding/json" "fmt" "github.com/gogf/gf/v2/container/gset" "bp.jydev.jianyu360.cn/BaseService/jyMicroservices/jyBXCore/rpc/model/mysql" "bp.jydev.jianyu360.cn/BaseService/jyMicroservices/jyBXCore/rpc/type/bxcore" "bp.jydev.jianyu360.cn/BaseService/jyMicroservices/jyBXCore/rpc/util" "log" "strings" "time" ) const ( PositionTypeEnt = 1 // 职位类型企业 PositionTypePersonal = 0 // 职位类型个人 ButtonValueParticipate = 0 // 参标按钮 显示值 0-参标 ButtonValueParticipated = 1 // 按钮显示值 列表页面 1-已参标 RoleEntManager = 1 // 企业管理员角色 RoleDepartManager = 2 // 部门管理员角色 BidTypeDirect = 1 // 直接投标 BidTypeChanel = 2 // 渠道投标 RecordTypeBidStatus = 1 // 存储类型 1:投标状态更新 ) type ParticipateBid struct { EntId int64 EntUserId int64 PositionType int64 PositionId int64 EntRoleId int64 // 角色 } func NewParticipateBid(entId, entUserId, positionType, positionId int64) ParticipateBid { return ParticipateBid{ EntId: entId, EntUserId: entUserId, PositionType: positionType, PositionId: positionId, } } // PersonalExistProject 个人版要展示的参标按钮 查询出已经参标的项目信息 用于后边格式化数据 func (p *ParticipateBid) PersonalExistProject(projectId []string) map[string]struct{} { rs := mysql.ParticipateProjectPersonal(p.PositionId, projectId) existProjectSet := map[string]struct{}{} if rs != nil && len(*rs) > 0 { // 如果查到了 说明已经参标 这部分应该显示已参标 // 处理成map for i := 0; i < len(*rs); i++ { existId := common.ObjToString((*rs)[i]["project_id"]) existProjectSet[existId] = struct{}{} } } return existProjectSet } // ListPersonalFormat 列表页个人版 参标按钮 格式化数据 func (p *ParticipateBid) ListPersonalFormat(existProjectSet map[string]struct{}, infoM map[string]string) []*bxcore.ShowInfo { // 处理成 要返回的返回数据 var formatList []*bxcore.ShowInfo for k, v := range infoM { buttonValue := ButtonValueParticipate // 不存在应该显示参标 if _, ok := existProjectSet[v]; ok { // 存在说明应该显示已参标 buttonValue = ButtonValueParticipated } formatList = append(formatList, &bxcore.ShowInfo{ Id: encrypt.EncodeArticleId2ByCheck(k), Value: int64(buttonValue), }) } return formatList } // EntExistProject 企业版 查出来企业下已经参标的这个项目的以及参标人信息 用于后边格式化数据判断有没有自己 func (p *ParticipateBid) EntExistProject(projectId []string) map[string]string { rs := mysql.ParticipateProjectEnt(p.EntId, projectId) existProjectMap := map[string]string{} if rs != nil && len(*rs) > 0 { // 如果查到了 说明这个项目公司里面已经参标 处理一下信息用于后边判断是否是自己参标 // 处理成map for i := 0; i < len(*rs); i++ { existId := common.ObjToString((*rs)[i]["project_id"]) personIds := common.ObjToString((*rs)[i]["personIds"]) existProjectMap[existId] = personIds } } return existProjectMap } // ListEntFormat 企业版 列表页数据格式化 func (p *ParticipateBid) ListEntFormat(existProjectMap, infoM map[string]string, isAllow bool) []*bxcore.ShowInfo { // 处理成 要返回的返回数据 var formatList []*bxcore.ShowInfo for k, v := range infoM { buttonValue := ButtonValueParticipate // 不存在时-显示参标 if userIds, ok := existProjectMap[v]; ok { // 存在时 说明项目在企业下已经参标 需要进一步判断 // 判断已经存在的参标人中是否包含自己 包含时 显示成已参标 if ContainId(userIds, common.InterfaceToStr(p.EntUserId)) { buttonValue = ButtonValueParticipated } else if isAllow { // 不包含自己时需要 进一步判断公司设置是否允许多人参标 // 允许时显示成 参标 buttonValue = ButtonValueParticipate } else { // 不允许时 跳过该条信息 continue } } formatList = append(formatList, &bxcore.ShowInfo{ Id: encrypt.EncodeArticleId2ByCheck(k), Value: int64(buttonValue), }) } return formatList } // DetailEntFormat 企业版 详情页数据格式化 返回数据 参标按钮 终止参标按钮 转给同事按钮 参标人姓名 func (p *ParticipateBid) DetailEntFormat(existProjectMap map[string]string, isValid, isAllow bool, bidEndTime int64) (formatData *bxcore.ParticipateDetailInfo) { // 处理成 要返回的返回数据 formatData = &bxcore.ParticipateDetailInfo{} if len(existProjectMap) == 0 && isValid { // 无参标人 展示参标按钮 其余按钮不显示 formatData.ShowParticipate = true return } persons := "" for _, v := range existProjectMap { // 这是为了取参标人id信息 列表页是多条数据 详情页这里 map里面只会有一条数据 persons = v break } // 参标人信息 处理成姓名 nameRs := mysql.GetNameByUserIds(persons) if nameRs != nil && len(*nameRs) > 0 { formatData.UserName = common.ObjToString((*nameRs)[0]["name"]) } if !isValid { return } formatData.CurrentTime = time.Now().Unix() formatData.BidEndTime = bidEndTime // 如果是管理员 显示 终止参标按钮、转给同事按钮 if p.EntRoleId == RoleEntManager || p.EntRoleId == RoleDepartManager { formatData.ShowStopParticipate = true formatData.ShowTransfer = true } // 如果包含自己 显示终止参标按钮 if ContainId(persons, common.InterfaceToStr(p.EntUserId)) { formatData.ShowStopParticipate = true formatData.ShowUpdate = true } else if isAllow { // 如果允许多人 显示参标按钮 formatData.ShowParticipate = true } return } // DetailPersonalFormat 详情页个人版 按钮格式化数据 func (p *ParticipateBid) DetailPersonalFormat(existProjectSet map[string]struct{}, isValid bool, bidEndTime int64) (formatData *bxcore.ParticipateDetailInfo) { // 处理成 要返回的返回数据 formatData = &bxcore.ParticipateDetailInfo{} if !isValid { return } // 存在在说明已经参标 显示终止参标 if len(existProjectSet) > 0 { formatData.CurrentTime = time.Now().Unix() formatData.BidEndTime = bidEndTime formatData.ShowStopParticipate = true formatData.ShowUpdate = true } else { // 不存在则说明 未参标 参标按钮展示 formatData.ShowParticipate = true } return } // HandlerProjectId 返回信息的映射集合,项目id列表 func HandlerProjectId(projectInfos []map[string]interface{}, infoIds map[string]struct{}) (map[string]string, []string) { result := map[string]string{} projectIdList := []string{} // 记录infoid 和项目id 对应关系 用于最后处理返回的数据 for i := 0; i < len(projectInfos); i++ { _id := common.ObjToString(projectInfos[i]["_id"]) projectIdList = append(projectIdList, _id) list, b := projectInfos[i]["list"].([]interface{}) if !b { continue } for j := 0; j < len(list); j++ { infoidMap := common.ObjToMap(list[j]) if infoidMap == nil { continue } infoid := common.ObjToString((*infoidMap)["infoid"]) if _, ok := infoIds[infoid]; ok && infoid != "" { result[infoid] = _id } } } return result, projectIdList } // DecodeId 解密标讯id 返回一个信息id的列表 和 集合 func DecodeId(ids string) (result []string, resultSet map[string]struct{}) { idList := strings.Split(ids, ",") resultSet = map[string]struct{}{} for i := 0; i < len(idList); i++ { decodeArray := encrypt.DecodeArticleId2ByCheck(idList[i]) if len(decodeArray) == 1 && decodeArray[0] != "" { result = append(result, decodeArray[0]) resultSet[decodeArray[0]] = struct{}{} } } return } // ContainId 用于判断给定的逗号分割的字符串中是否包含 目标的字符串 func ContainId(ids string, objId string) bool { list := strings.Split(ids, ",") for i := 0; i < len(list); i++ { if list[i] == objId { return true } } return false } // CheckBidPower 查看记录 验证权限(参标人或者是该企业下的管理员) func (p *ParticipateBid) CheckBidPower(projectId string, valid bool) (b bool) { switch p.PositionType { case PositionTypePersonal: // 查个人id b = mysql.CheckParticipatePersonal(projectId, p.PositionId, valid) case PositionTypeEnt: // 查企业 if p.EntRoleId == RoleEntManager || p.EntRoleId == RoleDepartManager { b = mysql.CheckParticipateManager(projectId, p.EntId, valid) } else { // 查参标人 b = mysql.CheckParticipateEntUser(projectId, p.EntUserId, valid) } } return } // CheckUpdateBidPower 验证投标状态更新权限 func (p *ParticipateBid) CheckUpdateBidPower(projectId string, valid bool) (b bool) { switch p.PositionType { case PositionTypePersonal: // 查个人id b = mysql.CheckParticipatePersonal(projectId, p.PositionId, valid) case PositionTypeEnt: // 查参标人 b = mysql.CheckParticipateEntUser(projectId, p.EntUserId, valid) } return } // UpdateBidStatus 更新投标状态 func (p *ParticipateBid) UpdateBidStatus(in *bxcore.UpdateBidStatusReq, projectId string) error { if p.PositionType == PositionTypeEnt { pLock := util.GetParticipateLock(fmt.Sprintf("%d_%s", p.EntId, projectId)) pLock.Lock() defer pLock.Unlock() } // 如果查出来旧的 那么就需要做新旧对比 oldMap, _ := p.GetLastBidStatus(projectId) // 查询出最新的招标状态信息 // 新的 if in.BidStage == nil { in.BidStage = []string{} } newMap := map[string]interface{}{ "bidType": in.BidType, "bidStage": in.BidStage, "isWin": in.IsWin, "channelName": in.ChannelName, "channelPerson": in.ChannelPerson, "channelPhone": in.ChannelPhone, "winner": in.Winner, } // 新旧对比 处理成要保存的字段 recordContent, err := processRecordStr(oldMap, newMap) if err != nil { return err } // 保存 recordData := map[string]interface{}{ "ent_id": p.EntId, "ent_user_id": p.EntUserId, "position_id": p.PositionId, "project_id": projectId, "record_content": recordContent, "record_type": RecordTypeBidStatus, "create_date": date.NowFormat(date.Date_Full_Layout), } if flag := mysql.InsertBidContent(recordData); !flag { return fmt.Errorf("更新失败") } mysql.UpdateParticipateUserTable(projectId, p.PositionId) return nil } // GetLastBidStatus 获取投标状态信息 func (p *ParticipateBid) GetLastBidStatus(projectId string) (map[string]interface{}, error) { var ( rs = map[string]interface{}{} // 查询项目投标信息 区分企业和个人 result *[]map[string]interface{} ) switch p.PositionType { case PositionTypeEnt: result = mysql.GetBidContentEnt(projectId, p.EntId) case PositionTypePersonal: result = mysql.GetBidContentPersonal(projectId, p.PositionId) } if rs != nil && len(*result) > 0 { content := common.ObjToMap((*result)[0]["record_content"]) if content != nil { bidStatus := common.ObjToMap((*content)["after"]) if bidStatus != nil { rs = *bidStatus } } } return rs, nil } func (p ParticipateBid) ParticipateContentFormat(data map[string]interface{}) (rs bxcore.ParticipateContentData) { if len(data) == 0 { return } rs.BidType = common.Int64All(data["bidType"]) rs.ChannelPhone = common.ObjToString(data["channelPhone"]) rs.ChannelPerson = common.ObjToString(data["channelPerson"]) rs.ChannelName = common.ObjToString(data["channelName"]) rs.IsWin = common.Int64All(data["isWin"]) rs.Winner = common.ObjToString(data["winner"]) tmp := data["bidStage"].([]interface{}) rs.BidStage = common.ObjArrToStringArr(tmp) return rs } // GetBidRecords 获取操作记录 func (p *ParticipateBid) GetBidRecords(projectId string, page, pageSize int64) *bxcore.ParticipateRecordsData { data := bxcore.ParticipateRecordsData{} var rs *[]map[string]interface{} var total int64 switch p.PositionType { case PositionTypeEnt: // 1. 查询出操作记录 rs, total = mysql.GetBidRecordsEnt(projectId, p.EntId, page, pageSize) case PositionTypePersonal: // 个人版不展示姓名 rs, total = mysql.GetBidRecordsPersonal(projectId, p.PositionId, page, pageSize) } if rs == nil || len(*rs) == 0 { return &data } data.Total = total data.List = p.BidRecordsFormat(*rs) return &data } // BidRecordsFormat 获取操作记录格式化 func (p ParticipateBid) BidRecordsFormat(data []map[string]interface{}) []*bxcore.ParticipateRecords { records := []*bxcore.ParticipateRecords{} switch p.PositionType { case PositionTypeEnt: // 用户id // 拿到所有的用户id userIdArr := []string{} userIdMap := map[int64]string{} for i := 0; i < len(data); i++ { userId := common.Int64All(data[i]["ent_user_id"]) if _, ok := userIdMap[userId]; !ok { userIdMap[userId] = "" userIdArr = append(userIdArr, fmt.Sprint(userId)) } } // 根据id查询出姓名{id:name} userIdMap = getUserIdName(userIdArr) // 遍历数据 换成姓名 for i := 0; i < len(data); i++ { id := common.Int64All(data[i]["ent_user_id"]) person := "" if name, ok := userIdMap[id]; ok { person = name } tmp := bxcore.ParticipateRecords{ RecordsData: common.ObjToString(data[i]["record_content"]), UpdateDate: common.ObjToString(data[i]["create_date"]), UpdatePerson: person, RecordType: common.Int64All(data[i]["record_type"]), } records = append(records, &tmp) } case PositionTypePersonal: // 遍历数据 for i := 0; i < len(data); i++ { tmp := bxcore.ParticipateRecords{ RecordsData: common.ObjToString(data[i]["record_content"]), UpdateDate: common.ObjToString(data[i]["create_date"]), RecordType: common.Int64All(data[i]["record_type"]), } records = append(records, &tmp) } } return records } // 获取id和姓名的对应关系 func getUserIdName(userIdArr []string) map[int64]string { rs := map[int64]string{} if len(userIdArr) > 0 { userIdStr := strings.Join(userIdArr, ",") userRs := mysql.GetUserMap(userIdStr) if userRs == nil || len(*userRs) == 0 { return rs } for i := 0; i < len(*userRs); i++ { user := (*userRs)[i] id := common.Int64All(user["id"]) name := common.ObjToString(user["name"]) rs[id] = name } } return rs } // 处理操作动作 var ( ParticipateBidContentKey = map[string]string{ "bidType": "投标类型", "bidStage": "投标项目阶段", "isWin": "是否中标", "channelName": "渠道名称", "channelPerson": "联系人", "channelPhone": "联系电话", "winner": "中标单位", } BidTypeMap = map[int]string{ BidTypeDirect: "直接投标", BidTypeChanel: "渠道投标", } WinMap = map[int]string{ 1: "是", 2: "否", 0: "未选择", } ) // 处理操作记录 func processRecordStr(oldMap, newMap map[string]interface{}) (recordContent string, err error) { var ( actonStr = "%s%s%s" changeField = []string{} result = []string{} afterMap = map[string]interface{}{} ) for k, fieldName := range ParticipateBidContentKey { var ( value interface{} status, changeStr string ) switch k { case "bidType": oldv := common.IntAll(oldMap[k]) newv := common.IntAll(newMap[k]) newBidType := BidTypeMap[newv] if newBidType == "" { newBidType = "未选择" } value = newBidType if oldv == newv { // 没有改变 afterMap[k] = map[string]interface{}{ "status": status, "value": value, } continue } value = newBidType if oldv == 0 && newv != 0 { // 说明是新增 status = "新增" changeStr = fmt.Sprintf(actonStr, fieldName, ": (新增)", newBidType) } else { // 调整 status = "调整" changeStr = fmt.Sprintf(actonStr, fieldName, "(调整):", newBidType) } case "isWin": oldV := common.IntAll(oldMap[k]) newV := common.IntAll(newMap[k]) value = WinMap[newV] if oldV == newV { // 没有改变 afterMap[k] = map[string]interface{}{ "status": status, "value": value, } continue } status = "调整" //fieldName := fieldName if common.IntAll(newMap["bidType"]) == BidTypeChanel { fieldName = fmt.Sprintf("%s%s", "渠道", fieldName) } changeStr = fmt.Sprintf(actonStr, fieldName, "(调整) 为", WinMap[newV]) case "bidStage": bidAction := "%s%s" bidChangeArr := []string{} oldSet := gset.NewFrom(oldMap[k]) newSet := gset.NewFrom(newMap[k]) value = newMap[k] // 判断相等 if oldSet.Equal(newSet) { afterMap[k] = map[string]interface{}{ "status": status, "value": value, } continue } status = "调整" // 差集计算 // 取消勾选的 cancleSet := oldSet.Diff(newSet) cancleSet.Iterator(func(v interface{}) bool { bidChangeArr = append(bidChangeArr, fmt.Sprintf(bidAction, "(取消勾选)", v)) return true }) // 新增的 addSet := newSet.Diff(oldSet) addSet.Iterator(func(v interface{}) bool { bidChangeArr = append(bidChangeArr, fmt.Sprintf(bidAction, "(新增)", v)) return true }) tmpStr := strings.Join(bidChangeArr, " ") changeStr = fmt.Sprintf(actonStr, fieldName, " :", tmpStr) default: oldV := common.ObjToString(oldMap[k]) newV := common.ObjToString(newMap[k]) value = newV if oldV == newV { // 没有变化 afterMap[k] = map[string]interface{}{ "status": status, "value": value, } continue } changeStr = fmt.Sprintf(actonStr, fieldName, "(调整)为", fmt.Sprintf("\"%s\"", newV)) status = "调整" } result = append(result, changeStr) changeField = append(changeField, k) afterMap[k] = map[string]interface{}{ "status": status, "value": value, } } b, _ := json.Marshal(afterMap) recordMap := map[string]interface{}{ "content": strings.Join(result, ";"), // 变更内容 文字描述 "before": oldMap, // 变更前 "after": newMap, // 变更后 "changeField": changeField, // 涉及变更的字段 "afterMap": string(b), // 涉及变更内容 } tmp, err := json.Marshal(recordMap) if err != nil { log.Println("序列化操作记录失败:", err) return "", err } if len(result) == 0 { log.Println("没有更新的内容:", recordContent) err = fmt.Errorf("没有变更的内容,不用更新") } return string(tmp), err }