You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

245 lines
9.4 KiB

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
}