739 lines
38 KiB
Go
739 lines
38 KiB
Go
package sys
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"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"
|
|
|
|
"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"
|
|
)
|
|
|
|
type sSysTradeOrder struct{}
|
|
|
|
func NewSysTradeOrder() *sSysTradeOrder { return &sSysTradeOrder{} }
|
|
func init() { service.RegisterSysTradeOrder(NewSysTradeOrder()) }
|
|
func (s *sSysTradeOrder) model(ctx context.Context) *gdb.Model {
|
|
return handler.Model(dao.TradeOrder.Ctx(ctx), &handler.Option{FilterTenant: true})
|
|
}
|
|
|
|
func (s *sSysTradeOrder) listModel(ctx context.Context) *gdb.Model {
|
|
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)
|
|
dispatchMode := fmt.Sprintf("CASE COALESCE(JSON_UNQUOTE(JSON_EXTRACT(%s,'$.dispatch_mode')),'') WHEN 'auto' THEN %d WHEN 'manual' THEN %d ELSE %s END auto_dispatch", col("o", oc.RuleSnapshot), consts.TradeAutoDispatchEnabled, consts.TradeAutoDispatchDisabled, col("m", mc.AutoDispatch))
|
|
paymentStatus := fmt.Sprintf("COALESCE(JSON_UNQUOTE(JSON_EXTRACT(%s,'$.manual_payment_status')),'') payment_status", col("o", oc.RuleSnapshot))
|
|
scheduledMatchAt := fmt.Sprintf("COALESCE(JSON_UNQUOTE(JSON_EXTRACT(%s,'$.scheduled_match_at')),'') scheduled_match_at", col("o", oc.RuleSnapshot))
|
|
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.Mobile), col("p", pc.RealNameMask), dispatchMode, paymentStatus, scheduledMatchAt, 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) {
|
|
oc, mc := dao.TradeOrder.Columns(), dao.AppMember.Columns()
|
|
mod := s.listModel(ctx)
|
|
if in.OrderNo != "" {
|
|
mod = mod.WhereLike(col("o", oc.OrderNo), "%"+in.OrderNo+"%")
|
|
}
|
|
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.AutoDispatch == consts.TradeAutoDispatchEnabled {
|
|
mod = mod.Where("COALESCE(JSON_UNQUOTE(JSON_EXTRACT("+col("o", oc.RuleSnapshot)+",'$.dispatch_mode')),'') = ?", "auto")
|
|
}
|
|
if in.AutoDispatch == consts.TradeAutoDispatchDisabled {
|
|
mod = mod.Where("COALESCE(JSON_UNQUOTE(JSON_EXTRACT("+col("o", oc.RuleSnapshot)+",'$.dispatch_mode')),'') = ?", "manual")
|
|
}
|
|
if in.OrderStatus > 0 {
|
|
mod = mod.Where(col("o", oc.OrderStatus), in.OrderStatus)
|
|
}
|
|
if len(in.CreatedAt) == 2 {
|
|
mod = mod.WhereBetween(col("o", oc.CreatedAt), in.CreatedAt[0], in.CreatedAt[1])
|
|
}
|
|
matchFirst := fmt.Sprintf("CASE WHEN %s=%d THEN 0 ELSE 1 END", col("o", oc.OrderStatus), consts.TradeOrderMatching)
|
|
err = mod.Page(in.Page, in.PerPage).OrderAsc(matchFirst).OrderDesc(col("o", oc.Id)).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取交易订单失败")
|
|
return
|
|
}
|
|
for _, row := range list {
|
|
row.CanMatch = canAdminMatchOrder(row.AutoDispatch, row.OrderStatus, row.ApprovalRequestId, row.ScheduledMatchAt, time.Now())
|
|
manualPending := row.AutoDispatch == consts.TradeAutoDispatchDisabled && row.OrderStatus == consts.TradeOrderPending && row.FreezeStatus == consts.TradeFreezeNormal
|
|
row.CanChangeAmount = manualPending
|
|
row.CanForcePay = manualPending
|
|
row.CanFreeze = manualPending
|
|
row.CanApply = row.ApprovalRequestId == 0 && row.OrderStatus != consts.TradeOrderCompleted && row.OrderStatus != consts.TradeOrderCancelled
|
|
row.CanExecute = row.ApprovalRequestId > 0 && row.ApprovalStatus == consts.ApprovalStatusApproved
|
|
}
|
|
return
|
|
}
|
|
|
|
func canAdminMatchOrder(dispatchMode, orderStatus int, approvalRequestId uint64, scheduledMatchAt string, now time.Time) bool {
|
|
if orderStatus != consts.TradeOrderMatching || approvalRequestId > 0 {
|
|
return false
|
|
}
|
|
if dispatchMode != consts.TradeAutoDispatchEnabled || scheduledMatchAt == "" {
|
|
return true
|
|
}
|
|
matchAt, err := time.Parse(time.RFC3339, scheduledMatchAt)
|
|
return err == nil && now.UTC().Before(matchAt.UTC())
|
|
}
|
|
|
|
func (s *sSysTradeOrder) View(ctx context.Context, in *sysin.TradeOrderViewInp) (res *sysin.TradeOrderViewModel, err error) {
|
|
var base *sysin.TradeOrderListModel
|
|
if err = s.listModel(ctx).Where(col("o", dao.TradeOrder.Columns().Id), in.Id).Scan(&base); err != nil {
|
|
return nil, gerror.Wrap(err, "获取订单详情失败")
|
|
}
|
|
if base == nil {
|
|
return nil, gerror.New("交易订单不存在")
|
|
}
|
|
var order *entity.TradeOrder
|
|
if err = s.model(ctx).WherePri(in.Id).Scan(&order); err != nil {
|
|
return nil, err
|
|
}
|
|
res = &sysin.TradeOrderViewModel{TradeOrderListModel: *base, TaskId: order.TaskId, ProductId: order.ProductId, MemberLevelId: order.MemberLevelId, RuleSnapshot: order.RuleSnapshot, UpdatedAt: order.UpdatedAt}
|
|
res.Timeline = []*sysin.TradeOrderTimelineModel{{Title: "订单创建", Time: order.CreatedAt}}
|
|
if order.MatchedAt != nil {
|
|
res.Timeline = append(res.Timeline, &sysin.TradeOrderTimelineModel{Title: "订单匹配", Time: order.MatchedAt})
|
|
}
|
|
if order.CompletedAt != nil {
|
|
res.Timeline = append(res.Timeline, &sysin.TradeOrderTimelineModel{Title: "订单完成", Time: order.CompletedAt})
|
|
}
|
|
if order.UnfrozenAt != nil {
|
|
res.Timeline = append(res.Timeline, &sysin.TradeOrderTimelineModel{Title: "订单解冻", Time: order.UnfrozenAt})
|
|
}
|
|
lc := dao.WalletLedger.Columns()
|
|
if err = handler.Model(dao.WalletLedger.Ctx(ctx), &handler.Option{FilterTenant: true}).Fields(lc.LedgerNo, lc.BizType, lc.Direction, lc.Amount, lc.BeforeAmount, lc.AfterAmount, lc.Remark, lc.OccurredAt).Where(lc.BizNo, order.OrderNo).OrderAsc(lc.Id).Scan(&res.Ledgers); err != nil {
|
|
return nil, gerror.Wrap(err, "获取订单资金流水失败")
|
|
}
|
|
if order.ApprovalRequestId > 0 {
|
|
ac := dao.ApprovalAction.Columns()
|
|
if err = handler.Model(dao.ApprovalAction.Ctx(ctx), &handler.Option{FilterTenant: true}).Fields(ac.StepNo, ac.ActorId, ac.Action, ac.Comment, ac.CreatedAt).Where(ac.ApprovalRequestId, order.ApprovalRequestId).OrderAsc(ac.StepNo).OrderAsc(ac.Id).Scan(&res.ApprovalActions); err != nil {
|
|
return nil, gerror.Wrap(err, "获取订单审批记录失败")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysTradeOrder) MatchProducts(ctx context.Context, in *sysin.TradeOrderMatchProductListInp) (list []*sysin.TradeOrderMatchProductModel, total int, err error) {
|
|
pc := dao.Product.Columns()
|
|
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())
|
|
if in.Name != "" {
|
|
mod = mod.WhereLike(pc.Name, "%"+in.Name+"%")
|
|
}
|
|
if in.PriceMin != "" {
|
|
amount, parseErr := parseCents(in.PriceMin)
|
|
if parseErr != nil {
|
|
return nil, 0, gerror.New("最低价格格式不正确")
|
|
}
|
|
mod = mod.WhereGTE(pc.PriceAmount, amount)
|
|
}
|
|
if in.PriceMax != "" {
|
|
amount, parseErr := parseCents(in.PriceMax)
|
|
if parseErr != nil {
|
|
return nil, 0, gerror.New("最高价格格式不正确")
|
|
}
|
|
mod = mod.WhereLTE(pc.PriceAmount, amount)
|
|
}
|
|
if in.PriceMin != "" && in.PriceMax != "" {
|
|
minAmount, _ := parseCents(in.PriceMin)
|
|
maxAmount, _ := parseCents(in.PriceMax)
|
|
if minAmount > maxAmount {
|
|
return nil, 0, gerror.New("最低价格不能高于最高价格")
|
|
}
|
|
}
|
|
err = mod.Page(in.Page, in.PerPage).OrderDesc(pc.PriceAmount).OrderDesc(pc.Id).ScanAndCount(&list, &total, false)
|
|
if err != nil {
|
|
err = gerror.Wrap(err, "获取可匹配商品失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysTradeOrder) Match(ctx context.Context, in *sysin.TradeOrderMatchInp) error {
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var order *entity.TradeOrder
|
|
if err := s.model(ctx).WherePri(in.Id).LockUpdate().Scan(&order); err != nil {
|
|
return gerror.Wrap(err, "读取待匹配订单失败")
|
|
}
|
|
if order == nil {
|
|
return gerror.New("交易订单不存在")
|
|
}
|
|
if order.OrderStatus != consts.TradeOrderMatching || order.ApprovalRequestId > 0 {
|
|
return gerror.New("当前订单状态不允许匹配商品")
|
|
}
|
|
dispatchMode := ""
|
|
if order.RuleSnapshot != nil {
|
|
dispatchMode = order.RuleSnapshot.Get("dispatch_mode").String()
|
|
}
|
|
if dispatchMode == "" {
|
|
var member *entity.AppMember
|
|
if err := handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true}).Fields(dao.AppMember.Columns().AutoDispatch).WherePri(order.MemberId).Scan(&member); err != nil {
|
|
return gerror.Wrap(err, "读取会员派单模式失败")
|
|
}
|
|
if member != nil && member.AutoDispatch == consts.TradeAutoDispatchDisabled {
|
|
dispatchMode = "manual"
|
|
}
|
|
}
|
|
if dispatchMode == "" {
|
|
dispatchMode = "auto"
|
|
}
|
|
if dispatchMode == "auto" && order.RuleSnapshot != nil {
|
|
scheduledMatchAt := order.RuleSnapshot.Get("scheduled_match_at").String()
|
|
if scheduledMatchAt != "" {
|
|
matchAt, parseErr := time.Parse(time.RFC3339, scheduledMatchAt)
|
|
if parseErr != nil {
|
|
return gerror.New("自动派单时间格式不正确,请刷新订单后重试")
|
|
}
|
|
if !time.Now().UTC().Before(matchAt.UTC()) {
|
|
return gerror.New("自动派单已到期,订单正在由系统自动匹配")
|
|
}
|
|
}
|
|
}
|
|
var product *entity.Product
|
|
pc := dao.Product.Columns()
|
|
if err := handler.Model(dao.Product.Ctx(ctx), &handler.Option{FilterTenant: true}).
|
|
WherePri(in.ProductId).Where(pc.PublishStatus, consts.BizPublishPublished).WhereNull(pc.DeletedAt).
|
|
LockUpdate().Scan(&product); err != nil {
|
|
return gerror.Wrap(err, "读取匹配商品失败")
|
|
}
|
|
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("商品当前不在可匹配时间范围内")
|
|
}
|
|
if in.Quantity == 0 || product.PriceAmount <= 0 || product.PriceAmount > math.MaxInt64/int64(in.Quantity) {
|
|
return gerror.New("商品价格或数量不合法")
|
|
}
|
|
levelId := order.MemberLevelId
|
|
if levelId == 0 {
|
|
var member *entity.AppMember
|
|
if err := handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true}).Fields(dao.AppMember.Columns().LevelId).WherePri(order.MemberId).Scan(&member); err != nil {
|
|
return gerror.Wrap(err, "读取会员等级失败")
|
|
}
|
|
if member == nil {
|
|
return gerror.New("订单会员不存在")
|
|
}
|
|
levelId = member.LevelId
|
|
}
|
|
var level *entity.AppMemberLevel
|
|
if err := handler.Model(dao.AppMemberLevel.Ctx(ctx), &handler.Option{FilterTenant: true}).WherePri(levelId).Scan(&level); err != nil {
|
|
return gerror.Wrap(err, "读取会员等级规则失败")
|
|
}
|
|
if level == nil || level.CurrentVersionId == 0 {
|
|
return gerror.New("会员等级尚未配置生效规则")
|
|
}
|
|
var levelVersion *entity.AppMemberLevelVersion
|
|
if err := handler.Model(dao.AppMemberLevelVersion.Ctx(ctx), &handler.Option{FilterTenant: true}).WherePri(level.CurrentVersionId).Scan(&levelVersion); err != nil {
|
|
return gerror.Wrap(err, "读取会员等级版本失败")
|
|
}
|
|
if levelVersion == nil {
|
|
return gerror.New("会员等级生效版本不存在")
|
|
}
|
|
totalAmount := product.PriceAmount * int64(in.Quantity)
|
|
if levelVersion.CommissionRate > 0 && uint64(levelVersion.CommissionRate) > uint64(math.MaxInt64/totalAmount) {
|
|
return gerror.New("佣金规则超出可计算范围")
|
|
}
|
|
commissionAmount := totalAmount * int64(levelVersion.CommissionRate) / 10000
|
|
snapshotData := g.Map{}
|
|
if order.RuleSnapshot != nil {
|
|
for key, value := range order.RuleSnapshot.Map() {
|
|
snapshotData[key] = value
|
|
}
|
|
}
|
|
snapshotData["source"], snapshotData["match_source"], snapshotData["dispatch_mode"] = "admin_manual_match", "admin_manual_match", dispatchMode
|
|
snapshotData["product_id"], snapshotData["product_no"] = product.Id, product.ProductNo
|
|
snapshotData["unit_price_amount"], snapshotData["quantity"] = product.PriceAmount, in.Quantity
|
|
snapshotData["member_level_id"], snapshotData["level_version_id"] = levelId, levelVersion.Id
|
|
snapshotData["commission_rate"], snapshotData["manual_payment_status"] = levelVersion.CommissionRate, "settled"
|
|
snapshotData["confirmation_required"], snapshotData["deadline_at"] = false, ""
|
|
snapshotData["settled_by"], snapshotData["settled_at"] = "admin", time.Now().UTC().Format(time.RFC3339)
|
|
snapshot := gjson.New(snapshotData)
|
|
commissionLedgerId, err := s.settleMatchedOrder(ctx, order, dispatchMode, totalAmount, commissionAmount, now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
oc := dao.TradeOrder.Columns()
|
|
data := g.Map{
|
|
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.TradeOrderCompleted,
|
|
oc.FreezeStatus: consts.TradeFreezeNormal, oc.MatchedAt: now, oc.CompletedAt: now, oc.UpdatedBy: contexts.GetUserId(ctx),
|
|
}
|
|
result, err := s.model(ctx).WherePri(order.Id).Where(oc.OrderStatus, consts.TradeOrderMatching).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "匹配订单失败")
|
|
}
|
|
rows, _ := result.RowsAffected()
|
|
if rows != 1 {
|
|
return gerror.New("订单状态已变化,请刷新后重试")
|
|
}
|
|
if err = s.insertMatchedSubmission(ctx, order, levelId, commissionAmount, commissionLedgerId, now, dispatchMode); err != nil {
|
|
return err
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{
|
|
Module: "trade", Action: "trade_order.match", TargetType: "trade_order", TargetId: order.Id,
|
|
Before: order, After: data, ChangeJson: gjson.New(g.Map{"productId": product.Id, "quantity": in.Quantity, "totalAmount": totalAmount}),
|
|
Reason: "后台人工匹配商品",
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *sSysTradeOrder) settleMatchedOrder(ctx context.Context, order *entity.TradeOrder, dispatchMode string, totalAmount, commissionAmount int64, now *gtime.Time) (uint64, error) {
|
|
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, "CNY").LockUpdate().Scan(&account); err != nil {
|
|
return 0, gerror.Wrap(err, "读取会员钱包失败")
|
|
}
|
|
if account == nil || account.AccountStatus != 1 {
|
|
return 0, gerror.New("会员钱包不存在或状态异常")
|
|
}
|
|
if totalAmount <= 0 || account.AvailableAmount < math.MinInt64+totalAmount {
|
|
return 0, gerror.New("订单金额超出钱包处理范围")
|
|
}
|
|
if commissionAmount < 0 || account.AvailableAmount > math.MaxInt64-commissionAmount {
|
|
return 0, gerror.New("订单佣金超出钱包处理范围")
|
|
}
|
|
if dispatchMode == "auto" && account.AvailableAmount < totalAmount {
|
|
return 0, gerror.New("自动派单商品金额不能超过会员可用余额")
|
|
}
|
|
|
|
principalAfter := account.AvailableAmount - totalAmount
|
|
settledAfter := principalAfter + commissionAmount
|
|
if dispatchMode == "auto" {
|
|
settledAfter = account.AvailableAmount + commissionAmount
|
|
if account.FrozenAmount > math.MaxInt64-totalAmount {
|
|
return 0, gerror.New("订单金额超出钱包冻结范围")
|
|
}
|
|
}
|
|
accountData := g.Map{
|
|
wc.AvailableAmount: settledAfter,
|
|
wc.TotalIncome: gdb.Raw(wc.TotalIncome + "+" + fmt.Sprint(commissionAmount)),
|
|
wc.Version: gdb.Raw(wc.Version + "+1"),
|
|
}
|
|
if dispatchMode == "manual" {
|
|
accountData[wc.TotalExpense] = gdb.Raw(wc.TotalExpense + "+" + fmt.Sprint(totalAmount))
|
|
}
|
|
result, err := handler.Model(dao.WalletAccount.Ctx(ctx), &handler.Option{FilterTenant: true}).WherePri(account.Id).Where(wc.Version, account.Version).Data(accountData).Update()
|
|
if err != nil {
|
|
return 0, gerror.Wrap(err, "结算订单余额失败")
|
|
}
|
|
if affected, _ := result.RowsAffected(); affected != 1 {
|
|
return 0, gerror.New("会员余额已变化,请刷新后重试")
|
|
}
|
|
|
|
if dispatchMode == "auto" {
|
|
holdFrozen := account.FrozenAmount + totalAmount
|
|
entries := []struct {
|
|
code, bizType, entryType string
|
|
direction int
|
|
before, after int64
|
|
suffix, remark string
|
|
}{
|
|
{"AA", "task_principal_hold", "available", consts.WalletDirectionExpense, account.AvailableAmount, principalAfter, "admin-auto:available", "人工提前匹配自动单,扣除可用余额"},
|
|
{"AF", "task_principal_hold", "frozen", consts.WalletDirectionIncome, account.FrozenAmount, holdFrozen, "admin-auto:frozen", "人工提前匹配自动单,冻结订单本金"},
|
|
{"RF", "task_principal_release", "frozen", consts.WalletDirectionExpense, holdFrozen, account.FrozenAmount, "admin-auto:release-frozen", "自动单完成释放冻结本金"},
|
|
{"RA", "task_principal_return", "available", consts.WalletDirectionIncome, principalAfter, account.AvailableAmount, "admin-auto:return-available", "自动单完成返回本金"},
|
|
}
|
|
for _, entry := range entries {
|
|
if _, err = insertMatchedOrderLedger(ctx, order, account, "WL"+fmt.Sprint(order.Id)+entry.code, entry.bizType, order.OrderNo, entry.entryType, entry.direction, totalAmount, entry.before, entry.after, entry.suffix, entry.remark, now); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
} else {
|
|
if _, err = insertMatchedOrderLedger(ctx, order, account, fmt.Sprintf("WL%dMP", order.Id), "task_principal_expense", order.OrderNo, "available", consts.WalletDirectionExpense, totalAmount, account.AvailableAmount, principalAfter, "admin-manual:principal", "手动单匹配完成扣除订单金额(允许负余额)", now); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if commissionAmount == 0 {
|
|
return 0, nil
|
|
}
|
|
commissionBefore := principalAfter
|
|
if dispatchMode == "auto" {
|
|
commissionBefore = account.AvailableAmount
|
|
}
|
|
return insertMatchedOrderLedger(ctx, order, account, fmt.Sprintf("WL%dMC", order.Id), "commission", fmt.Sprintf("TSADMIN%d", order.Id), "available", consts.WalletDirectionIncome, commissionAmount, commissionBefore, settledAfter, "admin-match:commission", "后台匹配订单完成返佣", now)
|
|
}
|
|
|
|
func (s *sSysTradeOrder) insertMatchedSubmission(ctx context.Context, order *entity.TradeOrder, levelId uint64, commissionAmount int64, ledgerId uint64, now *gtime.Time, dispatchMode string) error {
|
|
sc := dao.TaskSubmission.Columns()
|
|
uid := contexts.GetUserId(ctx)
|
|
reason := "手动派单由后台匹配并直接完成"
|
|
if dispatchMode == "auto" {
|
|
reason = "自动派单到期前由后台匹配并直接完成"
|
|
}
|
|
_, err := dao.TaskSubmission.Ctx(ctx).
|
|
Fields(sc.SubmissionNo, sc.TradeOrderId, sc.TaskId, sc.MemberId, sc.MemberLevelId, sc.EvidenceText, sc.EvidenceAttachmentIds,
|
|
sc.SubmissionStatus, sc.CommissionAmount, sc.SuccessRate, sc.ReviewedBy, sc.ReviewedAt, sc.ReviewReasonCode,
|
|
sc.ReviewReason, sc.AllowResubmit, sc.LedgerId, sc.SettledAt, sc.CreatedBy, sc.UpdatedBy).
|
|
Hook(hook.SaveTenant).Data(g.Map{
|
|
sc.SubmissionNo: fmt.Sprintf("TSADMIN%d", order.Id), sc.TradeOrderId: order.Id, sc.TaskId: order.TaskId,
|
|
sc.MemberId: order.MemberId, sc.MemberLevelId: levelId, sc.EvidenceText: "", sc.EvidenceAttachmentIds: gjson.New(g.Array{}),
|
|
sc.SubmissionStatus: consts.TaskSubmissionSettled, sc.CommissionAmount: commissionAmount, sc.SuccessRate: 10000,
|
|
sc.ReviewedBy: uid, sc.ReviewedAt: now, sc.ReviewReasonCode: "ADMIN_MATCHED", sc.ReviewReason: reason,
|
|
sc.AllowResubmit: consts.StatusDisable, sc.LedgerId: ledgerId, sc.SettledAt: now, sc.CreatedBy: uid, sc.UpdatedBy: uid,
|
|
}).Insert()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "创建已结算任务记录失败")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *sSysTradeOrder) UpdateAmount(ctx context.Context, in *sysin.TradeOrderUpdateAmountInp) error {
|
|
amount, err := parseCents(in.Amount)
|
|
if err != nil || amount <= 0 || amount < int64(in.Quantity) {
|
|
return gerror.New("更改金额不合法")
|
|
}
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var order *entity.TradeOrder
|
|
if err := s.model(ctx).WherePri(in.Id).LockUpdate().Scan(&order); err != nil {
|
|
return gerror.Wrap(err, "读取手动订单失败")
|
|
}
|
|
if err := validateManualPaymentOrder(order, false); err != nil {
|
|
return err
|
|
}
|
|
commissionRate := order.RuleSnapshot.Get("commission_rate").Int64()
|
|
commissionAmount := amount * commissionRate / 10000
|
|
if order.RuleSnapshot.Get("manual_payment_status").String() == "paid" && amount != order.TotalAmount {
|
|
if err := s.adjustPaidManualAmount(ctx, order, amount); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
snapshot := cloneOrderSnapshot(order)
|
|
snapshot["unit_price_amount"] = amount / int64(in.Quantity)
|
|
snapshot["quantity"] = in.Quantity
|
|
snapshot["manual_order_amount"] = amount
|
|
snapshot["amount_updated_at"] = time.Now().UTC().Format(time.RFC3339)
|
|
oc := dao.TradeOrder.Columns()
|
|
data := g.Map{oc.Quantity: in.Quantity, oc.UnitPriceAmount: amount / int64(in.Quantity), oc.TotalAmount: amount,
|
|
oc.CommissionAmount: commissionAmount, oc.RuleSnapshot: gjson.New(snapshot), oc.Message: "后台已更改订单金额,等待会员确认付款", oc.UpdatedBy: contexts.GetUserId(ctx)}
|
|
result, err := s.model(ctx).WherePri(order.Id).Where(oc.OrderStatus, consts.TradeOrderPending).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更改订单金额失败")
|
|
}
|
|
if affected, _ := result.RowsAffected(); affected != 1 {
|
|
return gerror.New("订单状态已变化,请刷新后重试")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "trade", Action: "trade_order.update_amount",
|
|
TargetType: "trade_order", TargetId: order.Id, Before: order, After: data,
|
|
ChangeJson: gjson.New(g.Map{"amount": amount, "quantity": in.Quantity}), Reason: "后台更改手动订单金额"})
|
|
})
|
|
}
|
|
|
|
func (s *sSysTradeOrder) ForcePay(ctx context.Context, in *sysin.TradeOrderManualActionInp) error {
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var order *entity.TradeOrder
|
|
if err := s.model(ctx).WherePri(in.Id).LockUpdate().Scan(&order); err != nil {
|
|
return gerror.Wrap(err, "读取手动订单失败")
|
|
}
|
|
if err := validateManualPaymentOrder(order, false); err != nil {
|
|
return err
|
|
}
|
|
if order.RuleSnapshot.Get("manual_payment_status").String() == "paid" {
|
|
return nil
|
|
}
|
|
if order.TotalAmount <= 0 {
|
|
return gerror.New("订单金额不合法")
|
|
}
|
|
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, order.Currency).LockUpdate().Scan(&account); err != nil {
|
|
return gerror.Wrap(err, "读取会员钱包失败")
|
|
}
|
|
if account == nil || account.AccountStatus != 1 {
|
|
return gerror.New("会员钱包不存在或状态异常")
|
|
}
|
|
if account.AvailableAmount < math.MinInt64+order.TotalAmount || account.FrozenAmount > math.MaxInt64-order.TotalAmount {
|
|
return gerror.New("订单金额超出钱包处理范围")
|
|
}
|
|
afterAvailable, afterFrozen := account.AvailableAmount-order.TotalAmount, account.FrozenAmount+order.TotalAmount
|
|
result, 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, _ := result.RowsAffected(); affected != 1 {
|
|
return gerror.New("会员余额已变化,请刷新后重试")
|
|
}
|
|
if err = insertTradeMatchLedger(ctx, order, account, fmt.Sprintf("WL%dMA", order.Id), "available", consts.WalletDirectionExpense,
|
|
order.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,
|
|
order.TotalAmount, account.FrozenAmount, afterFrozen, "match:frozen", "手动订单强制付款冻结本金"); err != nil {
|
|
return err
|
|
}
|
|
snapshot := cloneOrderSnapshot(order)
|
|
snapshot["manual_payment_status"], snapshot["payment_confirmed_by"] = "paid", "admin"
|
|
snapshot["payment_confirmed_at"] = time.Now().UTC().Format(time.RFC3339)
|
|
oc := dao.TradeOrder.Columns()
|
|
data := g.Map{oc.RuleSnapshot: gjson.New(snapshot), oc.Message: "后台已强制付款,等待会员提交任务", oc.UpdatedBy: contexts.GetUserId(ctx)}
|
|
result, err = s.model(ctx).WherePri(order.Id).Where(oc.OrderStatus, consts.TradeOrderPending).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "更新订单付款状态失败")
|
|
}
|
|
if affected, _ := result.RowsAffected(); affected != 1 {
|
|
return gerror.New("订单状态已变化,请刷新后重试")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "trade", Action: "trade_order.force_pay",
|
|
TargetType: "trade_order", TargetId: order.Id, Before: order, After: data, Reason: "后台强制支付手动订单"})
|
|
})
|
|
}
|
|
|
|
func (s *sSysTradeOrder) adjustPaidManualAmount(ctx context.Context, order *entity.TradeOrder, amount int64) error {
|
|
delta := amount - order.TotalAmount
|
|
if delta == 0 {
|
|
return nil
|
|
}
|
|
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, order.Currency).LockUpdate().Scan(&account); err != nil {
|
|
return gerror.Wrap(err, "读取会员钱包失败")
|
|
}
|
|
if account == nil || account.AccountStatus != 1 {
|
|
return gerror.New("会员钱包不存在或状态异常")
|
|
}
|
|
if delta > 0 && (account.AvailableAmount < math.MinInt64+delta || account.FrozenAmount > math.MaxInt64-delta) {
|
|
return gerror.New("更改金额超出钱包处理范围")
|
|
}
|
|
if delta < 0 {
|
|
refund := -delta
|
|
if account.FrozenAmount < refund || account.AvailableAmount > math.MaxInt64-refund {
|
|
return gerror.New("已冻结本金不足,无法减少订单金额")
|
|
}
|
|
}
|
|
afterAvailable, afterFrozen := account.AvailableAmount-delta, account.FrozenAmount+delta
|
|
result, 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, _ := result.RowsAffected(); affected != 1 {
|
|
return gerror.New("会员余额已变化,请刷新后重试")
|
|
}
|
|
tag := fmt.Sprintf("%x", time.Now().UnixNano())
|
|
ledgerAmount := delta
|
|
availableDirection, frozenDirection := consts.WalletDirectionExpense, consts.WalletDirectionIncome
|
|
if ledgerAmount < 0 {
|
|
ledgerAmount = -ledgerAmount
|
|
availableDirection, frozenDirection = consts.WalletDirectionIncome, consts.WalletDirectionExpense
|
|
}
|
|
if err = insertTradeMatchLedger(ctx, order, account, fmt.Sprintf("WL%dUA%s", order.Id, tag), "available", availableDirection,
|
|
ledgerAmount, account.AvailableAmount, afterAvailable, "amount:update:"+tag+":available", "手动订单更改金额调整可用余额"); err != nil {
|
|
return err
|
|
}
|
|
if err = insertTradeMatchLedger(ctx, order, account, fmt.Sprintf("WL%dUF%s", order.Id, tag), "frozen", frozenDirection,
|
|
ledgerAmount, account.FrozenAmount, afterFrozen, "amount:update:"+tag+":frozen", "手动订单更改金额调整冻结本金"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *sSysTradeOrder) Freeze(ctx context.Context, in *sysin.TradeOrderManualActionInp) error {
|
|
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
var order *entity.TradeOrder
|
|
if err := s.model(ctx).WherePri(in.Id).LockUpdate().Scan(&order); err != nil {
|
|
return gerror.Wrap(err, "读取手动订单失败")
|
|
}
|
|
if err := validateManualPaymentOrder(order, false); err != nil {
|
|
return err
|
|
}
|
|
snapshot := cloneOrderSnapshot(order)
|
|
snapshot["frozen_by"], snapshot["frozen_at"] = "admin", time.Now().UTC().Format(time.RFC3339)
|
|
oc := dao.TradeOrder.Columns()
|
|
data := g.Map{oc.OrderStatus: consts.TradeOrderFrozen, oc.FreezeStatus: consts.TradeFreezeFrozen,
|
|
oc.RuleSnapshot: gjson.New(snapshot), oc.Message: "订单已被后台冻结,请联系客服", oc.UpdatedBy: contexts.GetUserId(ctx)}
|
|
result, err := s.model(ctx).WherePri(order.Id).Where(oc.OrderStatus, consts.TradeOrderPending).Data(data).Update()
|
|
if err != nil {
|
|
return gerror.Wrap(err, "冻结订单失败")
|
|
}
|
|
if affected, _ := result.RowsAffected(); affected != 1 {
|
|
return gerror.New("订单状态已变化,请刷新后重试")
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "trade", Action: "trade_order.freeze",
|
|
TargetType: "trade_order", TargetId: order.Id, Before: order, After: data, Reason: "后台冻结手动订单"})
|
|
})
|
|
}
|
|
|
|
func validateManualPaymentOrder(order *entity.TradeOrder, requireUnpaid bool) error {
|
|
if order == nil {
|
|
return gerror.New("交易订单不存在")
|
|
}
|
|
if order.OrderStatus != consts.TradeOrderPending || order.FreezeStatus != consts.TradeFreezeNormal || order.RuleSnapshot == nil || order.RuleSnapshot.Get("dispatch_mode").String() != "manual" {
|
|
return gerror.New("当前订单状态不允许此操作")
|
|
}
|
|
if requireUnpaid && order.RuleSnapshot.Get("manual_payment_status").String() == "paid" {
|
|
return gerror.New("订单已经付款")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cloneOrderSnapshot(order *entity.TradeOrder) g.Map {
|
|
snapshot := g.Map{}
|
|
if order != nil && order.RuleSnapshot != nil {
|
|
for key, value := range order.RuleSnapshot.Map() {
|
|
snapshot[key] = value
|
|
}
|
|
}
|
|
return snapshot
|
|
}
|
|
|
|
func insertTradeMatchLedger(ctx context.Context, order *entity.TradeOrder, account *entity.WalletAccount,
|
|
ledgerNo, entryType string, direction int, amount, beforeAmount, afterAmount int64, suffix, remark string) error {
|
|
_, err := insertMatchedOrderLedger(ctx, order, account, ledgerNo, "task_principal_hold", order.OrderNo, entryType, direction, amount, beforeAmount, afterAmount, suffix, remark, gtime.Now())
|
|
return err
|
|
}
|
|
|
|
func insertMatchedOrderLedger(ctx context.Context, order *entity.TradeOrder, account *entity.WalletAccount,
|
|
ledgerNo, bizType, bizNo, entryType string, direction int, amount, beforeAmount, afterAmount int64, suffix, remark string, occurredAt *gtime.Time) (uint64, error) {
|
|
lc := dao.WalletLedger.Columns()
|
|
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: order.MemberId,
|
|
lc.BizType: bizType, lc.BizNo: bizNo, 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: occurredAt}).Insert()
|
|
if err != nil {
|
|
return 0, gerror.Wrap(err, "记录订单扣款流水失败")
|
|
}
|
|
ledgerId, err := result.LastInsertId()
|
|
if err != nil {
|
|
return 0, gerror.Wrap(err, "读取订单流水ID失败")
|
|
}
|
|
return uint64(ledgerId), 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 {
|
|
return
|
|
}
|
|
if order == nil {
|
|
return nil, gerror.New("交易订单不存在")
|
|
}
|
|
if order.ApprovalRequestId > 0 {
|
|
return nil, gerror.New("该订单已有待执行处理申请")
|
|
}
|
|
switch in.Action {
|
|
case consts.TradeActionCancelMatch:
|
|
if order.OrderStatus != consts.TradeOrderMatching {
|
|
return nil, gerror.New("仅匹配中订单可以取消匹配")
|
|
}
|
|
case consts.TradeActionRematch:
|
|
if order.OrderStatus != consts.TradeOrderTimeout && order.OrderStatus != consts.TradeOrderFailed {
|
|
return nil, gerror.New("仅超时或失败订单可以重新匹配")
|
|
}
|
|
case consts.TradeActionUnfreeze:
|
|
if order.OrderStatus != consts.TradeOrderFrozen && order.FreezeStatus != consts.TradeFreezeFrozen {
|
|
return nil, gerror.New("订单当前未冻结")
|
|
}
|
|
}
|
|
payload := g.Map{"id": order.Id, "action": in.Action, "reasonCode": in.ReasonCode, "reasonText": in.ReasonText}
|
|
approval, err := service.SysApproval().Create(ctx, &sysin.ApprovalCreateInp{ApprovalType: "trade_order_action", TargetType: "trade_order", TargetId: order.Id, Action: in.Action, Title: "交易订单人工处理 " + order.OrderNo, PayloadJson: gjson.New(payload), RiskLevel: consts.ApprovalRiskHigh, RequiredApprovals: 1, Remark: in.ReasonText})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = service.SysApproval().Submit(ctx, &sysin.ApprovalSubmitInp{Id: approval.Id}); err != nil {
|
|
return nil, err
|
|
}
|
|
data := g.Map{dao.TradeOrder.Columns().ApprovalRequestId: approval.Id, dao.TradeOrder.Columns().UpdatedBy: contexts.GetUserId(ctx)}
|
|
if in.Action == consts.TradeActionUnfreeze {
|
|
data[dao.TradeOrder.Columns().FreezeStatus] = consts.TradeFreezePending
|
|
}
|
|
if _, err = s.model(ctx).WherePri(order.Id).Data(data).Update(); err != nil {
|
|
return nil, gerror.Wrap(err, "登记订单处理申请失败")
|
|
}
|
|
return &sysin.FinanceApprovalModel{ApprovalId: approval.Id, ApprovalNo: approval.ApprovalNo}, nil
|
|
}
|
|
|
|
func (s *sSysTradeOrder) Execute(ctx context.Context, in *sysin.TradeOrderExecuteInp) 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 != "trade_order_action" {
|
|
return gerror.New("订单处理审批不存在")
|
|
}
|
|
if approval.ApprovalStatus != consts.ApprovalStatusApproved || approval.ExecutedAt != nil {
|
|
return gerror.New("审批尚未通过或已执行")
|
|
}
|
|
payload := approval.PayloadJson.Map()
|
|
orderId := gjson.New(payload).Get("id").Uint64()
|
|
action := gjson.New(payload).Get("action").String()
|
|
reason := gjson.New(payload).Get("reasonText").String()
|
|
var order *entity.TradeOrder
|
|
if err := s.model(ctx).WherePri(orderId).LockUpdate().Scan(&order); err != nil {
|
|
return err
|
|
}
|
|
if order == nil || order.ApprovalRequestId != approval.Id {
|
|
return gerror.New("订单处理申请已失效")
|
|
}
|
|
oc := dao.TradeOrder.Columns()
|
|
data := g.Map{oc.ApprovalRequestId: 0, oc.UpdatedBy: contexts.GetUserId(ctx)}
|
|
switch action {
|
|
case consts.TradeActionCancelMatch:
|
|
if order.OrderStatus != consts.TradeOrderMatching {
|
|
return gerror.New("订单状态已变化,不能取消匹配")
|
|
}
|
|
data[oc.OrderStatus] = consts.TradeOrderCancelled
|
|
case consts.TradeActionRematch:
|
|
if order.OrderStatus != consts.TradeOrderTimeout && order.OrderStatus != consts.TradeOrderFailed {
|
|
return gerror.New("订单状态已变化,不能重新匹配")
|
|
}
|
|
data[oc.OrderStatus], data[oc.MatchedAt] = consts.TradeOrderMatching, nil
|
|
case consts.TradeActionUnfreeze:
|
|
if order.FreezeStatus != consts.TradeFreezePending {
|
|
return gerror.New("订单解冻状态已变化")
|
|
}
|
|
data[oc.FreezeStatus], data[oc.OrderStatus], data[oc.UnfrozenAt] = consts.TradeFreezeNormal, consts.TradeOrderPending, gtime.Now()
|
|
default:
|
|
return gerror.New("订单处理动作不合法")
|
|
}
|
|
if _, err := s.model(ctx).WherePri(order.Id).Data(data).Update(); err != nil {
|
|
return gerror.Wrap(err, "执行订单处理失败")
|
|
}
|
|
ac := dao.ApprovalRequest.Columns()
|
|
if _, err := service.SysApproval().Model(ctx).WherePri(approval.Id).Data(g.Map{ac.ApprovalStatus: consts.ApprovalStatusExecuted, ac.ExecutedAt: gtime.Now(), ac.UpdatedBy: contexts.GetUserId(ctx), ac.Version: gdb.Raw(ac.Version + "+1")}).Update(); err != nil {
|
|
return err
|
|
}
|
|
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "trade", Action: "trade_order." + action, TargetType: "trade_order", TargetId: order.Id, Before: order, After: data, Reason: reason, ApprovalNo: approval.ApprovalNo})
|
|
})
|
|
}
|