feat(finance): 增加保险统计与订单保险明细
This commit is contained in:
@@ -406,6 +406,18 @@ func (r *Repository) dailySettlements(ctx context.Context, query DashboardQuery)
|
||||
err := db.Table("rental_orders AS ro USE INDEX (idx_rental_orders_dashboard_settled)").
|
||||
Select(`DATE_FORMAT(ro.settled_at, '%Y-%m-%d') AS date,
|
||||
COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
THEN 1 ELSE 0 END), 0) AS insurance_order_count,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS insurance_profit_amount_cent,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
AND ro.is_premium = 0 THEN 1 ELSE 0 END), 0) AS normal_insurance_order_count,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
AND ro.is_premium = 0 THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS normal_insurance_profit_amount_cent,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
AND ro.is_premium = 1 THEN 1 ELSE 0 END), 0) AS premium_insurance_order_count,
|
||||
COALESCE(SUM(CASE WHEN ro.compensation_plan = 'insurance' AND ro.insurance_fee_bps > 0
|
||||
AND ro.is_premium = 1 THEN COALESCE(oc.insurance_fee_cent, 0) ELSE 0 END), 0) AS premium_insurance_profit_amount_cent,
|
||||
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_amount_cent,
|
||||
COALESCE(SUM(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
|
||||
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
|
||||
@@ -466,6 +478,12 @@ func summaryFromDailyRows(payments []dailyPaymentRow, settlements []dailySettlem
|
||||
}
|
||||
for _, row := range settlements {
|
||||
summary.NormalOrderProfitAmountCent += row.PlatformIncomeAmountCent
|
||||
summary.InsuranceOrderCount += row.InsuranceOrderCount
|
||||
summary.InsuranceProfitAmountCent += row.InsuranceProfitAmountCent
|
||||
summary.NormalInsuranceOrderCount += row.NormalInsuranceOrderCount
|
||||
summary.NormalInsuranceProfitAmountCent += row.NormalInsuranceProfitAmountCent
|
||||
summary.PremiumInsuranceOrderCount += row.PremiumInsuranceOrderCount
|
||||
summary.PremiumInsuranceProfitAmountCent += row.PremiumInsuranceProfitAmountCent
|
||||
summary.OwnerShouldIncomeAmountCent += row.OwnerShouldIncomeAmountCent
|
||||
summary.OwnerWalletIncomeAmountCent += row.OwnerWalletIncomeAmountCent
|
||||
summary.OwnerEffectiveSettlementAmountCent += row.OwnerEffectiveSettlementAmountCent
|
||||
@@ -604,6 +622,12 @@ type dailyPaidOrderRow struct {
|
||||
type dailySettlementRow struct {
|
||||
Date string
|
||||
PlatformIncomeAmountCent int64
|
||||
InsuranceOrderCount int64
|
||||
InsuranceProfitAmountCent int64
|
||||
NormalInsuranceOrderCount int64
|
||||
NormalInsuranceProfitAmountCent int64
|
||||
PremiumInsuranceOrderCount int64
|
||||
PremiumInsuranceProfitAmountCent int64
|
||||
OwnerShouldIncomeAmountCent int64
|
||||
OwnerWalletIncomeAmountCent int64
|
||||
OwnerEffectiveSettlementAmountCent int64
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
const dashboardCacheTTL = 5 * time.Minute
|
||||
|
||||
func (r *Repository) dashboardCacheKey(query DashboardQuery) string {
|
||||
return fmt.Sprintf("admin-finance:dashboard:v1:%d:%d", query.StartDate.Unix(), query.EndDate.Unix())
|
||||
return fmt.Sprintf("admin-finance:dashboard:v2:%d:%d", query.StartDate.Unix(), query.EndDate.Unix())
|
||||
}
|
||||
|
||||
func (r *Repository) loadDashboardCache(ctx context.Context, query DashboardQuery) *DashboardDTO {
|
||||
|
||||
@@ -257,8 +257,8 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
|
||||
{TotalFlowAmountCent: 2500, TotalRefundAmountCent: 400, PendingRefundAmountCent: 70, SuccessfulPayCount: 5, PartialRefundCount: 2, PendingRefundCount: 1},
|
||||
}
|
||||
settlementRows := []dailySettlementRow{
|
||||
{PlatformIncomeAmountCent: 300, OwnerShouldIncomeAmountCent: 900, OwnerWalletIncomeAmountCent: 850, OwnerEffectiveSettlementAmountCent: 850, OfflineSettlementAmountCent: 100, OfflineSettlementPendingAmountCent: 20, OfflineSettlementPendingCount: 1, FinancialExceptionCount: 2, SettledOrderCount: 3},
|
||||
{PlatformIncomeAmountCent: 200, OwnerShouldIncomeAmountCent: 600, OwnerWalletIncomeAmountCent: 500, OwnerEffectiveSettlementAmountCent: 550, OfflineSettlementAmountCent: 200, OfflineSettlementPendingAmountCent: 40, OfflineSettlementPendingCount: 2, FinancialExceptionCount: 1, SettledOrderCount: 2},
|
||||
{PlatformIncomeAmountCent: 300, InsuranceOrderCount: 2, InsuranceProfitAmountCent: 80, NormalInsuranceOrderCount: 1, NormalInsuranceProfitAmountCent: 50, PremiumInsuranceOrderCount: 1, PremiumInsuranceProfitAmountCent: 30, OwnerShouldIncomeAmountCent: 900, OwnerWalletIncomeAmountCent: 850, OwnerEffectiveSettlementAmountCent: 850, OfflineSettlementAmountCent: 100, OfflineSettlementPendingAmountCent: 20, OfflineSettlementPendingCount: 1, FinancialExceptionCount: 2, SettledOrderCount: 3},
|
||||
{PlatformIncomeAmountCent: 200, InsuranceOrderCount: 1, InsuranceProfitAmountCent: 20, NormalInsuranceOrderCount: 1, NormalInsuranceProfitAmountCent: 20, OwnerShouldIncomeAmountCent: 600, OwnerWalletIncomeAmountCent: 500, OwnerEffectiveSettlementAmountCent: 550, OfflineSettlementAmountCent: 200, OfflineSettlementPendingAmountCent: 40, OfflineSettlementPendingCount: 2, FinancialExceptionCount: 1, SettledOrderCount: 2},
|
||||
}
|
||||
estimateRows := []dailyEstimatedRow{
|
||||
{EstimatedIncomeAmountCent: 120, PendingSettleOrderCount: 4},
|
||||
@@ -276,6 +276,11 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
|
||||
if summary.NormalOrderProfitAmountCent != 500 || summary.PickupProfitAmountCent != 90 || summary.PlatformIncomeAmountCent != 590 || summary.PickupCompletedCount != 2 {
|
||||
t.Fatalf("平台利润汇总错误: %+v", summary)
|
||||
}
|
||||
if summary.InsuranceOrderCount != 3 || summary.InsuranceProfitAmountCent != 100 ||
|
||||
summary.NormalInsuranceOrderCount != 2 || summary.NormalInsuranceProfitAmountCent != 70 ||
|
||||
summary.PremiumInsuranceOrderCount != 1 || summary.PremiumInsuranceProfitAmountCent != 30 {
|
||||
t.Fatalf("保险汇总错误: %+v", summary)
|
||||
}
|
||||
if summary.OwnerShouldIncomeAmountCent != 1500 || summary.OwnerWalletIncomeAmountCent != 1350 || summary.OwnerEffectiveSettlementAmountCent != 1400 || summary.SettlementDiffAmountCent != 100 {
|
||||
t.Fatalf("货主结算汇总错误: %+v", summary)
|
||||
}
|
||||
@@ -287,6 +292,54 @@ func TestSummaryFromDailyRowsPreservesDashboardTotals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsuranceSummaryUsesSettlementDateAndPremiumSnapshot(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
loc := timeutil.ShanghaiLocation()
|
||||
settledAt := time.Date(2026, 8, 2, 10, 0, 0, 0, loc)
|
||||
|
||||
for _, row := range [][]any{
|
||||
{1, "INS-NORMAL", 1, 1, 1, 1, settledAt, false, "insurance", 800},
|
||||
{2, "INS-PREMIUM", 2, 2, 2, 2, settledAt, true, "insurance", 400},
|
||||
{3, "NO-INSURANCE", 3, 3, 3, 3, settledAt, false, "normal", 0},
|
||||
{4, "OUTSIDE-RANGE", 4, 4, 4, 4, settledAt.AddDate(0, 0, 1), true, "insurance", 400},
|
||||
} {
|
||||
if err := db.Exec(`INSERT INTO rental_orders
|
||||
(id, order_no, listing_id, account_id, owner_id, renter_id, settled_at,
|
||||
is_premium, compensation_plan, insurance_fee_bps)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, row...).Error; err != nil {
|
||||
t.Fatalf("写入订单失败: %v", err)
|
||||
}
|
||||
}
|
||||
for _, row := range [][]any{
|
||||
{1, 1, 1, "accepted", 800},
|
||||
{2, 2, 2, "accepted", 400},
|
||||
{3, 3, 3, "accepted", 0},
|
||||
{4, 4, 4, "accepted", 400},
|
||||
} {
|
||||
if err := db.Exec(`INSERT INTO order_checkouts
|
||||
(id, order_id, initiated_by, status, insurance_fee_cent)
|
||||
VALUES (?, ?, ?, ?, ?)`, row...).Error; err != nil {
|
||||
t.Fatalf("写入结账单失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
repo := NewRepository(db)
|
||||
query := DashboardQuery{
|
||||
StartDate: time.Date(2026, 8, 2, 0, 0, 0, 0, loc),
|
||||
EndDate: time.Date(2026, 8, 2, 23, 59, 59, 0, loc),
|
||||
}
|
||||
settlements, err := repo.dailySettlements(t.Context(), query)
|
||||
if err != nil {
|
||||
t.Fatalf("读取每日结算汇总失败: %v", err)
|
||||
}
|
||||
summary := summaryFromDailyRows(nil, settlements, nil)
|
||||
if summary.InsuranceOrderCount != 2 || summary.InsuranceProfitAmountCent != 1200 ||
|
||||
summary.NormalInsuranceOrderCount != 1 || summary.NormalInsuranceProfitAmountCent != 800 ||
|
||||
summary.PremiumInsuranceOrderCount != 1 || summary.PremiumInsuranceProfitAmountCent != 400 {
|
||||
t.Fatalf("保险汇总错误: %+v", summary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardCacheKeyAndSingleflight(t *testing.T) {
|
||||
repo := NewRepository(nil)
|
||||
q := DashboardQuery{
|
||||
|
||||
@@ -246,6 +246,13 @@ type FinanceSummaryDTO struct {
|
||||
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
|
||||
NormalOrderProfitAmountCent int64 `json:"normal_order_profit_amount_cent"`
|
||||
PickupProfitAmountCent int64 `json:"pickup_profit_amount_cent"`
|
||||
// 无忧保险统计与平台净利润同口径,按结算日归集。
|
||||
InsuranceOrderCount int64 `json:"insurance_order_count"`
|
||||
InsuranceProfitAmountCent int64 `json:"insurance_profit_amount_cent"`
|
||||
NormalInsuranceOrderCount int64 `json:"normal_insurance_order_count"`
|
||||
NormalInsuranceProfitAmountCent int64 `json:"normal_insurance_profit_amount_cent"`
|
||||
PremiumInsuranceOrderCount int64 `json:"premium_insurance_order_count"`
|
||||
PremiumInsuranceProfitAmountCent int64 `json:"premium_insurance_profit_amount_cent"`
|
||||
// PickupCompletedCount 线下提号利润对应的已完成提号笔数。
|
||||
PickupCompletedCount int64 `json:"pickup_completed_count"`
|
||||
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
|
||||
|
||||
Reference in New Issue
Block a user