币商扣减
This commit is contained in:
parent
7515233227
commit
0d4ce59c10
@ -108,8 +108,7 @@ func (s *AslanMongoDashboardSource) coinSellerRechargeBillUnionSQL(query CoinSel
|
|||||||
goldArgs := []any{s.sysOrigin}
|
goldArgs := []any{s.sysOrigin}
|
||||||
freightWhere := []string{
|
freightWhere := []string{
|
||||||
"fbrw.sys_origin = ?",
|
"fbrw.sys_origin = ?",
|
||||||
"fbrw.origin = 'PURCHASE'",
|
"((fbrw.origin = 'PURCHASE' AND fbrw.type = 0) OR (fbrw.origin = 'DEDUCTION' AND fbrw.type = 1))",
|
||||||
"fbrw.type = 0",
|
|
||||||
"(COALESCE(fbrw.amount, 0) <> 0 OR COALESCE(fbrw.usd_quantity, 0) <> 0)",
|
"(COALESCE(fbrw.amount, 0) <> 0 OR COALESCE(fbrw.usd_quantity, 0) <> 0)",
|
||||||
"ubi.country_code IS NOT NULL",
|
"ubi.country_code IS NOT NULL",
|
||||||
"ubi.country_code != ''",
|
"ubi.country_code != ''",
|
||||||
@ -132,7 +131,7 @@ func (s *AslanMongoDashboardSource) coinSellerRechargeBillUnionSQL(query CoinSel
|
|||||||
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
|
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
|
||||||
goldWhere = append(goldWhere, "(CAST(ugcr.id AS CHAR) = ? OR CONCAT('aslan-gold-coin-', ugcr.id) = ? OR CAST(ugcr.user_id AS CHAR) = ?)")
|
goldWhere = append(goldWhere, "(CAST(ugcr.id AS CHAR) = ? OR CONCAT('aslan-gold-coin-', ugcr.id) = ? OR CAST(ugcr.user_id AS CHAR) = ?)")
|
||||||
goldArgs = append(goldArgs, keyword, keyword, keyword)
|
goldArgs = append(goldArgs, keyword, keyword, keyword)
|
||||||
freightWhere = append(freightWhere, "(CAST(fbrw.id AS CHAR) = ? OR CONCAT('aslan-freight-purchase-', fbrw.id) = ? OR CAST(fbrw.user_id AS CHAR) = ?)")
|
freightWhere = append(freightWhere, "(CAST(fbrw.id AS CHAR) = ? OR CONCAT('aslan-freight-', LOWER(fbrw.origin), '-', fbrw.id) = ? OR CAST(fbrw.user_id AS CHAR) = ?)")
|
||||||
freightArgs = append(freightArgs, keyword, keyword, keyword)
|
freightArgs = append(freightArgs, keyword, keyword, keyword)
|
||||||
}
|
}
|
||||||
if countryFilter.Applied {
|
if countryFilter.Applied {
|
||||||
@ -170,11 +169,18 @@ FROM user_gold_coin_recharge ugcr
|
|||||||
INNER JOIN user_base_info ubi ON ubi.id = ugcr.user_id
|
INNER JOIN user_base_info ubi ON ubi.id = ugcr.user_id
|
||||||
WHERE ` + strings.Join(goldWhere, " AND ") + `
|
WHERE ` + strings.Join(goldWhere, " AND ") + `
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT CONCAT('aslan-freight-purchase-', fbrw.id) AS transaction_id,
|
SELECT CONCAT('aslan-freight-', LOWER(fbrw.origin), '-', fbrw.id) AS transaction_id,
|
||||||
'freight_purchase' AS source,
|
CONCAT('freight_', LOWER(fbrw.origin)) AS source,
|
||||||
fbrw.user_id,
|
fbrw.user_id,
|
||||||
UPPER(ubi.country_code) AS country_code,
|
UPPER(ubi.country_code) AS country_code,
|
||||||
CAST(CASE WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount ELSE COALESCE(fbrw.usd_quantity, 0) END AS CHAR) AS amount,
|
CAST(CASE
|
||||||
|
WHEN fbrw.origin = 'DEDUCTION' OR fbrw.type = 1 THEN -ABS(CASE
|
||||||
|
WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount
|
||||||
|
ELSE COALESCE(fbrw.usd_quantity, 0)
|
||||||
|
END)
|
||||||
|
WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount
|
||||||
|
ELSE COALESCE(fbrw.usd_quantity, 0)
|
||||||
|
END AS CHAR) AS amount,
|
||||||
fbrw.create_time AS created_at
|
fbrw.create_time AS created_at
|
||||||
FROM ` + quoteMySQLIdentifier(walletDatabase) + `.user_freight_balance_running_water fbrw
|
FROM ` + quoteMySQLIdentifier(walletDatabase) + `.user_freight_balance_running_water fbrw
|
||||||
INNER JOIN user_base_info ubi ON ubi.id = fbrw.user_id
|
INNER JOIN user_base_info ubi ON ubi.id = fbrw.user_id
|
||||||
|
|||||||
@ -836,7 +836,7 @@ func (s *AslanMongoDashboardSource) loadLegacyCoinSellerRechargeStats(ctx contex
|
|||||||
if err := s.loadLegacyGoldCoinRechargeStats(ctx, startDate, endDate, countryFilter, grid, distinctUsers, perUser); err != nil {
|
if err := s.loadLegacyGoldCoinRechargeStats(ctx, startDate, endDate, countryFilter, grid, distinctUsers, perUser); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.loadLegacyFreightPurchaseRechargeStats(ctx, startDate, endDate, countryFilter, grid, distinctUsers, perUser); err != nil {
|
if err := s.loadLegacyFreightStockRechargeStats(ctx, startDate, endDate, countryFilter, grid, distinctUsers, perUser); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -912,17 +912,17 @@ GROUP BY DATE_FORMAT(ugcr.create_time, '%Y-%m-%d'), UPPER(ubi.country_code), ugc
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AslanMongoDashboardSource) loadLegacyFreightPurchaseRechargeStats(ctx context.Context, startDate time.Time, endDate time.Time, countryFilter externalDashboardCountryFilter, grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64) error {
|
func (s *AslanMongoDashboardSource) loadLegacyFreightStockRechargeStats(ctx context.Context, startDate time.Time, endDate time.Time, countryFilter externalDashboardCountryFilter, grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64) error {
|
||||||
walletDatabase := strings.TrimSpace(s.legacyWalletDatabase)
|
walletDatabase := strings.TrimSpace(s.legacyWalletDatabase)
|
||||||
if walletDatabase == "" {
|
if walletDatabase == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// 后台“金币代理 > 发货”实际是给代理进货:origin=PURCHASE/type=0,表单金额落 amount;
|
// 后台“金币代理 > 发货”实际是给代理进货:origin=PURCHASE/type=0,表单金额落 amount;
|
||||||
|
// “扣减”是库存/USDT 冲减:origin=DEDUCTION/type=1,amount 在源系统仍是正数,统计时必须转成负数。
|
||||||
// App/经销商路径可能只落 usd_quantity,因此金额优先取 amount,空值再回退 usd_quantity。
|
// App/经销商路径可能只落 usd_quantity,因此金额优先取 amount,空值再回退 usd_quantity。
|
||||||
where := []string{
|
where := []string{
|
||||||
"fbrw.sys_origin = ?",
|
"fbrw.sys_origin = ?",
|
||||||
"fbrw.origin = 'PURCHASE'",
|
"((fbrw.origin = 'PURCHASE' AND fbrw.type = 0) OR (fbrw.origin = 'DEDUCTION' AND fbrw.type = 1))",
|
||||||
"fbrw.type = 0",
|
|
||||||
"fbrw.create_time >= ?",
|
"fbrw.create_time >= ?",
|
||||||
"fbrw.create_time < ?",
|
"fbrw.create_time < ?",
|
||||||
"(COALESCE(fbrw.amount, 0) <> 0 OR COALESCE(fbrw.usd_quantity, 0) <> 0)",
|
"(COALESCE(fbrw.amount, 0) <> 0 OR COALESCE(fbrw.usd_quantity, 0) <> 0)",
|
||||||
@ -956,6 +956,10 @@ SELECT DATE_FORMAT(fbrw.create_time, '%Y-%m-%d') AS stat_day,
|
|||||||
UPPER(ubi.country_code) AS country_code,
|
UPPER(ubi.country_code) AS country_code,
|
||||||
fbrw.user_id,
|
fbrw.user_id,
|
||||||
CAST(COALESCE(SUM(CASE
|
CAST(COALESCE(SUM(CASE
|
||||||
|
WHEN fbrw.origin = 'DEDUCTION' OR fbrw.type = 1 THEN -ABS(CASE
|
||||||
|
WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount
|
||||||
|
ELSE COALESCE(fbrw.usd_quantity, 0)
|
||||||
|
END)
|
||||||
WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount
|
WHEN fbrw.amount IS NOT NULL AND fbrw.amount <> 0 THEN fbrw.amount
|
||||||
ELSE COALESCE(fbrw.usd_quantity, 0)
|
ELSE COALESCE(fbrw.usd_quantity, 0)
|
||||||
END), 0) AS CHAR) AS amount
|
END), 0) AS CHAR) AS amount
|
||||||
@ -964,7 +968,7 @@ FROM `+quoteMySQLIdentifier(walletDatabase)+`.user_freight_balance_running_water
|
|||||||
WHERE `+strings.Join(where, " AND ")+`
|
WHERE `+strings.Join(where, " AND ")+`
|
||||||
GROUP BY DATE_FORMAT(fbrw.create_time, '%Y-%m-%d'), UPPER(ubi.country_code), fbrw.user_id`, args...)
|
GROUP BY DATE_FORMAT(fbrw.create_time, '%Y-%m-%d'), UPPER(ubi.country_code), fbrw.user_id`, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("query legacy freight purchase recharge: %w", err)
|
return fmt.Errorf("query legacy freight stock recharge: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@ -974,7 +978,7 @@ GROUP BY DATE_FORMAT(fbrw.create_time, '%Y-%m-%d'), UPPER(ubi.country_code), fbr
|
|||||||
var userID int64
|
var userID int64
|
||||||
var amountText string
|
var amountText string
|
||||||
if err := rows.Scan(&day, &countryCode, &userID, &amountText); err != nil {
|
if err := rows.Scan(&day, &countryCode, &userID, &amountText); err != nil {
|
||||||
return fmt.Errorf("scan legacy freight purchase recharge: %w", err)
|
return fmt.Errorf("scan legacy freight stock recharge: %w", err)
|
||||||
}
|
}
|
||||||
day = strings.TrimSpace(day)
|
day = strings.TrimSpace(day)
|
||||||
countryCode = aslanMongoCountryCode(countryCode)
|
countryCode = aslanMongoCountryCode(countryCode)
|
||||||
@ -983,24 +987,27 @@ GROUP BY DATE_FORMAT(fbrw.create_time, '%Y-%m-%d'), UPPER(ubi.country_code), fbr
|
|||||||
}
|
}
|
||||||
amount, err := decimalTextToMinor(amountText)
|
amount, err := decimalTextToMinor(amountText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse legacy freight purchase recharge amount for %s/%s/%d: %w", day, countryCode, userID, err)
|
return fmt.Errorf("parse legacy freight stock recharge amount for %s/%s/%d: %w", day, countryCode, userID, err)
|
||||||
}
|
}
|
||||||
addLegacyCoinSellerRecharge(grid, distinctUsers, perUser, day, countryCode, userID, amount)
|
addLegacyCoinSellerRecharge(grid, distinctUsers, perUser, day, countryCode, userID, amount)
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return fmt.Errorf("iterate legacy freight purchase recharge: %w", err)
|
return fmt.Errorf("iterate legacy freight stock recharge: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addLegacyCoinSellerRecharge(grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64, day string, countryCode string, userID int64, amount int64) {
|
func addLegacyCoinSellerRecharge(grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64, day string, countryCode string, userID int64, amount int64) {
|
||||||
if amount <= 0 {
|
if amount == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
metric := grid.at(day, countryCode)
|
metric := grid.at(day, countryCode)
|
||||||
metric.RechargeUSDMinor += amount
|
metric.RechargeUSDMinor += amount
|
||||||
metric.CoinSellerRechargeUSDMinor += amount
|
metric.CoinSellerRechargeUSDMinor += amount
|
||||||
addLegacyRechargeUser(distinctUsers, perUser, day, countryCode, userID, amount)
|
// 扣减流水按负数冲减金额,但不是一次新的付费行为,不能增加付费用户或新用户充值。
|
||||||
|
if amount > 0 {
|
||||||
|
addLegacyRechargeUser(distinctUsers, perUser, day, countryCode, userID, amount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addLegacyStandardRecharge(grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64, day string, countryCode string, userID int64, amount int64) {
|
func addLegacyStandardRecharge(grid *legacyMongoGrid, distinctUsers map[aslanMongoDayCountryUsers]map[string]struct{}, perUser map[string]map[int64]int64, day string, countryCode string, userID int64, amount int64) {
|
||||||
@ -1748,7 +1755,7 @@ func aslanMongoDashboardMetricSources(goldWater bool, legacyRecharge bool) []map
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
legacySources := []map[string]any{
|
legacySources := []map[string]any{
|
||||||
{"field": "coin_seller_recharge_usd_minor", "available": true, "source": "likei legacy MySQL user_gold_coin_recharge gold_coin_source=2 recharge_amount + user_freight_balance_running_water PURCHASE amount/usd_quantity"},
|
{"field": "coin_seller_recharge_usd_minor", "available": true, "source": "likei legacy MySQL user_gold_coin_recharge gold_coin_source=2 recharge_amount + user_freight_balance_running_water PURCHASE amount/usd_quantity - DEDUCTION amount/usd_quantity"},
|
||||||
}
|
}
|
||||||
rechargeUsersSource := "likei Mongo in_app_purchase_details SUCCESS distinct acceptUserId(排除 FREIGHT_GOLD)"
|
rechargeUsersSource := "likei Mongo in_app_purchase_details SUCCESS distinct acceptUserId(排除 FREIGHT_GOLD)"
|
||||||
rechargeAmountSource := "likei Mongo in_app_purchase_details amountUsd(排除 FREIGHT_GOLD)"
|
rechargeAmountSource := "likei Mongo in_app_purchase_details amountUsd(排除 FREIGHT_GOLD)"
|
||||||
@ -1760,7 +1767,7 @@ func aslanMongoDashboardMetricSources(goldWater bool, legacyRecharge bool) []map
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rechargeUsersSource += " + legacy MySQL order_other_recharge/user_gold_coin_recharge/user_freight_balance_running_water distinct user_id"
|
rechargeUsersSource += " + legacy MySQL order_other_recharge/user_gold_coin_recharge/user_freight_balance_running_water distinct user_id"
|
||||||
rechargeAmountSource += " + legacy MySQL order_other_recharge amount + user_gold_coin_recharge recharge_amount + user_freight_balance_running_water PURCHASE amount/usd_quantity"
|
rechargeAmountSource += " + legacy MySQL order_other_recharge amount + user_gold_coin_recharge recharge_amount + user_freight_balance_running_water PURCHASE amount/usd_quantity - DEDUCTION amount/usd_quantity"
|
||||||
mifaPaySource += " + legacy MySQL order_other_recharge amount"
|
mifaPaySource += " + legacy MySQL order_other_recharge amount"
|
||||||
newUserRechargeSource += "/legacy 其他充值/legacy 币商充值"
|
newUserRechargeSource += "/legacy 其他充值/legacy 币商充值"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,7 +169,7 @@ func TestAslanMongoLegacyOtherRechargeFeedsThirdPartyChannel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAslanMongoLegacyFreightPurchaseRechargeFeedsCoinSellerChannel(t *testing.T) {
|
func TestAslanMongoLegacyFreightStockAdjustmentsFeedCoinSellerChannel(t *testing.T) {
|
||||||
db, mock, err := sqlmock.New()
|
db, mock, err := sqlmock.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("sqlmock: %v", err)
|
t.Fatalf("sqlmock: %v", err)
|
||||||
@ -196,11 +196,12 @@ func TestAslanMongoLegacyFreightPurchaseRechargeFeedsCoinSellerChannel(t *testin
|
|||||||
mock.ExpectQuery("FROM user_gold_coin_recharge[\\s\\S]*ugcr\\.gold_coin_source = 2").
|
mock.ExpectQuery("FROM user_gold_coin_recharge[\\s\\S]*ugcr\\.gold_coin_source = 2").
|
||||||
WithArgs("ATYOU", start, end).
|
WithArgs("ATYOU", start, end).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"stat_day", "country_code", "user_id", "amount"}))
|
WillReturnRows(sqlmock.NewRows([]string{"stat_day", "country_code", "user_id", "amount"}))
|
||||||
mock.ExpectQuery("FROM `atyou_wallet`\\.user_freight_balance_running_water fbrw[\\s\\S]*fbrw\\.origin = 'PURCHASE'[\\s\\S]*fbrw\\.type = 0").
|
mock.ExpectQuery("FROM `atyou_wallet`\\.user_freight_balance_running_water fbrw[\\s\\S]*fbrw\\.origin = 'PURCHASE'[\\s\\S]*fbrw\\.origin = 'DEDUCTION'").
|
||||||
WithArgs("ATYOU", start, end).
|
WithArgs("ATYOU", start, end).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"stat_day", "country_code", "user_id", "amount"}).
|
WillReturnRows(sqlmock.NewRows([]string{"stat_day", "country_code", "user_id", "amount"}).
|
||||||
AddRow("2026-07-05", "PH", int64(2069828597070528514), "2000.00").
|
AddRow("2026-07-05", "PH", int64(2069828597070528514), "2000.00").
|
||||||
AddRow("2026-07-05", "PK", int64(2071589123112779777), "25.47"))
|
AddRow("2026-07-05", "PK", int64(2071589123112779777), "25.47").
|
||||||
|
AddRow("2026-07-05", "SA", int64(2071589123112779778), "-1000.00"))
|
||||||
|
|
||||||
if err := source.loadLegacyCoinSellerRechargeStats(context.Background(), start, end, externalDashboardCountryFilter{}, grid, distinctUsers, perUser); err != nil {
|
if err := source.loadLegacyCoinSellerRechargeStats(context.Background(), start, end, externalDashboardCountryFilter{}, grid, distinctUsers, perUser); err != nil {
|
||||||
t.Fatalf("loadLegacyCoinSellerRechargeStats: %v", err)
|
t.Fatalf("loadLegacyCoinSellerRechargeStats: %v", err)
|
||||||
@ -218,6 +219,12 @@ func TestAslanMongoLegacyFreightPurchaseRechargeFeedsCoinSellerChannel(t *testin
|
|||||||
assertInt64(t, pkRow["coin_seller_recharge_usd_minor"], 2547)
|
assertInt64(t, pkRow["coin_seller_recharge_usd_minor"], 2547)
|
||||||
assertInt64(t, pkRow["recharge_usd_minor"], 2547)
|
assertInt64(t, pkRow["recharge_usd_minor"], 2547)
|
||||||
|
|
||||||
|
saRow := grid.metrics["2026-07-05"]["SA"].toExternal(nil).toMap()
|
||||||
|
assertInt64(t, saRow["coin_seller_recharge_usd_minor"], -100000)
|
||||||
|
assertInt64(t, saRow["recharge_usd_minor"], -100000)
|
||||||
|
assertInt64(t, saRow["user_recharge_usd_minor"], 0)
|
||||||
|
assertInt64(t, saRow["paid_users"], 0)
|
||||||
|
|
||||||
if err := mock.ExpectationsWereMet(); err != nil {
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
t.Fatalf("sql expectations: %v", err)
|
t.Fatalf("sql expectations: %v", err)
|
||||||
}
|
}
|
||||||
@ -244,10 +251,11 @@ func TestAslanMongoListCoinSellerRechargeBills(t *testing.T) {
|
|||||||
queryPattern := "FROM user_gold_coin_recharge ugcr[\\s\\S]*UNION ALL[\\s\\S]*FROM `atyou_wallet`\\.user_freight_balance_running_water fbrw"
|
queryPattern := "FROM user_gold_coin_recharge ugcr[\\s\\S]*UNION ALL[\\s\\S]*FROM `atyou_wallet`\\.user_freight_balance_running_water fbrw"
|
||||||
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM \\([\\s\\S]*"+queryPattern).
|
mock.ExpectQuery("SELECT COUNT\\(\\*\\) FROM \\([\\s\\S]*"+queryPattern).
|
||||||
WithArgs("ATYOU", startArg, endArg, "ATYOU", startArg, endArg).
|
WithArgs("ATYOU", startArg, endArg, "ATYOU", startArg, endArg).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(2)))
|
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(int64(3)))
|
||||||
mock.ExpectQuery("SELECT transaction_id, source, user_id, country_code, amount, created_at[\\s\\S]*"+queryPattern).
|
mock.ExpectQuery("SELECT transaction_id, source, user_id, country_code, amount, created_at[\\s\\S]*"+queryPattern).
|
||||||
WithArgs("ATYOU", startArg, endArg, "ATYOU", startArg, endArg, 10, 0).
|
WithArgs("ATYOU", startArg, endArg, "ATYOU", startArg, endArg, 10, 0).
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"transaction_id", "source", "user_id", "country_code", "amount", "created_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"transaction_id", "source", "user_id", "country_code", "amount", "created_at"}).
|
||||||
|
AddRow("aslan-freight-deduction-100", "freight_deduction", int64(2069828597070528516), "ae", "-1000.00", createdAt.Add(time.Minute)).
|
||||||
AddRow("aslan-freight-purchase-99", "freight_purchase", int64(2069828597070528514), "ph", "1327.90", createdAt).
|
AddRow("aslan-freight-purchase-99", "freight_purchase", int64(2069828597070528514), "ph", "1327.90", createdAt).
|
||||||
AddRow("aslan-gold-coin-7", "gold_coin_recharge", int64(2069828597070528515), "SA", "42.00", createdAt.Add(-time.Hour)))
|
AddRow("aslan-gold-coin-7", "gold_coin_recharge", int64(2069828597070528515), "SA", "42.00", createdAt.Add(-time.Hour)))
|
||||||
|
|
||||||
@ -260,18 +268,21 @@ func TestAslanMongoListCoinSellerRechargeBills(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ListCoinSellerRechargeBills: %v", err)
|
t.Fatalf("ListCoinSellerRechargeBills: %v", err)
|
||||||
}
|
}
|
||||||
if total != 2 || len(items) != 2 {
|
if total != 3 || len(items) != 3 {
|
||||||
t.Fatalf("unexpected result total=%d items=%+v", total, items)
|
t.Fatalf("unexpected result total=%d items=%+v", total, items)
|
||||||
}
|
}
|
||||||
if items[0].TransactionID != "aslan-freight-purchase-99" || items[0].Source != "freight_purchase" || items[0].CountryCode != "PH" {
|
if items[0].TransactionID != "aslan-freight-deduction-100" || items[0].Source != "freight_deduction" || items[0].CountryCode != "AE" {
|
||||||
t.Fatalf("unexpected first item identity: %+v", items[0])
|
t.Fatalf("unexpected first item identity: %+v", items[0])
|
||||||
}
|
}
|
||||||
if items[0].USDMinorAmount != 132790 || items[0].CreatedAtMS != createdAt.UnixMilli() {
|
if items[0].USDMinorAmount != -100000 || items[0].CreatedAtMS != createdAt.Add(time.Minute).UnixMilli() {
|
||||||
t.Fatalf("unexpected first item amount/time: %+v", items[0])
|
t.Fatalf("unexpected first item amount/time: %+v", items[0])
|
||||||
}
|
}
|
||||||
if items[1].TransactionID != "aslan-gold-coin-7" || items[1].USDMinorAmount != 4200 {
|
if items[1].TransactionID != "aslan-freight-purchase-99" || items[1].USDMinorAmount != 132790 {
|
||||||
t.Fatalf("unexpected second item: %+v", items[1])
|
t.Fatalf("unexpected second item: %+v", items[1])
|
||||||
}
|
}
|
||||||
|
if items[2].TransactionID != "aslan-gold-coin-7" || items[2].USDMinorAmount != 4200 {
|
||||||
|
t.Fatalf("unexpected third item: %+v", items[2])
|
||||||
|
}
|
||||||
if err := mock.ExpectationsWereMet(); err != nil {
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
t.Fatalf("sql expectations: %v", err)
|
t.Fatalf("sql expectations: %v", err)
|
||||||
}
|
}
|
||||||
@ -300,24 +311,31 @@ func TestAslanMongoRechargeMatchExcludesFreightGold(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAslanMongoLegacyRechargeSkipsNonPositiveAmount(t *testing.T) {
|
func TestAslanMongoLegacyRechargeHandlesZeroAndDeductionAmount(t *testing.T) {
|
||||||
start := time.Date(2026, 7, 5, 0, 0, 0, 0, time.UTC)
|
start := time.Date(2026, 7, 5, 0, 0, 0, 0, time.UTC)
|
||||||
grid := newLegacyMongoGrid(start, start.AddDate(0, 0, 1))
|
grid := newLegacyMongoGrid(start, start.AddDate(0, 0, 1))
|
||||||
distinctUsers := map[aslanMongoDayCountryUsers]map[string]struct{}{}
|
distinctUsers := map[aslanMongoDayCountryUsers]map[string]struct{}{}
|
||||||
perUser := map[string]map[int64]int64{}
|
perUser := map[string]map[int64]int64{}
|
||||||
|
|
||||||
addLegacyStandardRecharge(grid, distinctUsers, perUser, "2026-07-05", "SA", 101, 0)
|
addLegacyStandardRecharge(grid, distinctUsers, perUser, "2026-07-05", "SA", 101, 0)
|
||||||
addLegacyCoinSellerRecharge(grid, distinctUsers, perUser, "2026-07-05", "SA", 102, -100)
|
addLegacyCoinSellerRecharge(grid, distinctUsers, perUser, "2026-07-05", "SA", 102, 0)
|
||||||
|
if row := grid.metrics["2026-07-05"]["SA"]; row != nil {
|
||||||
|
t.Fatalf("zero legacy recharge should not create metric row: %#v", row)
|
||||||
|
}
|
||||||
|
|
||||||
|
addLegacyCoinSellerRecharge(grid, distinctUsers, perUser, "2026-07-05", "SA", 102, -100)
|
||||||
row := grid.metrics["2026-07-05"]["SA"]
|
row := grid.metrics["2026-07-05"]["SA"]
|
||||||
if row != nil {
|
if row == nil {
|
||||||
t.Fatalf("non-positive legacy recharge should not create metric row: %#v", row)
|
t.Fatalf("negative coin seller deduction should create metric row")
|
||||||
|
}
|
||||||
|
if row.RechargeUSDMinor != -100 || row.CoinSellerRechargeUSDMinor != -100 {
|
||||||
|
t.Fatalf("deduction should reduce recharge metrics: %#v", row)
|
||||||
}
|
}
|
||||||
if len(distinctUsers) != 0 {
|
if len(distinctUsers) != 0 {
|
||||||
t.Fatalf("non-positive legacy recharge should not create paid users: %#v", distinctUsers)
|
t.Fatalf("deduction should not create paid users: %#v", distinctUsers)
|
||||||
}
|
}
|
||||||
if len(perUser) != 0 {
|
if len(perUser) != 0 {
|
||||||
t.Fatalf("non-positive legacy recharge should not create per-user recharge: %#v", perUser)
|
t.Fatalf("deduction should not create per-user recharge: %#v", perUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -296,7 +296,7 @@ func (s *MongoRechargeBillSource) listCoinSellerRechargeBills(ctx context.Contex
|
|||||||
item := rechargeBillDTO{
|
item := rechargeBillDTO{
|
||||||
AppCode: s.config.AppCode,
|
AppCode: s.config.AppCode,
|
||||||
TransactionID: row.TransactionID,
|
TransactionID: row.TransactionID,
|
||||||
RechargeType: "coin_seller_stock_purchase",
|
RechargeType: legacyCoinSellerBillRechargeType(row.Source),
|
||||||
Status: "succeeded",
|
Status: "succeeded",
|
||||||
ExternalRef: row.Source,
|
ExternalRef: row.Source,
|
||||||
SellerUserID: row.UserID,
|
SellerUserID: row.UserID,
|
||||||
@ -318,6 +318,15 @@ func (s *MongoRechargeBillSource) listCoinSellerRechargeBills(ctx context.Contex
|
|||||||
return items, total, nil
|
return items, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func legacyCoinSellerBillRechargeType(source string) string {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(source)) {
|
||||||
|
case "freight_deduction", "coin_seller_stock_deduction":
|
||||||
|
return "coin_seller_stock_deduction"
|
||||||
|
default:
|
||||||
|
return "coin_seller_stock_purchase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MongoRechargeBillSource) listBillsByFilter(ctx context.Context, filter bson.M, page int, pageSize int) ([]rechargeBillDTO, int64, error) {
|
func (s *MongoRechargeBillSource) listBillsByFilter(ctx context.Context, filter bson.M, page int, pageSize int) ([]rechargeBillDTO, int64, error) {
|
||||||
total, err := s.collection.CountDocuments(ctx, filter)
|
total, err := s.collection.CountDocuments(ctx, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -237,15 +237,25 @@ func (s *fakeDashboardRechargeSource) ListCoinSellerRechargeBills(_ context.Cont
|
|||||||
func TestLegacyCoinSellerListUsesDashboardBillSource(t *testing.T) {
|
func TestLegacyCoinSellerListUsesDashboardBillSource(t *testing.T) {
|
||||||
dashboardSource := &fakeDashboardRechargeSource{
|
dashboardSource := &fakeDashboardRechargeSource{
|
||||||
appCode: "aslan",
|
appCode: "aslan",
|
||||||
billTotal: 1,
|
billTotal: 2,
|
||||||
bills: []dashboard.CoinSellerRechargeBill{{
|
bills: []dashboard.CoinSellerRechargeBill{
|
||||||
TransactionID: "aslan-freight-purchase-99",
|
{
|
||||||
Source: "freight_purchase",
|
TransactionID: "aslan-freight-purchase-99",
|
||||||
UserID: 2069828597070528514,
|
Source: "freight_purchase",
|
||||||
CountryCode: "PH",
|
UserID: 2069828597070528514,
|
||||||
USDMinorAmount: 132790,
|
CountryCode: "PH",
|
||||||
CreatedAtMS: 1_783_530_000_000,
|
USDMinorAmount: 132790,
|
||||||
}},
|
CreatedAtMS: 1_783_530_000_000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TransactionID: "aslan-freight-deduction-100",
|
||||||
|
Source: "freight_deduction",
|
||||||
|
UserID: 2069828597070528515,
|
||||||
|
CountryCode: "AE",
|
||||||
|
USDMinorAmount: -100000,
|
||||||
|
CreatedAtMS: 1_783_530_060_000,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
source := &MongoRechargeBillSource{
|
source := &MongoRechargeBillSource{
|
||||||
config: aslanBillSourceConfig(),
|
config: aslanBillSourceConfig(),
|
||||||
@ -254,6 +264,11 @@ func TestLegacyCoinSellerListUsesDashboardBillSource(t *testing.T) {
|
|||||||
RegionID: 301,
|
RegionID: 301,
|
||||||
Name: "Philippines",
|
Name: "Philippines",
|
||||||
Countries: []string{"PH"},
|
Countries: []string{"PH"},
|
||||||
|
}, {
|
||||||
|
AppCode: "aslan",
|
||||||
|
RegionID: 302,
|
||||||
|
Name: "United Arab Emirates",
|
||||||
|
Countries: []string{"AE"},
|
||||||
}}}},
|
}}}},
|
||||||
}
|
}
|
||||||
source.SetDashboardCoinSellerSource(dashboardSource)
|
source.SetDashboardCoinSellerSource(dashboardSource)
|
||||||
@ -268,7 +283,7 @@ func TestLegacyCoinSellerListUsesDashboardBillSource(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("ListRechargeBills: %v", err)
|
t.Fatalf("ListRechargeBills: %v", err)
|
||||||
}
|
}
|
||||||
if total != 1 || len(items) != 1 {
|
if total != 2 || len(items) != 2 {
|
||||||
t.Fatalf("unexpected result total=%d items=%+v", total, items)
|
t.Fatalf("unexpected result total=%d items=%+v", total, items)
|
||||||
}
|
}
|
||||||
item := items[0]
|
item := items[0]
|
||||||
@ -284,6 +299,19 @@ func TestLegacyCoinSellerListUsesDashboardBillSource(t *testing.T) {
|
|||||||
if item.UserPaidCurrencyCode != "USD" || item.UserPaidAmountMicro != 1_327_900_000 {
|
if item.UserPaidCurrencyCode != "USD" || item.UserPaidAmountMicro != 1_327_900_000 {
|
||||||
t.Fatalf("paid facts mismatch: %+v", item)
|
t.Fatalf("paid facts mismatch: %+v", item)
|
||||||
}
|
}
|
||||||
|
deduction := items[1]
|
||||||
|
if deduction.RechargeType != "coin_seller_stock_deduction" || deduction.ProviderCode != "coin_seller" {
|
||||||
|
t.Fatalf("unexpected deduction mapping: %+v", deduction)
|
||||||
|
}
|
||||||
|
if deduction.TransactionID != "aslan-freight-deduction-100" || deduction.SellerUserID != 2069828597070528515 || deduction.USDMinorAmount != -100000 {
|
||||||
|
t.Fatalf("unexpected deduction fields: %+v", deduction)
|
||||||
|
}
|
||||||
|
if deduction.SellerRegionID != 302 || deduction.TargetRegionID != 302 {
|
||||||
|
t.Fatalf("deduction region mapping failed: %+v", deduction)
|
||||||
|
}
|
||||||
|
if deduction.UserPaidCurrencyCode != "USD" || deduction.UserPaidAmountMicro != -1_000_000_000 {
|
||||||
|
t.Fatalf("deduction paid facts mismatch: %+v", deduction)
|
||||||
|
}
|
||||||
if dashboardSource.billCalls != 1 || dashboardSource.billQuery.PageSize != 20 || dashboardSource.billQuery.StatTZ != "Asia/Shanghai" {
|
if dashboardSource.billCalls != 1 || dashboardSource.billQuery.PageSize != 20 || dashboardSource.billQuery.StatTZ != "Asia/Shanghai" {
|
||||||
t.Fatalf("dashboard bill source not called as expected: calls=%d query=%+v", dashboardSource.billCalls, dashboardSource.billQuery)
|
t.Fatalf("dashboard bill source not called as expected: calls=%d query=%+v", dashboardSource.billCalls, dashboardSource.billQuery)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user