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.
237 lines
9.1 KiB
237 lines
9.1 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.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
|
|
}
|
|
// 渠道商是全局实体:品牌商 2026-09-12 下线后不再有上级归属。
|
|
m.Brandid = 0
|
|
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 不可改(改它与已生产的授权码矛盾);
|
|
// 状态只能走审批接口;分成比例只有有审批权的账号能改。
|
|
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 生产授权码时解析并校验渠道商:必选、须为启用状态。
|
|
// 品牌归属的校验随品牌商下线(2026-09-12)一并去掉。
|
|
// 返回渠道商与错误数据(errdata 非空即校验失败,调用方直接 return)。
|
|
func (this *apiComp) resolveProductionChannel(channelid string) (*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.Status != ChannelStatusEnabled {
|
|
return nil, &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "渠道商未启用(待审核/停用/驳回),不能用于生产: " + id}
|
|
}
|
|
return ch, nil
|
|
}
|
|
|