增加创建时间
This commit is contained in:
parent
18d1a1c7cc
commit
b9603832fc
@ -646,6 +646,14 @@ func (CoinSellerRechargeOrder) TableName() string {
|
|||||||
return "admin_coin_seller_recharge_orders"
|
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 {
|
type UserWithdrawalApplication struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
AppCode string `gorm:"size:32;index:idx_admin_withdrawal_app_status_time;not null" json:"appCode"`
|
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,
|
"target_region_id": order.TargetRegionID,
|
||||||
"usd_minor": order.USDMinorAmount,
|
"usd_minor": order.USDMinorAmount,
|
||||||
"coin_amount": order.CoinAmount,
|
"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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
nowMS := s.now().UnixMilli()
|
||||||
|
input.OrderDateMS, err = normalizeCreateOrderBusinessTime(input.OrderDateMS, nowMS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// 金币数只由当前 APP 汇率配置生成;即使调用方绕过前端直接提交 coinAmount,也不会进入请求结构或账务结果。
|
// 金币数只由当前 APP 汇率配置生成;即使调用方绕过前端直接提交 coinAmount,也不会进入请求结构或账务结果。
|
||||||
target, err := s.resolveCoinSellerTarget(ctx, requestID, input.AppCode, input.TargetUserID)
|
target, err := s.resolveCoinSellerTarget(ctx, requestID, input.AppCode, input.TargetUserID)
|
||||||
if err != nil {
|
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 {
|
if err := verifyReceiptMatchesInput(input, receipt); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nowMS := s.now().UnixMilli()
|
|
||||||
verifiedAtMS := receipt.VerifiedAtMS
|
verifiedAtMS := receipt.VerifiedAtMS
|
||||||
if verifiedAtMS <= 0 {
|
if verifiedAtMS <= 0 {
|
||||||
verifiedAtMS = nowMS
|
verifiedAtMS = nowMS
|
||||||
@ -935,6 +939,19 @@ func normalizeOrderDateMS(orderDate string, orderDateMS int64) (int64, error) {
|
|||||||
return 0, errors.New("订单日期不正确")
|
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 {
|
func requiresLegacyWriter(appCode string) bool {
|
||||||
switch appctx.Normalize(appCode) {
|
switch appctx.Normalize(appCode) {
|
||||||
case "aslan", "yumi":
|
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) {
|
func TestNormalizeCreateOrderLeavesCoinAmountForServerQuote(t *testing.T) {
|
||||||
input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{
|
input, err := normalizeCreateOrderRequest(createCoinSellerRechargeOrderRequest{
|
||||||
AppCode: "lalu",
|
AppCode: "lalu",
|
||||||
|
|||||||
@ -530,11 +530,11 @@ func financeCoinSellerRechargeOrderBill(order model.CoinSellerRechargeOrder) rec
|
|||||||
CoinAmount: order.CoinAmount,
|
CoinAmount: order.CoinAmount,
|
||||||
USDMinorAmount: order.USDMinorAmount,
|
USDMinorAmount: order.USDMinorAmount,
|
||||||
ProviderAmountMinor: providerAmountMinor,
|
ProviderAmountMinor: providerAmountMinor,
|
||||||
CreatedAtMS: order.CreatedAtMS,
|
CreatedAtMS: order.BusinessTimeMS(),
|
||||||
ProviderCode: order.ProviderCode,
|
ProviderCode: order.ProviderCode,
|
||||||
UserPaidCurrencyCode: currency,
|
UserPaidCurrencyCode: currency,
|
||||||
UserPaidAmountMicro: userPaidAmountMicro,
|
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")
|
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 {
|
type CoinSellerRechargeOrderListOptions struct {
|
||||||
Page int
|
Page int
|
||||||
PageSize int
|
PageSize int
|
||||||
@ -121,7 +124,7 @@ func (s *Store) ListCoinSellerRechargeOrders(options CoinSellerRechargeOrderList
|
|||||||
}
|
}
|
||||||
var orders []model.CoinSellerRechargeOrder
|
var orders []model.CoinSellerRechargeOrder
|
||||||
err := query.
|
err := query.
|
||||||
Order("created_at_ms DESC, id DESC").
|
Order(coinSellerRechargeBusinessTimeSQL + " DESC, id DESC").
|
||||||
Limit(pageSize).
|
Limit(pageSize).
|
||||||
Offset((page - 1) * pageSize).
|
Offset((page - 1) * pageSize).
|
||||||
Find(&orders).Error
|
Find(&orders).Error
|
||||||
@ -375,10 +378,10 @@ func applyCoinSellerRechargeOrderFilters(query *gorm.DB, options CoinSellerRecha
|
|||||||
query = query.Where("operator_user_id = ?", options.OperatorID)
|
query = query.Where("operator_user_id = ?", options.OperatorID)
|
||||||
}
|
}
|
||||||
if options.CreatedFromMS > 0 {
|
if options.CreatedFromMS > 0 {
|
||||||
query = query.Where("created_at_ms >= ?", options.CreatedFromMS)
|
query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS)
|
||||||
}
|
}
|
||||||
if options.CreatedToMS > 0 {
|
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 != "" {
|
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
|
||||||
like := "%" + keyword + "%"
|
like := "%" + keyword + "%"
|
||||||
@ -406,10 +409,10 @@ func applyCoinSellerRechargeOrderStatsFilters(query *gorm.DB, options CoinSeller
|
|||||||
query = query.Where("target_country_id = ?", options.CountryID)
|
query = query.Where("target_country_id = ?", options.CountryID)
|
||||||
}
|
}
|
||||||
if options.CreatedFromMS > 0 {
|
if options.CreatedFromMS > 0 {
|
||||||
query = query.Where("created_at_ms >= ?", options.CreatedFromMS)
|
query = query.Where(coinSellerRechargeBusinessTimeSQL+" >= ?", options.CreatedFromMS)
|
||||||
}
|
}
|
||||||
if options.CreatedToMS > 0 {
|
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 != "" {
|
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
|
||||||
like := "%" + keyword + "%"
|
like := "%" + keyword + "%"
|
||||||
@ -434,9 +437,9 @@ func (s *Store) scanCoinSellerRechargeOrderDailyStats(options CoinSellerRecharge
|
|||||||
}
|
}
|
||||||
var rows []row
|
var rows []row
|
||||||
tzOffsetMS := int64(options.TzOffsetMinutes) * 60_000
|
tzOffsetMS := int64(options.TzOffsetMinutes) * 60_000
|
||||||
// 财务筛选按 created_at_ms 取数;日趋势按前端传入的统计时区偏移切日,避免北京时间财务页跨日错桶。
|
// 财务筛选和趋势都按订单业务时间取数;历史记录自动回退录入时间,并继续按前端统计时区偏移切日。
|
||||||
if err := query.Select(`
|
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,
|
COUNT(*) AS count,
|
||||||
COALESCE(SUM(usd_minor_amount), 0) AS usd_minor,
|
COALESCE(SUM(usd_minor_amount), 0) AS usd_minor,
|
||||||
COALESCE(SUM(coin_amount), 0) AS coin_amount`, tzOffsetMS).
|
COALESCE(SUM(coin_amount), 0) AS coin_amount`, tzOffsetMS).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user