增加创建时间
This commit is contained in:
parent
18d1a1c7cc
commit
b9603832fc
@ -646,6 +646,14 @@ func (CoinSellerRechargeOrder) TableName() string {
|
||||
return "admin_coin_seller_recharge_orders"
|
||||
}
|
||||
|
||||
// BusinessTimeMS 是财务归档和统计切日使用的订单业务时间;历史订单未保存 order_date_ms 时回退到真实录入时间。
|
||||
func (order CoinSellerRechargeOrder) BusinessTimeMS() int64 {
|
||||
if order.OrderDateMS > 0 {
|
||||
return order.OrderDateMS
|
||||
}
|
||||
return order.CreatedAtMS
|
||||
}
|
||||
|
||||
type UserWithdrawalApplication struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
AppCode string `gorm:"size:32;index:idx_admin_withdrawal_app_status_time;not null" json:"appCode"`
|
||||
|
||||
@ -293,7 +293,8 @@ func (s *DashboardService) RecordFinanceCoinSellerRechargeOrder(ctx context.Cont
|
||||
"target_region_id": order.TargetRegionID,
|
||||
"usd_minor": order.USDMinorAmount,
|
||||
"coin_amount": order.CoinAmount,
|
||||
"created_at_ms": order.CreatedAtMS,
|
||||
// 统计切日必须使用运营选择的订单业务时间;历史数据通过模型方法回退到录入时间。
|
||||
"created_at_ms": order.BusinessTimeMS(),
|
||||
}
|
||||
return s.statisticsPOST(ctx, "/internal/v1/statistics/social/coin-seller-recharge-orders", payload)
|
||||
}
|
||||
|
||||
@ -107,6 +107,11 @@ func (s *Service) CreateOrder(ctx context.Context, actor shared.Actor, req creat
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nowMS := s.now().UnixMilli()
|
||||
input.OrderDateMS, err = normalizeCreateOrderBusinessTime(input.OrderDateMS, nowMS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 金币数只由当前 APP 汇率配置生成;即使调用方绕过前端直接提交 coinAmount,也不会进入请求结构或账务结果。
|
||||
target, err := s.resolveCoinSellerTarget(ctx, requestID, input.AppCode, input.TargetUserID)
|
||||
if err != nil {
|
||||
@ -128,7 +133,6 @@ func (s *Service) CreateOrder(ctx context.Context, actor shared.Actor, req creat
|
||||
if err := verifyReceiptMatchesInput(input, receipt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nowMS := s.now().UnixMilli()
|
||||
verifiedAtMS := receipt.VerifiedAtMS
|
||||
if verifiedAtMS <= 0 {
|
||||
verifiedAtMS = nowMS
|
||||
@ -935,6 +939,19 @@ func normalizeOrderDateMS(orderDate string, orderDateMS int64) (int64, error) {
|
||||
return 0, errors.New("订单日期不正确")
|
||||
}
|
||||
|
||||
func normalizeCreateOrderBusinessTime(orderDateMS int64, nowMS int64) (int64, error) {
|
||||
if nowMS <= 0 {
|
||||
return 0, errors.New("当前时间不正确")
|
||||
}
|
||||
if orderDateMS <= 0 {
|
||||
return nowMS, nil
|
||||
}
|
||||
if orderDateMS > nowMS {
|
||||
return 0, errors.New("订单时间不能晚于当前时间")
|
||||
}
|
||||
return orderDateMS, nil
|
||||
}
|
||||
|
||||
func requiresLegacyWriter(appCode string) bool {
|
||||
switch appctx.Normalize(appCode) {
|
||||
case "aslan", "yumi":
|
||||
|
||||
@ -102,6 +102,19 @@ func TestNormalizeReceiptVerificationCarriesProviderCurrencyAmount(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCreateOrderBusinessTimeDefaultsToNowAndRejectsFuture(t *testing.T) {
|
||||
const nowMS = int64(1_783_900_000_000)
|
||||
if got, err := normalizeCreateOrderBusinessTime(0, nowMS); err != nil || got != nowMS {
|
||||
t.Fatalf("missing order time should default to now: got=%d err=%v", got, err)
|
||||
}
|
||||
if got, err := normalizeCreateOrderBusinessTime(nowMS-86_400_000, nowMS); err != nil || got != nowMS-86_400_000 {
|
||||
t.Fatalf("past business time should be preserved: got=%d err=%v", got, err)
|
||||
}
|
||||
if _, err := normalizeCreateOrderBusinessTime(nowMS+1, nowMS); err == nil || !strings.Contains(err.Error(), "不能晚于") {
|
||||
t.Fatalf("future business time should be rejected, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCreateOrderLeavesCoinAmountForServerQuote(t *testing.T) {
|
||||
input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{
|
||||
AppCode: "lalu",
|
||||
|
||||
@ -530,11 +530,11 @@ func financeCoinSellerRechargeOrderBill(order model.CoinSellerRechargeOrder) rec
|
||||
CoinAmount: order.CoinAmount,
|
||||
USDMinorAmount: order.USDMinorAmount,
|
||||
ProviderAmountMinor: providerAmountMinor,
|
||||
CreatedAtMS: order.CreatedAtMS,
|
||||
CreatedAtMS: order.BusinessTimeMS(),
|
||||
ProviderCode: order.ProviderCode,
|
||||
UserPaidCurrencyCode: currency,
|
||||
UserPaidAmountMicro: userPaidAmountMicro,
|
||||
PaidSyncedAtMS: order.CreatedAtMS,
|
||||
PaidSyncedAtMS: order.BusinessTimeMS(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hyapp-admin-server/internal/model"
|
||||
)
|
||||
|
||||
func TestFinanceCoinSellerRechargeOrderBillUsesBusinessTime(t *testing.T) {
|
||||
order := model.CoinSellerRechargeOrder{
|
||||
ID: 7,
|
||||
AppCode: "lalu",
|
||||
TargetUserID: 10001,
|
||||
USDMinorAmount: 15000,
|
||||
CoinAmount: 12000000,
|
||||
OrderDateMS: 1_783_708_200_000,
|
||||
CreatedAtMS: 1_783_881_000_000,
|
||||
}
|
||||
bill := financeCoinSellerRechargeOrderBill(order)
|
||||
if bill.CreatedAtMS != order.OrderDateMS || bill.PaidSyncedAtMS != order.OrderDateMS {
|
||||
t.Fatalf("finance bill must use selected business time: %+v", bill)
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,9 @@ var (
|
||||
ErrCoinSellerRechargeOrderGranted = errors.New("coin seller recharge order already granted")
|
||||
)
|
||||
|
||||
// coinSellerRechargeBusinessTimeSQL 让新订单按运营选择的业务时间归档,同时保持历史 order_date_ms=0 数据仍按录入时间查询。
|
||||
const coinSellerRechargeBusinessTimeSQL = "CASE WHEN order_date_ms > 0 THEN order_date_ms ELSE created_at_ms END"
|
||||
|
||||
type CoinSellerRechargeOrderListOptions struct {
|
||||
Page int
|
||||
PageSize int
|
||||
@ -121,7 +124,7 @@ func (s *Store) ListCoinSellerRechargeOrders(options CoinSellerRechargeOrderList
|
||||
}
|
||||
var orders []model.CoinSellerRechargeOrder
|
||||
err := query.
|
||||
Order("created_at_ms DESC, id DESC").
|
||||
Order(coinSellerRechargeBusinessTimeSQL + " DESC, id DESC").
|
||||
Limit(pageSize).
|
||||
Offset((page - 1) * pageSize).
|
||||
Find(&orders).Error
|
||||
@ -375,10 +378,10 @@ func applyCoinSellerRechargeOrderFilters(query *gorm.DB, options CoinSellerRecha
|
||||
query = query.Where("operator_user_id = ?", options.OperatorID)
|
||||
}
|
||||
if options.CreatedFromMS > 0 {
|
||||
query = query.Where("created_at_ms >= ?", options.CreatedFromMS)
|
||||
query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS)
|
||||
}
|
||||
if options.CreatedToMS > 0 {
|
||||
query = query.Where("created_at_ms < ?", options.CreatedToMS)
|
||||
query = query.Where(coinSellerRechargeBusinessTimeSQL+" < ?", options.CreatedToMS)
|
||||
}
|
||||
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
@ -406,10 +409,10 @@ func applyCoinSellerRechargeOrderStatsFilters(query *gorm.DB, options CoinSeller
|
||||
query = query.Where("target_country_id = ?", options.CountryID)
|
||||
}
|
||||
if options.CreatedFromMS > 0 {
|
||||
query = query.Where("created_at_ms >= ?", options.CreatedFromMS)
|
||||
query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS)
|
||||
}
|
||||
if options.CreatedToMS > 0 {
|
||||
query = query.Where("created_at_ms < ?", options.CreatedToMS)
|
||||
query = query.Where(coinSellerRechargeBusinessTimeSQL+" < ?", options.CreatedToMS)
|
||||
}
|
||||
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
@ -434,9 +437,9 @@ func (s *Store) scanCoinSellerRechargeOrderDailyStats(options CoinSellerRecharge
|
||||
}
|
||||
var rows []row
|
||||
tzOffsetMS := int64(options.TzOffsetMinutes) * 60_000
|
||||
// 财务筛选按 created_at_ms 取数;日趋势按前端传入的统计时区偏移切日,避免北京时间财务页跨日错桶。
|
||||
// 财务筛选和趋势都按订单业务时间取数;历史记录自动回退录入时间,并继续按前端统计时区偏移切日。
|
||||
if err := query.Select(`
|
||||
DATE_FORMAT(FROM_UNIXTIME((created_at_ms + ?) / 1000), '%Y-%m-%d') AS date,
|
||||
DATE_FORMAT(FROM_UNIXTIME((`+coinSellerRechargeBusinessTimeSQL+` + ?) / 1000), '%Y-%m-%d') AS date,
|
||||
COUNT(*) AS count,
|
||||
COALESCE(SUM(usd_minor_amount), 0) AS usd_minor,
|
||||
COALESCE(SUM(coin_amount), 0) AS coin_amount`, tzOffsetMS).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user