feat: 完善会员交易与任务管理流程
This commit is contained in:
@@ -9,10 +9,10 @@ import (
|
||||
"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"
|
||||
@@ -30,14 +30,15 @@ func (s *sSysTradeOrder) model(ctx context.Context) *gdb.Model {
|
||||
}
|
||||
|
||||
func (s *sSysTradeOrder) listModel(ctx context.Context) *gdb.Model {
|
||||
oc, mc, pc, wc, ac := dao.TradeOrder.Columns(), dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns(), dao.ApprovalRequest.Columns()
|
||||
oc, mc, pc, wc, ac, sc := dao.TradeOrder.Columns(), dao.AppMember.Columns(), dao.AppMemberProfile.Columns(), dao.WalletAccount.Columns(), dao.ApprovalRequest.Columns(), dao.TaskSubmission.Columns()
|
||||
todayCount := fmt.Sprintf("(SELECT COUNT(*) FROM %s td WHERE td.%s=%s AND td.%s>=CURDATE()) today_trade_count", dao.TradeOrder.Table(), oc.MemberId, col("o", oc.MemberId), oc.CreatedAt)
|
||||
canReview := fmt.Sprintf("EXISTS(SELECT 1 FROM %s ts WHERE ts.%s=%s AND ts.%s=%d) can_review", dao.TaskSubmission.Table(), sc.TradeOrderId, col("o", oc.Id), sc.SubmissionStatus, consts.TaskSubmissionPending)
|
||||
return s.model(ctx).As("o").
|
||||
LeftJoin(dao.AppMember.Table()+" m", col("m", mc.Id)+"="+col("o", oc.MemberId)).
|
||||
LeftJoin(dao.AppMemberProfile.Table()+" p", col("p", pc.MemberId)+"="+col("o", oc.MemberId)).
|
||||
LeftJoin(dao.WalletAccount.Table()+" w", col("w", wc.MemberId)+"="+col("o", oc.MemberId)+" AND "+col("w", wc.Currency)+"='CNY'").
|
||||
LeftJoin(dao.ApprovalRequest.Table()+" a", col("a", ac.Id)+"="+col("o", oc.ApprovalRequestId)).
|
||||
Fields(col("o", oc.Id), col("o", oc.OrderNo), col("o", oc.MemberId), col("m", mc.Username), col("m", mc.MobileMask), col("p", pc.RealNameMask), col("m", mc.AutoDispatch), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)), fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), todayCount, col("o", oc.ProductName), col("o", oc.OrderType), col("o", oc.Quantity), col("o", oc.UnitPriceAmount), col("o", oc.TotalAmount), col("o", oc.CommissionAmount), col("o", oc.Currency), col("o", oc.Message), col("o", oc.OrderStatus), col("o", oc.FreezeStatus), col("o", oc.ApprovalRequestId), fmt.Sprintf("COALESCE(%s,0) approval_status", col("a", ac.ApprovalStatus)), col("o", oc.MatchedAt), col("o", oc.CompletedAt), col("o", oc.UnfrozenAt), col("o", oc.CreatedAt))
|
||||
Fields(col("o", oc.Id), col("o", oc.OrderNo), col("o", oc.MemberId), col("m", mc.Username), col("m", mc.Mobile), col("p", pc.RealNameMask), col("m", mc.AutoDispatch), fmt.Sprintf("COALESCE(%s,0) available_amount", col("w", wc.AvailableAmount)), fmt.Sprintf("COALESCE(%s,0) frozen_amount", col("w", wc.FrozenAmount)), todayCount, canReview, col("o", oc.ProductName), col("o", oc.OrderType), col("o", oc.Quantity), col("o", oc.UnitPriceAmount), col("o", oc.TotalAmount), col("o", oc.CommissionAmount), col("o", oc.Currency), col("o", oc.Message), col("o", oc.OrderStatus), col("o", oc.FreezeStatus), col("o", oc.ApprovalRequestId), fmt.Sprintf("COALESCE(%s,0) approval_status", col("a", ac.ApprovalStatus)), col("o", oc.MatchedAt), col("o", oc.CompletedAt), col("o", oc.UnfrozenAt), col("o", oc.CreatedAt))
|
||||
}
|
||||
|
||||
func (s *sSysTradeOrder) List(ctx context.Context, in *sysin.TradeOrderListInp) (list []*sysin.TradeOrderListModel, total int, err error) {
|
||||
@@ -50,11 +51,7 @@ func (s *sSysTradeOrder) List(ctx context.Context, in *sysin.TradeOrderListInp)
|
||||
mod = mod.WhereLike(col("m", mc.Username), "%"+in.Username+"%")
|
||||
}
|
||||
if in.Mobile != "" {
|
||||
hash, e := encrypt.HashField(ctx, in.Mobile)
|
||||
if e != nil {
|
||||
return nil, 0, e
|
||||
}
|
||||
mod = mod.Where(col("m", mc.MobileHash), hash)
|
||||
mod = mod.Where(col("m", mc.Mobile), in.Mobile)
|
||||
}
|
||||
if in.OrderStatus > 0 {
|
||||
mod = mod.Where(col("o", oc.OrderStatus), in.OrderStatus)
|
||||
@@ -97,6 +94,7 @@ func (s *sSysTradeOrder) MatchProducts(ctx context.Context, in *sysin.TradeOrder
|
||||
mod := handler.Model(dao.Product.Ctx(ctx), &handler.Option{FilterTenant: true}).
|
||||
Fields(pc.Id, pc.Name, pc.CoverUrl, pc.PriceAmount).
|
||||
Where(pc.PublishStatus, consts.BizPublishPublished).
|
||||
Where(pc.Currency, "CNY").
|
||||
WhereNull(pc.DeletedAt).
|
||||
Where("("+pc.StartAt+" IS NULL OR "+pc.StartAt+" <= ?)", gtime.Now()).
|
||||
Where("("+pc.EndAt+" IS NULL OR "+pc.EndAt+" >= ?)", gtime.Now())
|
||||
@@ -153,6 +151,9 @@ func (s *sSysTradeOrder) Match(ctx context.Context, in *sysin.TradeOrderMatchInp
|
||||
if product == nil {
|
||||
return gerror.New("商品不存在、已下架或无权使用")
|
||||
}
|
||||
if product.Currency != "CNY" {
|
||||
return gerror.New("任务订单只能匹配 CNY 商品")
|
||||
}
|
||||
now := gtime.Now()
|
||||
if product.StartAt != nil && product.StartAt.After(now) || product.EndAt != nil && product.EndAt.Before(now) {
|
||||
return gerror.New("商品当前不在可匹配时间范围内")
|
||||
@@ -186,7 +187,38 @@ func (s *sSysTradeOrder) Match(ctx context.Context, in *sysin.TradeOrderMatchInp
|
||||
return gerror.New("会员等级生效版本不存在")
|
||||
}
|
||||
totalAmount := product.PriceAmount * int64(in.Quantity)
|
||||
wc := dao.WalletAccount.Columns()
|
||||
var account *entity.WalletAccount
|
||||
if err := handler.Model(dao.WalletAccount.Ctx(ctx), &handler.Option{FilterTenant: true}).
|
||||
Where(wc.MemberId, order.MemberId).Where(wc.Currency, product.Currency).LockUpdate().Scan(&account); err != nil {
|
||||
return gerror.Wrap(err, "读取会员钱包失败")
|
||||
}
|
||||
if account == nil || account.AccountStatus != 1 {
|
||||
return gerror.New("会员钱包不存在或状态异常")
|
||||
}
|
||||
if totalAmount > account.AvailableAmount {
|
||||
return gerror.New("匹配商品总额不能超过会员可用余额")
|
||||
}
|
||||
commissionAmount := totalAmount * int64(levelVersion.CommissionRate) / 10000
|
||||
afterAvailable := account.AvailableAmount - totalAmount
|
||||
afterFrozen := account.FrozenAmount + totalAmount
|
||||
walletResult, err := handler.Model(dao.WalletAccount.Ctx(ctx), &handler.Option{FilterTenant: true}).
|
||||
WherePri(account.Id).Where(wc.Version, account.Version).
|
||||
Data(g.Map{wc.AvailableAmount: afterAvailable, wc.FrozenAmount: afterFrozen, wc.Version: gdb.Raw(wc.Version + "+1")}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "扣除订单本金失败")
|
||||
}
|
||||
if affected, _ := walletResult.RowsAffected(); affected != 1 {
|
||||
return gerror.New("会员余额已变化,请刷新后重试")
|
||||
}
|
||||
if err = insertTradeMatchLedger(ctx, order, account, fmt.Sprintf("WL%dMA", order.Id), "available", consts.WalletDirectionExpense,
|
||||
totalAmount, account.AvailableAmount, afterAvailable, "match:available", "人工匹配扣除可用余额"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = insertTradeMatchLedger(ctx, order, account, fmt.Sprintf("WL%dMF", order.Id), "frozen", consts.WalletDirectionIncome,
|
||||
totalAmount, account.FrozenAmount, afterFrozen, "match:frozen", "人工匹配冻结订单本金"); err != nil {
|
||||
return err
|
||||
}
|
||||
snapshot := gjson.New(g.Map{
|
||||
"source": "admin_manual_match", "productId": product.Id, "productNo": product.ProductNo,
|
||||
"unitPriceAmount": product.PriceAmount, "quantity": in.Quantity,
|
||||
@@ -195,10 +227,10 @@ func (s *sSysTradeOrder) Match(ctx context.Context, in *sysin.TradeOrderMatchInp
|
||||
})
|
||||
oc := dao.TradeOrder.Columns()
|
||||
data := g.Map{
|
||||
oc.ProductId: product.Id, oc.ProductName: product.Name, oc.OrderType: consts.TradeOrderTypeManual,
|
||||
oc.ProductId: product.Id, oc.ProductName: product.Name,
|
||||
oc.Quantity: in.Quantity, oc.UnitPriceAmount: product.PriceAmount, oc.TotalAmount: totalAmount,
|
||||
oc.CommissionAmount: commissionAmount, oc.Currency: product.Currency, oc.MemberLevelId: levelId,
|
||||
oc.RuleSnapshot: snapshot, oc.Message: "后台人工匹配商品", oc.OrderStatus: consts.TradeOrderPending,
|
||||
oc.RuleSnapshot: snapshot, oc.Message: "后台人工匹配商品,订单本金已扣除", oc.OrderStatus: consts.TradeOrderPending,
|
||||
oc.FreezeStatus: consts.TradeFreezeNormal, oc.MatchedAt: now, oc.UpdatedBy: contexts.GetUserId(ctx),
|
||||
}
|
||||
result, err := s.model(ctx).WherePri(order.Id).Where(oc.OrderStatus, consts.TradeOrderMatching).Data(data).Update()
|
||||
@@ -217,6 +249,24 @@ func (s *sSysTradeOrder) Match(ctx context.Context, in *sysin.TradeOrderMatchInp
|
||||
})
|
||||
}
|
||||
|
||||
func insertTradeMatchLedger(ctx context.Context, order *entity.TradeOrder, account *entity.WalletAccount,
|
||||
ledgerNo, entryType string, direction int, amount, beforeAmount, afterAmount int64, suffix, remark string) error {
|
||||
lc := dao.WalletLedger.Columns()
|
||||
_, 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: order.MemberId,
|
||||
lc.BizType: "task_principal_hold", lc.BizNo: order.OrderNo, lc.EntryType: entryType, lc.Direction: direction,
|
||||
lc.Amount: amount, lc.BeforeAmount: beforeAmount, lc.AfterAmount: afterAmount, lc.Currency: account.Currency,
|
||||
lc.IdempotencyKey: fmt.Sprintf("task:%d:%s", order.Id, suffix), lc.Remark: remark,
|
||||
lc.OperatorId: contexts.GetUserId(ctx), lc.OccurredAt: gtime.Now()}).Insert()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "记录订单扣款流水失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sSysTradeOrder) Apply(ctx context.Context, in *sysin.TradeOrderApplyInp) (res *sysin.FinanceApprovalModel, err error) {
|
||||
var order *entity.TradeOrder
|
||||
if err = s.model(ctx).WherePri(in.Id).Scan(&order); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user