package api import ( "strings" "time" "yunyan/comm" "yunyan/lego/sys/log" "yunyan/pb" ) // 渠道商管理:渠道商挂在品牌商下,一个品牌商可绑定多个渠道商,id 为 8 位可读短码。 // 生产授权码时必须选定渠道商,归属随批次写进每条授权码,用户绑定设备后统计与结算按它归属。 // // 与 console 侧 api_channel.go 是同表同库的两套实现(沿用本仓库品牌商/产品既有的双实现格局), // 状态取值、审批规则、校验口径两端必须保持一致,改一处要同步另一处。 // 渠道商状态取值(与 DBChannel.status 及前端一致)。 const ( ChannelStatusPending int32 = 0 // 待审核 ChannelStatusEnabled int32 = 1 // 启用 ChannelStatusDisabled int32 = 2 // 停用 ChannelStatusRejected int32 = 3 // 驳回 ) // channelShareRateMax 渠道商分成比例上限(万分比,10000 = 100%);语义是从品牌商那份里再切。 const channelShareRateMax int32 = 10000 // canAuditChannel 是否有渠道商审批权(超管/管理员)。 func canAuditChannel(session comm.IUserSession) bool { identity := pb.Identity(session.GetMateToInt32("identity")) return identity == pb.Identity_Admin || identity == pb.Identity_Manager } // GetChannels 渠道商列表。status<0 为不限(0 是「待审核」,不能拿零值当全部)。 // 运营/代理按账号的品牌商白名单收敛。 func (this *apiComp) GetChannels(session comm.IUserSession, req *pb.ApiGetChannelsReq) (resp *pb.ApiGetChannelsResp, errdata *pb.ErrorData) { models, err := this.module.model.channels(req.Brandid, req.Status, req.Keyword) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} this.module.Error("GetChannels Fail!", log.Field{Key: "err", Value: err.Error()}) return } scope := this.getSessionScope(session) if !scope.Unlimited { allProducts, _ := this.module.model.products() allowed := visibleFactoryIDs(scope, allProducts) allowSet := make(map[uint32]struct{}, len(allowed)) for _, id := range allowed { allowSet[id] = struct{}{} } filtered := make([]*pb.DBChannel, 0, len(models)) for _, m := range models { if _, ok := allowSet[m.Brandid]; ok { filtered = append(filtered, m) } } models = filtered } resp = &pb.ApiGetChannelsResp{Channels: models} return } // GetChannel 取单个渠道商。 func (this *apiComp) GetChannel(session comm.IUserSession, req *pb.ApiGetChannelReq) (resp *pb.ApiGetChannelResp, errdata *pb.ErrorData) { id := strings.TrimSpace(req.Id) if id == "" { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "id 必填"} return } model, err := this.module.model.channel(id) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "渠道商不存在: " + id} return } resp = &pb.ApiGetChannelResp{Channel: model} return } // AddChannel 新增渠道商。id 一律服务端生成短码(前端传入的忽略); // 无审批权的账号(品牌商)提交后为待审核,须超管/管理员通过才能用于生产。 func (this *apiComp) AddChannel(session comm.IUserSession, req *pb.ApiAddChannelReq) (resp *pb.ApiAddChannelResp, errdata *pb.ErrorData) { if req.Channel == nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "channel 必填"} return } m := req.Channel m.Name = strings.TrimSpace(m.Name) if m.Name == "" { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商名称必填"} return } if m.Brandid == 0 { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "所属品牌商必选"} return } if _, err := this.module.model.factory(m.Brandid); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "品牌商不存在"} return } if m.Sharerate < 0 || m.Sharerate > channelShareRateMax { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "分成比例需在 0~10000(万分比) 之间"} return } now := time.Now().Unix() m.Id = this.module.model.genchannelid() m.Createtime = now m.Updatetime = now if canAuditChannel(session) { m.Status = ChannelStatusEnabled m.Auditaccount = session.GetUserId() m.Audittime = now } else { m.Status = ChannelStatusPending m.Applyaccount = session.GetUserId() m.Auditaccount = "" m.Audittime = 0 } m.Auditremark = "" if err := this.module.model.addchannel(m); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} this.module.Error("AddChannel Fail!", log.Field{Key: "err", Value: err.Error()}) return } resp = &pb.ApiAddChannelResp{Channel: m} return } // UpdateChannel 更新渠道商资料。id/brandid 不可改(改归属与已生产的授权码矛盾); // 状态只能走审批接口;分成比例只有有审批权的账号能改。 func (this *apiComp) UpdateChannel(session comm.IUserSession, req *pb.ApiUpdateChannelReq) (resp *pb.ApiUpdateChannelResp, errdata *pb.ErrorData) { if req.Channel == nil || strings.TrimSpace(req.Channel.Id) == "" { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "channel id 必填"} return } old, err := this.module.model.channel(strings.TrimSpace(req.Channel.Id)) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "渠道商不存在: " + req.Channel.Id} return } if req.Channel.Sharerate < 0 || req.Channel.Sharerate > channelShareRateMax { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "分成比例需在 0~10000(万分比) 之间"} return } if name := strings.TrimSpace(req.Channel.Name); name != "" { old.Name = name } old.Contact = req.Channel.Contact old.Contactmails = req.Channel.Contactmails old.Remark = req.Channel.Remark if canAuditChannel(session) { old.Sharerate = req.Channel.Sharerate } old.Updatetime = time.Now().Unix() if err = this.module.model.savechannel(old); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } resp = &pb.ApiUpdateChannelResp{Channel: old} return } // DelChannel 删除渠道商。已产生生产批次的禁止删除——历史授权码/统计/结算都按 id 归属; // 要停止使用请走审批接口置停用。 func (this *apiComp) DelChannel(session comm.IUserSession, req *pb.ApiDelChannelReq) (resp *pb.ApiDelChannelResp, errdata *pb.ErrorData) { id := strings.TrimSpace(req.Id) if id == "" { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "id 必填"} return } if _, err := this.module.model.channel(id); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "渠道商不存在: " + id} return } n, err := this.module.model.countbatchesbychannel(id) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } if n > 0 { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "该渠道商已有生产批次,不能删除,请改为停用"} return } if err = this.module.model.delchannel(id); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } resp = &pb.ApiDelChannelResp{Id: id} return } // AuditChannel 审批/启停渠道商:通过(1) / 停用(2) / 驳回(3)。仅超管、管理员。 func (this *apiComp) AuditChannel(session comm.IUserSession, req *pb.ApiAuditChannelReq) (resp *pb.ApiAuditChannelResp, errdata *pb.ErrorData) { if !canAuditChannel(session) { errdata = &pb.ErrorData{Code: pb.ErrorCode_InsufficientPermissions, Message: "仅超管/管理员可审批渠道商"} return } id := strings.TrimSpace(req.Id) if id == "" { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "id 必填"} return } switch req.Status { case ChannelStatusEnabled, ChannelStatusDisabled, ChannelStatusRejected: default: errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "status 仅支持 1(通过启用) / 2(停用) / 3(驳回)"} return } old, err := this.module.model.channel(id) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "渠道商不存在: " + id} return } old.Status = req.Status old.Auditaccount = session.GetUserId() old.Auditremark = req.Auditremark old.Audittime = time.Now().Unix() old.Updatetime = old.Audittime if err = this.module.model.savechannel(old); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } resp = &pb.ApiAuditChannelResp{Channel: old} return } // resolveProductionChannel 生产授权码时解析并校验渠道商:必选、须归属该品牌商、须为启用状态。 // 返回渠道商与错误数据(errdata 非空即校验失败,调用方直接 return)。 func (this *apiComp) resolveProductionChannel(channelid string, brandid uint32) (*pb.DBChannel, *pb.ErrorData) { id := strings.TrimSpace(channelid) if id == "" { return nil, &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商必选"} } ch, err := this.module.model.channel(id) if err != nil { return nil, &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商不存在: " + id} } if ch.Brandid != brandid { return nil, &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商不属于该品牌商: " + id} } if ch.Status != ChannelStatusEnabled { return nil, &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商未启用(待审核/停用/驳回),不能用于生产: " + id} } return ch, nil }