813 lines
43 KiB
Go
813 lines
43 KiB
Go
package sys
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"hotgo/internal/consts"
|
|
"hotgo/internal/dao"
|
|
"hotgo/internal/library/contexts"
|
|
"hotgo/internal/library/hgorm/handler"
|
|
"hotgo/internal/library/hgorm/hook"
|
|
"hotgo/internal/model/entity"
|
|
"hotgo/internal/model/input/sysin"
|
|
"hotgo/internal/service"
|
|
"hotgo/utility/encrypt"
|
|
|
|
"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/grand"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type sSysAppMember struct{}
|
|
|
|
func NewSysAppMember() *sSysAppMember { return &sSysAppMember{} }
|
|
func init() { service.RegisterSysAppMember(NewSysAppMember()) }
|
|
|
|
func (s *sSysAppMember) memberModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) profileModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.AppMemberProfile.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) paymentModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.AppMemberPaymentAccount.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) addressModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.AppMemberAddress.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) walletModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.WalletAccount.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) adjustmentModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.WalletAdjustmentRequest.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
func (s *sSysAppMember) noticeModel(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.AppMemberNotice.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
|
|
func col(alias, column string) string { return alias + "." + column }
|
|
|
|
func (s *sSysAppMember) List(ctx context.Context, in *sysin.AppMemberListInp) (list []*sysin.AppMemberListModel, totalCount int, err error) {
|
|
mc, pc, wc := dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns()
|
|
bc, uc := dao.MemberUrlBinding.Columns(), dao.UrlPool.Columns()
|
|
mod := s.memberModel(ctx).As("m").
|
|
LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("m", mc.Id)).
|
|
LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").
|
|
LeftJoin(dao.AppMember.Table()+" parent", col("parent", mc.Id)+"="+col("m", mc.ParentId)).
|
|
LeftJoin(dao.MemberUrlBinding.Table()+" ub", col("ub", bc.MemberId)+"="+col("m", mc.Id)).
|
|
LeftJoin(dao.UrlPool.Table()+" up", col("up", uc.Id)+"="+col("ub", bc.UrlPoolId)).
|
|
Fields(
|
|
col("m", mc.Id), col("m", mc.MemberNo), col("m", mc.Username), col("m", mc.Mobile),
|
|
col("p", pc.RealNameMask), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)),
|
|
fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), col("m", mc.ParentId),
|
|
fmt.Sprintf("COALESCE(%s,'') parent_username", col("parent", mc.Username)), col("m", mc.InviteCode),
|
|
col("m", mc.MemberStatus), col("m", mc.RiskStatus), col("m", mc.AutoDispatch), col("m", mc.IsTestAccount),
|
|
fmt.Sprintf("CASE WHEN %s=1 THEN %d ELSE %d END member_category", col("m", mc.IsTestAccount), consts.AppMemberCategoryFake, consts.AppMemberCategoryReal),
|
|
col("m", mc.LastLoginAt), col("m", mc.LastLoginIpMask), col("m", mc.LastLoginRegion), col("m", mc.CreatedAt),
|
|
fmt.Sprintf("COALESCE(%s,0) url_pool_id", col("ub", bc.UrlPoolId)),
|
|
fmt.Sprintf("COALESCE(%s,'') register_domain", col("ub", bc.DomainSnapshot)), col("ub", bc.BoundAt)+" domain_bound_at",
|
|
fmt.Sprintf("CASE WHEN %s IS NULL THEN %d WHEN %s=%d THEN %d ELSE %d END domain_status", col("ub", bc.Id), consts.AppMemberDomainUnbound, col("up", uc.Status), consts.UrlPoolStatusEnabled, consts.AppMemberDomainBoundEnabled, consts.AppMemberDomainBoundInvalid),
|
|
)
|
|
if in.Username != "" {
|
|
mod = mod.WhereLike(col("m", mc.Username), "%"+in.Username+"%")
|
|
}
|
|
if in.Mobile != "" {
|
|
mod = mod.Where(col("m", mc.Mobile), in.Mobile)
|
|
}
|
|
if in.MemberStatus > 0 {
|
|
mod = mod.Where(col("m", mc.MemberStatus), in.MemberStatus)
|
|
}
|
|
if in.AutoDispatch > 0 {
|
|
mod = mod.Where(col("m", mc.AutoDispatch), in.AutoDispatch)
|
|
}
|
|
if in.MemberCategory == consts.AppMemberCategoryReal {
|
|
mod = mod.Where(col("m", mc.IsTestAccount), 2)
|
|
} else if in.MemberCategory == consts.AppMemberCategoryFake {
|
|
mod = mod.Where(col("m", mc.IsTestAccount), 1)
|
|
}
|
|
if in.RegisterDomain != "" {
|
|
mod = mod.WhereLike(col("ub", bc.DomainSnapshot), "%"+strings.ToLower(strings.TrimSpace(in.RegisterDomain))+"%")
|
|
}
|
|
if in.DomainStatus == consts.AppMemberDomainBoundEnabled {
|
|
mod = mod.WhereNotNull(col("ub", bc.Id)).Where(col("up", uc.Status), consts.UrlPoolStatusEnabled)
|
|
} else if in.DomainStatus == consts.AppMemberDomainUnbound {
|
|
mod = mod.WhereNull(col("ub", bc.Id))
|
|
} else if in.DomainStatus == consts.AppMemberDomainBoundInvalid {
|
|
mod = mod.WhereNotNull(col("ub", bc.Id)).Where(col("up", uc.Status), consts.UrlPoolStatusInvalid)
|
|
}
|
|
if len(in.CreatedAt) == 2 {
|
|
mod = mod.WhereBetween(col("m", mc.CreatedAt), in.CreatedAt[0], in.CreatedAt[1])
|
|
}
|
|
err = mod.Page(in.Page, in.PerPage).OrderDesc(col("m", mc.Id)).ScanAndCount(&list, &totalCount, false)
|
|
if err != nil {
|
|
return nil, 0, gerror.Wrap(err, "获取会员列表失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) View(ctx context.Context, in *sysin.AppMemberViewInp) (res *sysin.AppMemberViewModel, err error) {
|
|
mc, pc, wc := dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns()
|
|
bc, uc := dao.MemberUrlBinding.Columns(), dao.UrlPool.Columns()
|
|
res = new(sysin.AppMemberViewModel)
|
|
err = s.memberModel(ctx).As("m").LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("m", mc.Id)).LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").LeftJoin(dao.AppMember.Table()+" parent", col("parent", mc.Id)+"="+col("m", mc.ParentId)).LeftJoin(dao.MemberUrlBinding.Table()+" ub", col("ub", bc.MemberId)+"="+col("m", mc.Id)).LeftJoin(dao.UrlPool.Table()+" up", col("up", uc.Id)+"="+col("ub", bc.UrlPoolId)).Fields(
|
|
col("m", mc.Id), col("m", mc.MemberNo), col("m", mc.Username), col("m", mc.Mobile), col("p", pc.RealNameMask), col("p", pc.IdNoMask), col("p", pc.KycStatus), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)), fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), col("m", mc.ParentId), fmt.Sprintf("COALESCE(%s,'') parent_username", col("parent", mc.Username)), col("m", mc.InviteCode), col("m", mc.MemberStatus), col("m", mc.RiskStatus), col("m", mc.AutoDispatch), col("m", mc.IsTestAccount), fmt.Sprintf("CASE WHEN %s=1 THEN %d ELSE %d END member_category", col("m", mc.IsTestAccount), consts.AppMemberCategoryFake, consts.AppMemberCategoryReal), col("m", mc.LastLoginAt), col("m", mc.LastLoginIpMask), col("m", mc.LastLoginRegion), col("m", mc.CreatedAt), col("m", mc.LevelId), col("m", mc.Level), col("m", mc.CreditScore),
|
|
fmt.Sprintf("COALESCE(%s,0) url_pool_id", col("ub", bc.UrlPoolId)), fmt.Sprintf("COALESCE(%s,'') register_domain", col("ub", bc.DomainSnapshot)), col("ub", bc.BoundAt)+" domain_bound_at", fmt.Sprintf("CASE WHEN %s IS NULL THEN %d WHEN %s=%d THEN %d ELSE %d END domain_status", col("ub", bc.Id), consts.AppMemberDomainUnbound, col("up", uc.Status), consts.UrlPoolStatusEnabled, consts.AppMemberDomainBoundEnabled, consts.AppMemberDomainBoundInvalid),
|
|
).Where(col("m", mc.Id), in.Id).Scan(res)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "获取会员详情失败")
|
|
}
|
|
if res.Id == 0 {
|
|
return nil, gerror.New("会员不存在")
|
|
}
|
|
res.PaymentAccountCount, _ = s.paymentModel(ctx).Where(dao.AppMemberPaymentAccount.Columns().MemberId, in.Id).Count()
|
|
res.AddressCount, _ = s.addressModel(ctx).Where(dao.AppMemberAddress.Columns().MemberId, in.Id).Count()
|
|
token := "tr_" + fmt.Sprint(in.Id) + " "
|
|
res.TeamCount, _ = s.memberModel(ctx).WhereOr(dao.AppMember.Columns().ParentId, in.Id).WhereOrLike(dao.AppMember.Columns().Tree, "%"+token+"%").Count()
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) Edit(ctx context.Context, in *sysin.AppMemberEditInp) error {
|
|
if in.Id == 0 {
|
|
return gerror.New("后台不支持创建会员,请由会员在C端完成注册")
|
|
}
|
|
isTestAccount := 2
|
|
if in.MemberCategory == consts.AppMemberCategoryFake {
|
|
isTestAccount = 1
|
|
}
|
|
if isTestAccount == 1 && g.Cfg().MustGet(ctx, "system.mode").String() == "product" {
|
|
return gerror.New("生产环境禁止创建测试账号")
|
|
}
|
|
mc := dao.AppMember.Columns()
|
|
userId := uint64(contexts.GetUserId(ctx))
|
|
var before *entity.AppMember
|
|
if in.Id > 0 {
|
|
if err := s.memberModel(ctx).WherePri(in.Id).Scan(&before); err != nil {
|
|
return gerror.Wrap(err, "读取会员失败")
|
|
}
|
|
if before == nil {
|
|
return gerror.New("会员不存在")
|
|
}
|
|
}
|
|
count, err := s.memberModel(ctx).Where(mc.Username, in.Username).WhereNot(mc.Id, in.Id).Count()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return gerror.New("用户名已存在")
|
|
}
|
|
data := g.Map{mc.Username: in.Username, mc.LevelId: in.LevelId, mc.ParentId: in.ParentId, mc.RiskStatus: in.RiskStatus, mc.CreditScore: in.CreditScore, mc.AutoDispatch: in.AutoDispatch, mc.IsTestAccount: isTestAccount, mc.UpdatedBy: userId}
|
|
if in.Mobile != "" {
|
|
count, err = s.memberModel(ctx).Where(mc.Mobile, in.Mobile).WhereNot(mc.Id, in.Id).Count()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return gerror.New("手机号已存在")
|
|
}
|
|
data[mc.Mobile] = in.Mobile
|
|
}
|
|
if in.Password != "" {
|
|
hashed, e := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
|
|
if e != nil {
|
|
return gerror.Wrap(e, "生成密码摘要失败")
|
|
}
|
|
data[mc.PasswordHash] = string(hashed)
|
|
}
|
|
if in.TradePassword != "" {
|
|
hashed, e := bcrypt.GenerateFromPassword([]byte(in.TradePassword), bcrypt.DefaultCost)
|
|
if e != nil {
|
|
return gerror.Wrap(e, "生成交易密码摘要失败")
|
|
}
|
|
data[mc.TradePasswordHash] = string(hashed)
|
|
}
|
|
level, tree := 1, ""
|
|
if in.ParentId > 0 {
|
|
var parent *entity.AppMember
|
|
if err = s.memberModel(ctx).WherePri(in.ParentId).Scan(&parent); err != nil {
|
|
return err
|
|
}
|
|
if parent == nil {
|
|
return gerror.New("上级会员不存在")
|
|
}
|
|
if parent.Id == in.Id {
|
|
return gerror.New("上级会员不能是自己")
|
|
}
|
|
level = parent.Level + 1
|
|
tree = parent.Tree + "tr_" + fmt.Sprint(parent.Id) + " "
|
|
}
|
|
data[mc.Level] = level
|
|
data[mc.Tree] = tree
|
|
if in.Id > 0 {
|
|
_, err = s.memberModel(ctx).WherePri(in.Id).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新会员失败")
|
|
}
|
|
} else {
|
|
data[mc.MemberNo] = "MB" + gtime.Now().Format("YmdHis") + grand.S(5)
|
|
inviteCode, e := newAppMemberInviteCode(ctx, s.memberModel(ctx))
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[mc.InviteCode] = inviteCode
|
|
data[mc.MemberStatus] = consts.AppMemberStatusNormal
|
|
data[mc.CreatedBy] = userId
|
|
result, e := s.memberModel(ctx).Fields(mc.MemberNo, mc.Username, mc.Mobile, mc.PasswordHash, mc.TradePasswordHash, mc.LevelId, mc.ParentId, mc.Level, mc.Tree, mc.InviteCode, mc.MemberStatus, mc.RiskStatus, mc.AutoDispatch, mc.CreditScore, mc.IsTestAccount, mc.CreatedBy, mc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
|
if e != nil {
|
|
return gerror.Wrap(e, "创建会员失败")
|
|
}
|
|
id, _ := result.LastInsertId()
|
|
in.Id = uint64(id)
|
|
_, e = s.walletModel(ctx).Fields(dao.WalletAccount.Columns().MemberId, dao.WalletAccount.Columns().Currency).Hook(hook.SaveTenant).Data(g.Map{dao.WalletAccount.Columns().MemberId: in.Id, dao.WalletAccount.Columns().Currency: "CNY"}).Insert()
|
|
if e != nil {
|
|
return gerror.Wrap(e, "初始化会员钱包失败")
|
|
}
|
|
}
|
|
if in.RealName != "" || in.Id > 0 {
|
|
if err = s.saveProfile(ctx, in); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "member.edit", TargetType: "app_member", TargetId: in.Id, Before: before, After: g.Map{"username": in.Username, "mobileChanged": in.Mobile != "", "parentId": in.ParentId, "riskStatus": in.RiskStatus, "autoDispatch": in.AutoDispatch, "memberCategory": in.MemberCategory}})
|
|
}
|
|
|
|
func (s *sSysAppMember) saveProfile(ctx context.Context, in *sysin.AppMemberEditInp) error {
|
|
pc := dao.AppMemberProfile.Columns()
|
|
var old *entity.AppMemberProfile
|
|
_ = s.profileModel(ctx).Where(pc.MemberId, in.Id).Scan(&old)
|
|
data := g.Map{pc.UpdatedBy: contexts.GetUserId(ctx)}
|
|
if in.RealName != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.RealName)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[pc.RealNameCipher] = v
|
|
data[pc.RealNameMask] = encrypt.MaskName(in.RealName)
|
|
}
|
|
if in.IdNo != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.IdNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
h, e := encrypt.HashField(ctx, in.IdNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[pc.IdNoCipher] = v
|
|
data[pc.IdNoHash] = h
|
|
data[pc.IdNoMask] = encrypt.MaskAccount(in.IdNo)
|
|
}
|
|
if old != nil {
|
|
_, err := s.profileModel(ctx).WherePri(old.Id).Data(data).Update()
|
|
return err
|
|
}
|
|
data[pc.MemberId] = in.Id
|
|
data[pc.CreatedBy] = contexts.GetUserId(ctx)
|
|
_, err := s.profileModel(ctx).Fields(pc.MemberId, pc.RealNameCipher, pc.RealNameMask, pc.IdNoCipher, pc.IdNoHash, pc.IdNoMask, pc.CreatedBy, pc.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
|
return err
|
|
}
|
|
|
|
func (s *sSysAppMember) Delete(ctx context.Context, in *sysin.AppMemberDeleteInp) error {
|
|
var wallet *entity.WalletAccount
|
|
_ = s.walletModel(ctx).Where(dao.WalletAccount.Columns().MemberId, in.Id).Scan(&wallet)
|
|
if wallet != nil && (wallet.AvailableAmount != 0 || wallet.FrozenAmount != 0) {
|
|
return gerror.New("会员钱包仍有余额或冻结金额,不能注销")
|
|
}
|
|
var before *entity.AppMember
|
|
if err := s.memberModel(ctx).WherePri(in.Id).Scan(&before); err != nil {
|
|
return err
|
|
}
|
|
if before == nil {
|
|
return gerror.New("会员不存在")
|
|
}
|
|
mc := dao.AppMember.Columns()
|
|
_, err := s.memberModel(ctx).WherePri(in.Id).Data(g.Map{mc.MemberStatus: consts.AppMemberStatusCancelled, mc.DeletedBy: contexts.GetUserId(ctx), mc.DeletedAt: gtime.Now(), mc.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "注销会员失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "member.cancel", TargetType: "app_member", TargetId: in.Id, Before: before, After: g.Map{"status": consts.AppMemberStatusCancelled}, Reason: in.Reason})
|
|
}
|
|
|
|
func (s *sSysAppMember) AutoDispatch(ctx context.Context, in *sysin.AppMemberAutoDispatchInp) error {
|
|
mc := dao.AppMember.Columns()
|
|
_, err := s.memberModel(ctx).WherePri(in.Id).Data(g.Map{mc.AutoDispatch: in.AutoDispatch, mc.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新自动派单失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "member.auto_dispatch", TargetType: "app_member", TargetId: in.Id, After: g.Map{"autoDispatch": in.AutoDispatch}})
|
|
}
|
|
|
|
func (s *sSysAppMember) Category(ctx context.Context, in *sysin.AppMemberCategoryInp) error {
|
|
isTestAccount := 2
|
|
if in.MemberCategory == consts.AppMemberCategoryFake {
|
|
isTestAccount = 1
|
|
}
|
|
mc := dao.AppMember.Columns()
|
|
result, err := s.memberModel(ctx).WherePri(in.Id).Data(g.Map{mc.IsTestAccount: isTestAccount, mc.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "切换会员类别失败")
|
|
}
|
|
affected, _ := result.RowsAffected()
|
|
if affected == 0 {
|
|
return gerror.New("会员不存在或类别未发生变化")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "member.category", TargetType: "app_member", TargetId: in.Id, After: g.Map{"memberCategory": in.MemberCategory}})
|
|
}
|
|
|
|
func (s *sSysAppMember) PaymentList(ctx context.Context, in *sysin.AppMemberPaymentListInp) (list []*sysin.AppMemberPaymentModel, err error) {
|
|
c := dao.AppMemberPaymentAccount.Columns()
|
|
ac := dao.SysAttachment.Columns()
|
|
err = s.paymentModel(ctx).As("p").
|
|
LeftJoin(dao.SysAttachment.Table()+" wq", col("wq", ac.Id)+"="+col("p", c.WechatQrAttachmentId)).
|
|
LeftJoin(dao.SysAttachment.Table()+" aq", col("aq", ac.Id)+"="+col("p", c.AlipayQrAttachmentId)).
|
|
Fields(col("p", c.Id), col("p", c.MemberId), col("p", c.AccountType), col("p", c.BankCode), col("p", c.BankName), col("p", c.HolderMask), col("p", c.IdentityNoMask), col("p", c.MobileMask), col("p", c.AccountNoMask), col("p", c.DigitalRmbCodeMask), col("p", c.QrAttachmentId), col("p", c.WechatQrAttachmentId), col("p", c.AlipayQrAttachmentId), fmt.Sprintf("COALESCE(%s,'') wechat_qr_url", col("wq", ac.FileUrl)), fmt.Sprintf("COALESCE(%s,'') alipay_qr_url", col("aq", ac.FileUrl)), col("p", c.VerifyStatus), col("p", c.IsDefault), col("p", c.CreatedAt), col("p", c.UpdatedAt)).
|
|
Where(col("p", c.MemberId), in.MemberId).OrderDesc(col("p", c.IsDefault)).OrderDesc(col("p", c.Id)).Scan(&list)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取收款账户失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) PaymentEdit(ctx context.Context, in *sysin.AppMemberPaymentEditInp) error {
|
|
c := dao.AppMemberPaymentAccount.Columns()
|
|
attachmentColumns := dao.SysAttachment.Columns()
|
|
if in.WechatQrAttachmentId == 0 && in.WechatQrUrl != "" {
|
|
_ = dao.SysAttachment.Ctx(ctx).Fields(attachmentColumns.Id).Where(attachmentColumns.FileUrl, in.WechatQrUrl).OrderDesc(attachmentColumns.Id).Limit(1).Scan(&in.WechatQrAttachmentId)
|
|
}
|
|
if in.AlipayQrAttachmentId == 0 && in.AlipayQrUrl != "" {
|
|
_ = dao.SysAttachment.Ctx(ctx).Fields(attachmentColumns.Id).Where(attachmentColumns.FileUrl, in.AlipayQrUrl).OrderDesc(attachmentColumns.Id).Limit(1).Scan(&in.AlipayQrAttachmentId)
|
|
}
|
|
data := g.Map{c.MemberId: in.MemberId, c.AccountType: in.AccountType, c.BankCode: in.BankCode, c.BankName: in.BankName, c.VerifyStatus: in.VerifyStatus, c.IsDefault: in.IsDefault, c.UpdatedBy: contexts.GetUserId(ctx)}
|
|
if in.QrAttachmentId > 0 {
|
|
data[c.QrAttachmentId] = in.QrAttachmentId
|
|
}
|
|
if in.WechatQrAttachmentId > 0 {
|
|
data[c.WechatQrAttachmentId] = in.WechatQrAttachmentId
|
|
}
|
|
if in.AlipayQrAttachmentId > 0 {
|
|
data[c.AlipayQrAttachmentId] = in.AlipayQrAttachmentId
|
|
}
|
|
if in.Holder != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.Holder)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.HolderCipher] = v
|
|
data[c.HolderMask] = encrypt.MaskName(in.Holder)
|
|
}
|
|
if in.Mobile != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.Mobile)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.MobileCipher] = v
|
|
data[c.MobileMask] = encrypt.MaskMobile(in.Mobile)
|
|
}
|
|
if in.IdentityNo != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.IdentityNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
h, e := encrypt.HashField(ctx, in.IdentityNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.IdentityNoCipher] = v
|
|
data[c.IdentityNoHash] = h
|
|
data[c.IdentityNoMask] = encrypt.MaskAccount(in.IdentityNo)
|
|
}
|
|
if in.AccountNo != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.AccountNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
h, e := encrypt.HashField(ctx, in.AccountNo)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.AccountNoCipher] = v
|
|
data[c.AccountNoHash] = h
|
|
data[c.AccountNoMask] = encrypt.MaskAccount(in.AccountNo)
|
|
}
|
|
if in.DigitalRMBCode != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.DigitalRMBCode)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
h, e := encrypt.HashField(ctx, in.DigitalRMBCode)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.DigitalRmbCodeCipher] = v
|
|
data[c.DigitalRmbCodeHash] = h
|
|
data[c.DigitalRmbCodeMask] = encrypt.MaskAccount(in.DigitalRMBCode)
|
|
}
|
|
if in.IsDefault == 1 {
|
|
_, _ = s.paymentModel(ctx).Where(c.MemberId, in.MemberId).Data(g.Map{c.IsDefault: 2}).Update()
|
|
}
|
|
if in.Id > 0 {
|
|
_, err := s.paymentModel(ctx).WherePri(in.Id).Where(c.MemberId, in.MemberId).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新收款账户失败")
|
|
}
|
|
} else {
|
|
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
|
_, err := s.paymentModel(ctx).Fields(c.MemberId, c.AccountType, c.BankCode, c.BankName, c.HolderCipher, c.HolderMask, c.IdentityNoCipher, c.IdentityNoHash, c.IdentityNoMask, c.MobileCipher, c.MobileMask, c.AccountNoCipher, c.AccountNoHash, c.AccountNoMask, c.DigitalRmbCodeCipher, c.DigitalRmbCodeHash, c.DigitalRmbCodeMask, c.QrAttachmentId, c.WechatQrAttachmentId, c.AlipayQrAttachmentId, c.VerifyStatus, c.IsDefault, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "创建收款账户失败")
|
|
}
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "payment.edit", TargetType: "app_member_payment_account", TargetId: in.Id, After: g.Map{"memberId": in.MemberId, "accountType": in.AccountType, "bankName": in.BankName, "sensitiveChanged": in.Holder != "" || in.Mobile != "" || in.AccountNo != ""}})
|
|
}
|
|
func (s *sSysAppMember) PaymentDelete(ctx context.Context, in *sysin.AppMemberPaymentDeleteInp) error {
|
|
c := dao.AppMemberPaymentAccount.Columns()
|
|
_, err := s.paymentModel(ctx).WherePri(in.Id).Data(g.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "删除收款账户失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "payment.delete", TargetType: "app_member_payment_account", TargetId: in.Id})
|
|
}
|
|
|
|
func (s *sSysAppMember) AddressList(ctx context.Context, in *sysin.AppMemberAddressListInp) (list []*sysin.AppMemberAddressModel, err error) {
|
|
c := dao.AppMemberAddress.Columns()
|
|
err = s.addressModel(ctx).Fields(c.Id, c.MemberId, c.ReceiverMask, c.MobileMask, c.Province, c.City, c.District, c.AddressMask, c.IsDefault, c.CreatedAt, c.UpdatedAt).Where(c.MemberId, in.MemberId).OrderDesc(c.IsDefault).OrderDesc(c.Id).Scan(&list)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取会员地址失败")
|
|
}
|
|
return
|
|
}
|
|
func (s *sSysAppMember) AddressEdit(ctx context.Context, in *sysin.AppMemberAddressEditInp) error {
|
|
c := dao.AppMemberAddress.Columns()
|
|
data := g.Map{c.MemberId: in.MemberId, c.Province: in.Province, c.City: in.City, c.District: in.District, c.IsDefault: in.IsDefault, c.UpdatedBy: contexts.GetUserId(ctx)}
|
|
if in.Receiver != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.Receiver)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.ReceiverCipher] = v
|
|
data[c.ReceiverMask] = encrypt.MaskName(in.Receiver)
|
|
}
|
|
if in.Mobile != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.Mobile)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.MobileCipher] = v
|
|
data[c.MobileMask] = encrypt.MaskMobile(in.Mobile)
|
|
}
|
|
if in.Address != "" {
|
|
v, e := encrypt.EncryptField(ctx, in.Address)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
data[c.AddressCipher] = v
|
|
data[c.AddressMask] = encrypt.MaskAddress(in.Address)
|
|
}
|
|
if in.IsDefault == 1 {
|
|
_, _ = s.addressModel(ctx).Where(c.MemberId, in.MemberId).Data(g.Map{c.IsDefault: 2}).Update()
|
|
}
|
|
if in.Id > 0 {
|
|
_, err := s.addressModel(ctx).WherePri(in.Id).Where(c.MemberId, in.MemberId).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新地址失败")
|
|
}
|
|
} else {
|
|
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
|
_, err := s.addressModel(ctx).Fields(c.MemberId, c.ReceiverCipher, c.ReceiverMask, c.MobileCipher, c.MobileMask, c.Province, c.City, c.District, c.AddressCipher, c.AddressMask, c.IsDefault, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "创建地址失败")
|
|
}
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "address.edit", TargetType: "app_member_address", TargetId: in.Id, After: g.Map{"memberId": in.MemberId, "region": in.Province + in.City + in.District, "sensitiveChanged": in.Receiver != "" || in.Mobile != "" || in.Address != ""}})
|
|
}
|
|
func (s *sSysAppMember) AddressDelete(ctx context.Context, in *sysin.AppMemberAddressDeleteInp) error {
|
|
c := dao.AppMemberAddress.Columns()
|
|
_, err := s.addressModel(ctx).WherePri(in.Id).Data(g.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "删除地址失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "address.delete", TargetType: "app_member_address", TargetId: in.Id})
|
|
}
|
|
|
|
func (s *sSysAppMember) Sensitive(ctx context.Context, in *sysin.AppMemberSensitiveInp) (res *sysin.AppMemberSensitiveModel, err error) {
|
|
res = &sysin.AppMemberSensitiveModel{Fields: map[string]string{}, AccessNo: "SA" + gtime.Now().Format("YmdHis") + grand.S(6)}
|
|
switch in.TargetType {
|
|
case "member":
|
|
var member *entity.AppMember
|
|
if err = s.memberModel(ctx).WherePri(in.TargetId).Scan(&member); err == nil && member != nil {
|
|
res.Fields["mobile"] = member.Mobile
|
|
var p *entity.AppMemberProfile
|
|
_ = s.profileModel(ctx).Where(dao.AppMemberProfile.Columns().MemberId, in.TargetId).Scan(&p)
|
|
if p != nil {
|
|
res.Fields["realName"], _ = encrypt.DecryptField(ctx, p.RealNameCipher)
|
|
res.Fields["idNo"], _ = encrypt.DecryptField(ctx, p.IdNoCipher)
|
|
}
|
|
}
|
|
case "payment":
|
|
var row *entity.AppMemberPaymentAccount
|
|
if err = s.paymentModel(ctx).WherePri(in.TargetId).Scan(&row); err == nil && row != nil {
|
|
res.Fields["holder"], _ = encrypt.DecryptField(ctx, row.HolderCipher)
|
|
res.Fields["identityNo"], _ = encrypt.DecryptField(ctx, row.IdentityNoCipher)
|
|
res.Fields["mobile"], _ = encrypt.DecryptField(ctx, row.MobileCipher)
|
|
res.Fields["accountNo"], _ = encrypt.DecryptField(ctx, row.AccountNoCipher)
|
|
res.Fields["digitalRmbCode"], _ = encrypt.DecryptField(ctx, row.DigitalRmbCodeCipher)
|
|
if res.Fields["holder"] == "" {
|
|
res.Fields["holder"] = row.HolderMask
|
|
}
|
|
if res.Fields["identityNo"] == "" {
|
|
res.Fields["identityNo"] = row.IdentityNoMask
|
|
}
|
|
if res.Fields["mobile"] == "" {
|
|
res.Fields["mobile"] = row.MobileMask
|
|
}
|
|
if res.Fields["accountNo"] == "" {
|
|
res.Fields["accountNo"] = row.AccountNoMask
|
|
}
|
|
if res.Fields["digitalRmbCode"] == "" {
|
|
res.Fields["digitalRmbCode"] = row.DigitalRmbCodeMask
|
|
}
|
|
}
|
|
case "address":
|
|
var row *entity.AppMemberAddress
|
|
if err = s.addressModel(ctx).WherePri(in.TargetId).Scan(&row); err == nil && row != nil {
|
|
res.Fields["receiver"], _ = encrypt.DecryptField(ctx, row.ReceiverCipher)
|
|
res.Fields["mobile"], _ = encrypt.DecryptField(ctx, row.MobileCipher)
|
|
res.Fields["address"], err = encrypt.DecryptField(ctx, row.AddressCipher)
|
|
}
|
|
}
|
|
if err == nil && len(res.Fields) == 0 {
|
|
err = gerror.New("敏感信息目标不存在或无可查看字段")
|
|
}
|
|
result := 1
|
|
if err != nil {
|
|
result = 2
|
|
}
|
|
lc := dao.SensitiveAccessLog.Columns()
|
|
_, logErr := dao.SensitiveAccessLog.Ctx(ctx).Fields(lc.AccessNo, lc.ActorId, lc.TargetType, lc.TargetId, lc.FieldSet, lc.PurposeCode, lc.PurposeText, lc.AuthorizationNo, lc.MfaVerified, lc.Result).Hook(hook.SaveTenant).Data(g.Map{lc.AccessNo: res.AccessNo, lc.ActorId: contexts.GetUserId(ctx), lc.TargetType: in.TargetType, lc.TargetId: in.TargetId, lc.FieldSet: gjson.New(res.Fields), lc.PurposeCode: in.PurposeCode, lc.PurposeText: in.PurposeText, lc.AuthorizationNo: res.AccessNo, lc.MfaVerified: 0, lc.Result: result}).Insert()
|
|
if logErr != nil {
|
|
return nil, gerror.Wrap(logErr, "记录敏感信息访问失败")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) Team(ctx context.Context, in *sysin.AppMemberTeamInp) (list []*sysin.AppMemberTeamModel, total int, err error) {
|
|
var root *entity.AppMember
|
|
if err = s.memberModel(ctx).WherePri(in.MemberId).Scan(&root); err != nil {
|
|
return
|
|
}
|
|
if root == nil {
|
|
return nil, 0, gerror.New("会员不存在")
|
|
}
|
|
mc, wc := dao.AppMember.Columns(), dao.WalletAccount.Columns()
|
|
token := "tr_" + fmt.Sprint(in.MemberId) + " "
|
|
maxAbsolute := root.Level + in.MaxLevel
|
|
mod := s.memberModel(ctx).As("m").LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("m", mc.Id)+" AND "+col("w", wc.Currency)+"='CNY'").Fields(col("m", mc.Id), col("m", mc.Username), col("m", mc.Mobile), col("m", mc.ParentId), fmt.Sprintf("%s-%d relative_level", col("m", mc.Level), root.Level), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)), fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), col("m", mc.MemberStatus), col("m", mc.CreatedAt)).WhereLike(col("m", mc.Tree), "%"+token+"%").WhereLTE(col("m", mc.Level), maxAbsolute)
|
|
err = mod.Page(in.Page, in.PerPage).OrderAsc(col("m", mc.Level)).OrderAsc(col("m", mc.Id)).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "查询会员团队失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) StatusRequest(ctx context.Context, in *sysin.AppMemberStatusRequestInp) (res *sysin.AppMemberApprovalModel, err error) {
|
|
var member *entity.AppMember
|
|
if err = s.memberModel(ctx).WherePri(in.MemberId).Scan(&member); err != nil {
|
|
return
|
|
}
|
|
if member == nil {
|
|
return nil, gerror.New("会员不存在")
|
|
}
|
|
if member.MemberStatus == in.ToStatus {
|
|
return nil, gerror.New("会员已处于目标状态")
|
|
}
|
|
payload := gjson.New(g.Map{"memberId": in.MemberId, "fromStatus": member.MemberStatus, "toStatus": in.ToStatus, "reasonCode": in.ReasonCode, "reasonText": in.ReasonText, "expireAt": in.ExpireAt})
|
|
approval, err := service.SysApproval().Create(ctx, &sysin.ApprovalCreateInp{ApprovalType: "member_status", TargetType: "app_member", TargetId: in.MemberId, Action: "change_status", Title: "会员状态变更 " + member.Username, PayloadJson: payload, DiffJson: gjson.New(g.Map{"memberStatus": g.Map{"before": member.MemberStatus, "after": in.ToStatus}}), RiskLevel: consts.ApprovalRiskHigh, RequiredApprovals: 2, Remark: in.ReasonText})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = service.SysApproval().Submit(ctx, &sysin.ApprovalSubmitInp{Id: approval.Id, Version: 0}); err != nil {
|
|
return nil, err
|
|
}
|
|
return &sysin.AppMemberApprovalModel{ApprovalId: approval.Id, ApprovalNo: approval.ApprovalNo}, nil
|
|
}
|
|
|
|
func (s *sSysAppMember) StatusExecute(ctx context.Context, in *sysin.AppMemberStatusExecuteInp) error {
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var approval *entity.ApprovalRequest
|
|
if err := service.SysApproval().Model(ctx).WherePri(in.ApprovalId).LockUpdate().Scan(&approval); err != nil {
|
|
return err
|
|
}
|
|
if approval == nil || approval.ApprovalType != "member_status" {
|
|
return gerror.New("会员状态审批不存在")
|
|
}
|
|
if approval.ApprovalStatus != consts.ApprovalStatusApproved {
|
|
return gerror.New("审批尚未通过")
|
|
}
|
|
if approval.ExecutedAt != nil {
|
|
return gerror.New("该审批已执行")
|
|
}
|
|
memberId := approval.PayloadJson.Get("memberId").Uint64()
|
|
toStatus := approval.PayloadJson.Get("toStatus").Int()
|
|
reasonCode := approval.PayloadJson.Get("reasonCode").String()
|
|
reasonText := approval.PayloadJson.Get("reasonText").String()
|
|
var member *entity.AppMember
|
|
if err := s.memberModel(ctx).WherePri(memberId).LockUpdate().Scan(&member); err != nil {
|
|
return err
|
|
}
|
|
if member == nil {
|
|
return gerror.New("会员不存在")
|
|
}
|
|
mc := dao.AppMember.Columns()
|
|
if _, err := s.memberModel(ctx).WherePri(memberId).Data(g.Map{mc.MemberStatus: toStatus, mc.UpdatedBy: contexts.GetUserId(ctx)}).Update(); err != nil {
|
|
return gerror.Wrap(err, "更新会员状态失败")
|
|
}
|
|
sc := dao.AppMemberStatusLog.Columns()
|
|
_, err := dao.AppMemberStatusLog.Ctx(ctx).Fields(sc.MemberId, sc.FromStatus, sc.ToStatus, sc.ReasonCode, sc.ReasonText, sc.EffectiveAt, sc.ExpireAt, sc.ApprovalRequestId, sc.OperatorId).Hook(hook.SaveTenant).Data(g.Map{sc.MemberId: memberId, sc.FromStatus: member.MemberStatus, sc.ToStatus: toStatus, sc.ReasonCode: reasonCode, sc.ReasonText: reasonText, sc.EffectiveAt: gtime.Now(), sc.ExpireAt: approval.PayloadJson.Get("expireAt"), sc.ApprovalRequestId: approval.Id, sc.OperatorId: contexts.GetUserId(ctx)}).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(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
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "member.status.execute", TargetType: "app_member", TargetId: memberId, Before: g.Map{"memberStatus": member.MemberStatus}, After: g.Map{"memberStatus": toStatus}, Reason: reasonText, ApprovalNo: approval.ApprovalNo})
|
|
})
|
|
}
|
|
|
|
func (s *sSysAppMember) WalletList(ctx context.Context, in *sysin.WalletAdjustmentListInp) (list []*sysin.WalletAdjustmentListModel, total int, err error) {
|
|
ac, mc := dao.WalletAdjustmentRequest.Columns(), dao.AppMember.Columns()
|
|
mod := s.adjustmentModel(ctx).As("a").LeftJoin(dao.AppMember.Table()+" m", col("m", mc.Id)+"="+col("a", ac.MemberId)).Fields(col("a", ac.Id), col("a", ac.RequestNo), col("a", ac.MemberId), col("m", mc.Username), col("m", mc.Mobile), col("a", ac.AdjustType), col("a", ac.Direction), col("a", ac.Amount), col("a", ac.ReasonCode), col("a", ac.ReasonText), col("a", ac.RequestStatus), col("a", ac.ApprovalRequestId), col("a", ac.LedgerId), col("a", ac.CreatedAt))
|
|
if in.MemberId > 0 {
|
|
mod = mod.Where(col("a", ac.MemberId), in.MemberId)
|
|
}
|
|
if in.RequestStatus > 0 {
|
|
mod = mod.Where(col("a", ac.RequestStatus), in.RequestStatus)
|
|
}
|
|
if len(in.CreatedAt) == 2 {
|
|
mod = mod.WhereBetween(col("a", ac.CreatedAt), in.CreatedAt[0], in.CreatedAt[1])
|
|
}
|
|
err = mod.Page(in.Page, in.PerPage).OrderDesc(col("a", ac.Id)).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取资金调账列表失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) LedgerList(ctx context.Context, in *sysin.WalletLedgerListInp) (list []*sysin.WalletLedgerListModel, total int, err error) {
|
|
c := dao.WalletLedger.Columns()
|
|
mod := handler.Model(dao.WalletLedger.Ctx(ctx), &handler.Option{FilterTenant: true}).
|
|
Fields(c.Id, c.LedgerNo, c.BizType, c.BizNo, c.EntryType, c.Direction, c.Amount, c.BeforeAmount, c.AfterAmount, c.Currency, c.Remark, c.OccurredAt).
|
|
Where(c.MemberId, in.MemberId)
|
|
err = mod.Page(in.Page, in.PerPage).OrderDesc(c.OccurredAt).OrderDesc(c.Id).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取会员账变流水失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysAppMember) WalletRequest(ctx context.Context, in *sysin.WalletAdjustmentRequestInp) (res *sysin.AppMemberApprovalModel, err error) {
|
|
var member *entity.AppMember
|
|
if err = s.memberModel(ctx).WherePri(in.MemberId).Scan(&member); err != nil {
|
|
return
|
|
}
|
|
if member == nil {
|
|
return nil, gerror.New("会员不存在")
|
|
}
|
|
wc := dao.WalletAccount.Columns()
|
|
var account *entity.WalletAccount
|
|
if err = s.walletModel(ctx).Where(wc.MemberId, in.MemberId).Where(wc.Currency, "CNY").Scan(&account); err != nil {
|
|
return
|
|
}
|
|
if account == nil {
|
|
return nil, gerror.New("会员钱包不存在")
|
|
}
|
|
payload := gjson.New(g.Map{"memberId": in.MemberId, "accountId": account.Id, "adjustType": in.AdjustType, "direction": in.Direction, "amount": in.Amount, "reasonCode": in.ReasonCode, "reasonText": in.ReasonText, "idempotencyKey": in.IdempotencyKey, "evidence": in.EvidenceJson})
|
|
approval, err := service.SysApproval().Create(ctx, &sysin.ApprovalCreateInp{ApprovalType: "wallet_adjustment", TargetType: "wallet_account", TargetId: account.Id, Action: "adjust_balance", Title: "会员资金调账 " + member.Username, PayloadJson: payload, DiffJson: gjson.New(g.Map{"direction": in.Direction, "amount": in.Amount}), RiskLevel: consts.ApprovalRiskHigh, RequiredApprovals: 2, Remark: in.ReasonText})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = service.SysApproval().Submit(ctx, &sysin.ApprovalSubmitInp{Id: approval.Id, Version: 0}); err != nil {
|
|
return nil, err
|
|
}
|
|
c := dao.WalletAdjustmentRequest.Columns()
|
|
_, err = s.adjustmentModel(ctx).Fields(c.RequestNo, c.MemberId, c.AccountId, c.AdjustType, c.Direction, c.Amount, c.ReasonCode, c.ReasonText, c.EvidenceJson, c.IdempotencyKey, c.RequestStatus, c.ApprovalRequestId, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(g.Map{c.RequestNo: "WA" + gtime.Now().Format("YmdHis") + grand.S(6), c.MemberId: in.MemberId, c.AccountId: account.Id, c.AdjustType: in.AdjustType, c.Direction: in.Direction, c.Amount: in.Amount, c.ReasonCode: in.ReasonCode, c.ReasonText: in.ReasonText, c.EvidenceJson: in.EvidenceJson, c.IdempotencyKey: in.IdempotencyKey, c.RequestStatus: consts.WalletRequestPending, c.ApprovalRequestId: approval.Id, c.CreatedBy: contexts.GetUserId(ctx), c.UpdatedBy: contexts.GetUserId(ctx)}).Insert()
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "创建调账申请失败")
|
|
}
|
|
return &sysin.AppMemberApprovalModel{ApprovalId: approval.Id, ApprovalNo: approval.ApprovalNo}, nil
|
|
}
|
|
|
|
func (s *sSysAppMember) WalletExecute(ctx context.Context, in *sysin.WalletAdjustmentExecuteInp) error {
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var request *entity.WalletAdjustmentRequest
|
|
if err := s.adjustmentModel(ctx).WherePri(in.Id).LockUpdate().Scan(&request); err != nil {
|
|
return err
|
|
}
|
|
if request == nil {
|
|
return gerror.New("调账申请不存在")
|
|
}
|
|
if request.RequestStatus == consts.WalletRequestExecuted {
|
|
return gerror.New("调账申请已执行")
|
|
}
|
|
var approval *entity.ApprovalRequest
|
|
if err := service.SysApproval().Model(ctx).WherePri(request.ApprovalRequestId).LockUpdate().Scan(&approval); err != nil {
|
|
return err
|
|
}
|
|
if approval == nil || approval.ApprovalStatus != consts.ApprovalStatusApproved {
|
|
return gerror.New("审批尚未通过")
|
|
}
|
|
wc := dao.WalletAccount.Columns()
|
|
var account *entity.WalletAccount
|
|
if err := s.walletModel(ctx).WherePri(request.AccountId).LockUpdate().Scan(&account); err != nil {
|
|
return err
|
|
}
|
|
if account == nil {
|
|
return gerror.New("钱包账户不存在")
|
|
}
|
|
after := account.AvailableAmount + request.Amount
|
|
if request.Direction == consts.WalletDirectionExpense {
|
|
after = account.AvailableAmount - request.Amount
|
|
}
|
|
if after < 0 {
|
|
return gerror.New("会员可用余额不足,不能执行扣减")
|
|
}
|
|
lc := dao.WalletLedger.Columns()
|
|
ledgerNo := "WL" + gtime.Now().Format("YmdHis") + grand.S(7)
|
|
result, err := dao.WalletLedger.Ctx(ctx).Fields(lc.LedgerNo, lc.AccountId, lc.MemberId, lc.BizType, lc.BizNo, lc.EntryType, lc.Direction, lc.Amount, lc.BeforeAmount, lc.AfterAmount, lc.Currency, lc.IdempotencyKey, lc.Remark, lc.OperatorId, lc.OccurredAt).Hook(hook.SaveTenant).Data(g.Map{lc.LedgerNo: ledgerNo, lc.AccountId: account.Id, lc.MemberId: request.MemberId, lc.BizType: "manual_adjustment", lc.BizNo: request.RequestNo, lc.EntryType: fmt.Sprint(request.AdjustType), lc.Direction: request.Direction, lc.Amount: request.Amount, lc.BeforeAmount: account.AvailableAmount, lc.AfterAmount: after, lc.Currency: account.Currency, lc.IdempotencyKey: request.IdempotencyKey, lc.Remark: request.ReasonText, lc.OperatorId: contexts.GetUserId(ctx), lc.OccurredAt: gtime.Now()}).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "写入账变流水失败")
|
|
}
|
|
ledgerId, _ := result.LastInsertId()
|
|
walletData := g.Map{wc.AvailableAmount: after, wc.Version: gdb.Raw(wc.Version + "+1")}
|
|
if request.Direction == consts.WalletDirectionIncome {
|
|
walletData[wc.TotalIncome] = gdb.Raw(wc.TotalIncome + "+" + fmt.Sprint(request.Amount))
|
|
} else {
|
|
walletData[wc.TotalExpense] = gdb.Raw(wc.TotalExpense + "+" + fmt.Sprint(request.Amount))
|
|
}
|
|
update, err := s.walletModel(ctx).WherePri(account.Id).Where(wc.Version, account.Version).Data(walletData).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新钱包余额失败")
|
|
}
|
|
rows, _ := update.RowsAffected()
|
|
if rows != 1 {
|
|
return gerror.New("钱包余额已变化,请重试")
|
|
}
|
|
rc := dao.WalletAdjustmentRequest.Columns()
|
|
_, err = s.adjustmentModel(ctx).WherePri(request.Id).Data(g.Map{rc.RequestStatus: consts.WalletRequestExecuted, rc.LedgerId: ledgerId, rc.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
|
if 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
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "wallet", Action: "wallet.adjust.execute", TargetType: "wallet_account", TargetId: account.Id, Before: g.Map{"availableAmount": account.AvailableAmount}, After: g.Map{"availableAmount": after, "ledgerId": ledgerId}, Reason: request.ReasonText, ApprovalNo: approval.ApprovalNo})
|
|
})
|
|
}
|
|
|
|
func (s *sSysAppMember) NoticeList(ctx context.Context, in *sysin.MemberNoticeListInp) (list []*sysin.MemberNoticeListModel, total int, err error) {
|
|
nc, mc := dao.AppMemberNotice.Columns(), dao.AppMember.Columns()
|
|
mod := s.noticeModel(ctx).As("n").LeftJoin(dao.AppMember.Table()+" m", col("m", mc.Id)+"="+col("n", nc.MemberId)).Fields(col("n", nc.Id), col("n", nc.TenantId), col("n", nc.MemberId), col("n", nc.Title), col("n", nc.Content), col("n", nc.SourceType), col("n", nc.StartAt), col("n", nc.EndAt), col("n", nc.NoticeStatus), col("n", nc.CreatedBy), col("n", nc.UpdatedBy), col("n", nc.CreatedAt), col("n", nc.UpdatedAt), fmt.Sprintf("COALESCE(%s,'全体会员') username", col("m", mc.Username)))
|
|
if in.Title != "" {
|
|
mod = mod.WhereLike(col("n", nc.Title), "%"+in.Title+"%")
|
|
}
|
|
if in.SourceType > 0 {
|
|
mod = mod.Where(col("n", nc.SourceType), in.SourceType)
|
|
}
|
|
if in.NoticeStatus > 0 {
|
|
mod = mod.Where(col("n", nc.NoticeStatus), in.NoticeStatus)
|
|
}
|
|
err = mod.Page(in.Page, in.PerPage).OrderDesc(col("n", nc.Id)).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取会员通知失败")
|
|
}
|
|
return
|
|
}
|
|
func (s *sSysAppMember) NoticeEdit(ctx context.Context, in *sysin.MemberNoticeEditInp) error {
|
|
if in.SourceType == consts.MemberNoticeSimulation && g.Cfg().MustGet(ctx, "system.mode").String() == "product" {
|
|
return gerror.New("生产环境禁止创建运营模拟通知")
|
|
}
|
|
c := dao.AppMemberNotice.Columns()
|
|
_, err := s.noticeModel(ctx).Fields(c.MemberId, c.Title, c.Content, c.SourceType, c.StartAt, c.EndAt, c.NoticeStatus, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(g.Map{c.MemberId: in.MemberId, c.Title: in.Title, c.Content: in.Content, c.SourceType: in.SourceType, c.StartAt: in.StartAt, c.EndAt: in.EndAt, c.NoticeStatus: consts.MemberNoticeActive, c.CreatedBy: contexts.GetUserId(ctx), c.UpdatedBy: contexts.GetUserId(ctx)}).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "创建会员通知失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "notice.create", TargetType: "app_member_notice", After: g.Map{"memberId": in.MemberId, "title": in.Title, "sourceType": in.SourceType, "startAt": in.StartAt, "endAt": in.EndAt}})
|
|
}
|
|
func (s *sSysAppMember) NoticeRevoke(ctx context.Context, in *sysin.MemberNoticeRevokeInp) error {
|
|
c := dao.AppMemberNotice.Columns()
|
|
_, err := s.noticeModel(ctx).WherePri(in.Id).Data(g.Map{c.NoticeStatus: consts.MemberNoticeRevoked, c.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "撤回会员通知失败")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "member", Action: "notice.revoke", TargetType: "app_member_notice", TargetId: in.Id, Reason: in.Reason})
|
|
}
|