package sys import ( "context" "fmt" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/dict" "hotgo/internal/library/hgorm/handler" "hotgo/internal/library/hgorm/hook" "hotgo/internal/model" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) type sSysMemberLevel struct{} func NewSysMemberLevel() *sSysMemberLevel { return &sSysMemberLevel{} } func init() { service.RegisterSysMemberLevel(NewSysMemberLevel()) dict.RegisterFunc("memberLevelOption", "会员等级选项", service.SysMemberLevel().Option, true) } func (s *sSysMemberLevel) model(ctx context.Context) *gdb.Model { return handler.Model(dao.AppMemberLevel.Ctx(ctx), &handler.Option{FilterTenant: true}) } func (s *sSysMemberLevel) versionModel(ctx context.Context) *gdb.Model { return handler.Model(dao.AppMemberLevelVersion.Ctx(ctx), &handler.Option{FilterTenant: true}) } func (s *sSysMemberLevel) List(ctx context.Context, in *sysin.MemberLevelListInp) (list []*sysin.MemberLevelListModel, total int, err error) { lc, vc := dao.AppMemberLevel.Columns(), dao.AppMemberLevelVersion.Columns() // 已发布等级展示当前生效版本;尚未发布时展示最新草稿,避免列表误显示为零值。 versionJoin := fmt.Sprintf( "%s=COALESCE(NULLIF(%s,0),(SELECT lv.%s FROM %s lv WHERE lv.%s=%s ORDER BY lv.%s DESC,lv.%s DESC LIMIT 1))", col("v", vc.Id), col("l", lc.CurrentVersionId), vc.Id, dao.AppMemberLevelVersion.Table(), vc.LevelId, col("l", lc.Id), vc.VersionNo, vc.Id, ) mod := s.model(ctx).As("l").LeftJoin(dao.AppMemberLevelVersion.Table()+" v", versionJoin).Fields(col("l", lc.Id), col("l", lc.LevelCode), col("l", lc.Name), col("l", lc.IconUrl), col("l", lc.CurrentVersionId), col("l", lc.MemberCount), col("l", lc.Status), col("l", lc.Sort), col("l", lc.Remark), col("l", lc.CreatedAt), col("v", vc.VersionNo), col("v", vc.UpgradeAmount), col("v", vc.GiftAmount), col("v", vc.DailyOrderLimit), col("v", vc.CommissionRate), col("v", vc.DailyWithdrawLimit), col("v", vc.PublishStatus), col("v", vc.EffectiveAt)) if in.Name != "" { mod = mod.WhereLike(col("l", lc.Name), "%"+in.Name+"%") } if in.Status > 0 { mod = mod.Where(col("l", lc.Status), in.Status) } if in.PublishStatus > 0 { mod = mod.Where(col("v", vc.PublishStatus), in.PublishStatus) } err = mod.Page(in.Page, in.PerPage).OrderAsc(col("l", lc.Sort)).OrderDesc(col("l", lc.Id)).ScanAndCount(&list, &total, false) if err != nil { err = gerror.Wrap(err, "获取会员等级列表失败") } return } func (s *sSysMemberLevel) View(ctx context.Context, in *sysin.MemberLevelViewInp) (res *sysin.MemberLevelViewModel, err error) { res = new(sysin.MemberLevelViewModel) if err = s.model(ctx).WherePri(in.Id).Scan(&res.Level); err != nil { return nil, gerror.Wrap(err, "获取会员等级失败") } if res.Level == nil { return nil, gerror.New("会员等级不存在") } vc := dao.AppMemberLevelVersion.Columns() if err = s.versionModel(ctx).Where(vc.LevelId, in.Id).WhereIn(vc.PublishStatus, []int{consts.BizPublishDraft, consts.BizPublishPending}).OrderDesc(vc.Id).Limit(1).Scan(&res.Draft); err != nil { return nil, err } if res.Level.CurrentVersionId > 0 { if err = s.versionModel(ctx).WherePri(res.Level.CurrentVersionId).Scan(&res.Published); err != nil { return nil, err } } return } func (s *sSysMemberLevel) Save(ctx context.Context, in *sysin.MemberLevelSaveInp) (id, versionId uint64, err error) { err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { lc, vc := dao.AppMemberLevel.Columns(), dao.AppMemberLevelVersion.Columns() count, e := s.model(ctx).Where(lc.LevelCode, in.LevelCode).WhereNot(lc.Id, in.Id).Count() if e != nil { return e } if count > 0 { return gerror.New("等级编码已存在") } count, e = s.model(ctx).Where(lc.Name, in.Name).WhereNot(lc.Id, in.Id).Count() if e != nil { return e } if count > 0 { return gerror.New("等级名称已存在") } userId := contexts.GetUserId(ctx) if in.Id == 0 { result, e := s.model(ctx).Fields(lc.LevelCode, lc.Name, lc.IconAttachmentId, lc.IconUrl, lc.Status, lc.Sort, lc.Remark, lc.CreatedBy, lc.UpdatedBy).Hook(hook.SaveTenant).Data(g.Map{lc.LevelCode: in.LevelCode, lc.Name: in.Name, lc.IconAttachmentId: in.IconAttachmentId, lc.IconUrl: in.IconUrl, lc.Status: in.Status, lc.Sort: in.Sort, lc.Remark: in.Remark, lc.CreatedBy: userId, lc.UpdatedBy: userId}).Insert() if e != nil { return gerror.Wrap(e, "创建会员等级失败") } last, _ := result.LastInsertId() in.Id = uint64(last) } else { _, e = s.model(ctx).WherePri(in.Id).Data(g.Map{lc.LevelCode: in.LevelCode, lc.Name: in.Name, lc.IconAttachmentId: in.IconAttachmentId, lc.IconUrl: in.IconUrl, lc.Status: in.Status, lc.Sort: in.Sort, lc.Remark: in.Remark, lc.UpdatedBy: userId}).Update() if e != nil { return gerror.Wrap(e, "更新会员等级失败") } } id = in.Id data := g.Map{vc.UpgradeAmount: in.UpgradeAmount, vc.GiftAmount: in.GiftAmount, vc.DailyOrderLimit: in.DailyOrderLimit, vc.CommissionRate: in.CommissionRate, vc.DailyWithdrawLimit: in.DailyWithdrawLimit, vc.RuleJson: in.RuleJson, vc.PublishStatus: consts.BizPublishPublished, vc.EffectiveAt: nil, vc.PublishedAt: gtime.Now(), vc.Remark: in.Remark, vc.UpdatedBy: userId} var level *entity.AppMemberLevel if e = s.model(ctx).WherePri(in.Id).Scan(&level); e != nil { return e } if level == nil { return gerror.New("会员等级不存在") } versionId = level.CurrentVersionId if versionId == 0 { var latest *entity.AppMemberLevelVersion if e = s.versionModel(ctx).Where(vc.LevelId, in.Id).OrderDesc(vc.Id).Limit(1).Scan(&latest); e != nil { return e } if latest != nil { versionId = latest.Id } } if versionId > 0 { data[vc.Version] = gdb.Raw(vc.Version + "+1") if _, e = s.versionModel(ctx).WherePri(versionId).Where(vc.LevelId, in.Id).Data(data).Update(); e != nil { return gerror.Wrap(e, "更新等级规则失败") } } else { data[vc.LevelId] = in.Id data[vc.VersionNo] = 1 data[vc.CreatedBy] = userId result, insertErr := s.versionModel(ctx).Fields(vc.LevelId, vc.VersionNo, vc.UpgradeAmount, vc.GiftAmount, vc.DailyOrderLimit, vc.CommissionRate, vc.DailyWithdrawLimit, vc.RuleJson, vc.PublishStatus, vc.EffectiveAt, vc.PublishedAt, vc.Remark, vc.CreatedBy, vc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert() if insertErr != nil { return gerror.Wrap(insertErr, "创建等级规则失败") } last, _ := result.LastInsertId() versionId = uint64(last) } if _, e = s.model(ctx).WherePri(in.Id).Data(g.Map{lc.CurrentVersionId: versionId, lc.UpdatedBy: userId}).Update(); e != nil { return gerror.Wrap(e, "更新等级当前规则失败") } _ = dict.ClearFuncCache("memberLevelOption") return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member_level", Action: "level.save", TargetType: "app_member_level", TargetId: in.Id, After: g.Map{"upgradeAmount": in.UpgradeAmount, "giftAmount": in.GiftAmount, "commissionRate": in.CommissionRate}, Reason: in.Remark}) }) return } func (s *sSysMemberLevel) Submit(ctx context.Context, in *sysin.MemberLevelSubmitInp) (res *sysin.MemberLevelApprovalModel, err error) { var v *entity.AppMemberLevelVersion vc := dao.AppMemberLevelVersion.Columns() if err = s.versionModel(ctx).WherePri(in.VersionId).Scan(&v); err != nil { return } if v == nil { return nil, gerror.New("等级规则版本不存在") } if v.PublishStatus != consts.BizPublishDraft { return nil, gerror.New("仅草稿可以提交审批") } if v.Version != in.Version { return nil, gerror.New("等级规则已更新,请刷新后重试") } var level *entity.AppMemberLevel _ = s.model(ctx).WherePri(v.LevelId).Scan(&level) approval, err := service.SysApproval().Create(ctx, &sysin.ApprovalCreateInp{ApprovalType: "member_level_publish", TargetType: "app_member_level_version", TargetId: v.Id, Action: "publish", Title: "发布会员等级 " + level.Name + fmt.Sprintf(" V%d", v.VersionNo), PayloadJson: gjson.New(v), RiskLevel: consts.ApprovalRiskHigh, RequiredApprovals: 1, Remark: in.Reason}) if err != nil { return nil, err } if err = service.SysApproval().Submit(ctx, &sysin.ApprovalSubmitInp{Id: approval.Id, Version: 0}); err != nil { return nil, err } result, err := s.versionModel(ctx).WherePri(v.Id).Where(vc.Version, v.Version).Data(g.Map{vc.PublishStatus: consts.BizPublishPending, vc.ApprovalRequestId: approval.Id, vc.UpdatedBy: contexts.GetUserId(ctx), vc.Version: gdb.Raw(vc.Version + "+1")}).Update() if err != nil { return nil, gerror.Wrap(err, "更新等级审批状态失败") } rows, _ := result.RowsAffected() if rows != 1 { return nil, gerror.New("等级规则已更新,请刷新后重试") } return &sysin.MemberLevelApprovalModel{ApprovalId: approval.Id, ApprovalNo: approval.ApprovalNo}, nil } func (s *sSysMemberLevel) Publish(ctx context.Context, in *sysin.MemberLevelPublishInp) error { return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { vc, lc := dao.AppMemberLevelVersion.Columns(), dao.AppMemberLevel.Columns() var v *entity.AppMemberLevelVersion if err := s.versionModel(ctx).WherePri(in.VersionId).LockUpdate().Scan(&v); err != nil { return err } if v == nil { return gerror.New("等级规则版本不存在") } if v.PublishStatus != consts.BizPublishPending { return gerror.New("仅待审版本可以发布") } if v.Version != in.Version { return gerror.New("等级规则已更新,请刷新后重试") } var approval *entity.ApprovalRequest if err := service.SysApproval().Model(ctx).WherePri(v.ApprovalRequestId).Scan(&approval); err != nil { return err } if approval == nil || approval.ApprovalStatus != consts.ApprovalStatusApproved { return gerror.New("审批尚未通过") } if v.EffectiveAt != nil && v.EffectiveAt.After(gtime.Now()) { return gerror.New("计划生效时间尚未到达") } _, _ = s.versionModel(ctx).Where(vc.LevelId, v.LevelId).Where(vc.PublishStatus, consts.BizPublishPublished).Data(g.Map{vc.PublishStatus: consts.BizPublishArchived, vc.UpdatedBy: contexts.GetUserId(ctx)}).Update() result, err := s.versionModel(ctx).WherePri(v.Id).Where(vc.Version, v.Version).Data(g.Map{vc.PublishStatus: consts.BizPublishPublished, vc.PublishedAt: gtime.Now(), vc.UpdatedBy: contexts.GetUserId(ctx), vc.Version: gdb.Raw(vc.Version + "+1")}).Update() if err != nil { return gerror.Wrap(err, "发布等级规则失败") } rows, _ := result.RowsAffected() if rows != 1 { return gerror.New("等级规则已更新,请刷新后重试") } if _, err = s.model(ctx).WherePri(v.LevelId).Data(g.Map{lc.CurrentVersionId: v.Id, lc.UpdatedBy: contexts.GetUserId(ctx)}).Update(); err != nil { return err } _, err = service.SysApproval().Model(ctx).WherePri(approval.Id).Data(g.Map{dao.ApprovalRequest.Columns().ExecutedAt: gtime.Now(), dao.ApprovalRequest.Columns().UpdatedBy: contexts.GetUserId(ctx)}).Update() if err != nil { return err } _ = dict.ClearFuncCache("memberLevelOption") return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member_level", Action: "level.publish", TargetType: "app_member_level", TargetId: v.LevelId, After: v, ApprovalNo: approval.ApprovalNo, Reason: v.Remark}) }) } func (s *sSysMemberLevel) Rollback(ctx context.Context, in *sysin.MemberLevelRollbackInp) (versionId uint64, err error) { var source *entity.AppMemberLevelVersion if err = s.versionModel(ctx).WherePri(in.VersionId).Scan(&source); err != nil { return } if source == nil { return 0, gerror.New("历史版本不存在") } var level *entity.AppMemberLevel if err = s.model(ctx).WherePri(source.LevelId).Scan(&level); err != nil { return } if level == nil { return 0, gerror.New("会员等级不存在") } _, versionId, err = s.Save(ctx, &sysin.MemberLevelSaveInp{Id: level.Id, LevelCode: level.LevelCode, Name: level.Name, IconAttachmentId: level.IconAttachmentId, IconUrl: level.IconUrl, UpgradeAmount: source.UpgradeAmount, GiftAmount: source.GiftAmount, DailyOrderLimit: source.DailyOrderLimit, CommissionRate: source.CommissionRate, DailyWithdrawLimit: source.DailyWithdrawLimit, RuleJson: source.RuleJson, EffectiveAt: nil, Sort: level.Sort, Status: level.Status, Remark: "回滚自 V" + fmt.Sprint(source.VersionNo) + ":" + in.Reason}) return } func (s *sSysMemberLevel) History(ctx context.Context, in *sysin.MemberLevelHistoryInp) (list []*entity.AppMemberLevelVersion, total int, err error) { c := dao.AppMemberLevelVersion.Columns() err = s.versionModel(ctx).Where(c.LevelId, in.LevelId).Page(in.Page, in.PerPage).OrderDesc(c.VersionNo).ScanAndCount(&list, &total, false) if err != nil { err = gerror.Wrap(err, "获取等级版本历史失败") } return } func (s *sSysMemberLevel) Status(ctx context.Context, in *sysin.MemberLevelStatusInp) error { c := dao.AppMemberLevel.Columns() _, err := s.model(ctx).WherePri(in.Id).Data(g.Map{c.Status: in.Status, c.UpdatedBy: contexts.GetUserId(ctx)}).Update() if err != nil { return gerror.Wrap(err, "更新等级状态失败") } _ = dict.ClearFuncCache("memberLevelOption") return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member_level", Action: "level.status", TargetType: "app_member_level", TargetId: in.Id, After: g.Map{"status": in.Status}}) } func (s *sSysMemberLevel) Delete(ctx context.Context, in *sysin.MemberLevelDeleteInp) error { count, err := handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(dao.AppMember.Columns().LevelId, in.Id).Count() if err != nil { return err } if count > 0 { return gerror.New("该等级仍有会员使用,不能删除,请改为停用") } count, err = handler.Model(dao.TaskHallLevel.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(dao.TaskHallLevel.Columns().LevelId, in.Id).Count() if err != nil { return err } if count > 0 { return gerror.New("该等级仍被任务大厅引用,不能删除") } c := dao.AppMemberLevel.Columns() _, err = s.model(ctx).WherePri(in.Id).Data(g.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx), c.UpdatedBy: contexts.GetUserId(ctx)}).Update() if err != nil { return gerror.Wrap(err, "删除会员等级失败") } _ = dict.ClearFuncCache("memberLevelOption") return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member_level", Action: "level.delete", TargetType: "app_member_level", TargetId: in.Id, Reason: in.Reason}) } func (s *sSysMemberLevel) Option(ctx context.Context) (opts []*model.Option, err error) { var rows []*entity.AppMemberLevel c := dao.AppMemberLevel.Columns() if err = s.model(ctx).Fields(c.Id, c.Name).Where(c.Status, consts.StatusEnabled).WhereGT(c.CurrentVersionId, 0).OrderAsc(c.Sort).OrderAsc(c.Id).Scan(&rows); err != nil { return nil, gerror.Wrap(err, "获取会员等级选项失败") } opts = make([]*model.Option, len(rows)) for i, row := range rows { opts[i] = dict.GenHashOption(row.Id, gconv.String(row.Name)) } return }