修复平台代管财务差异

This commit is contained in:
yml2213
2026-07-26 10:09:37 +08:00
parent 9ccc16e068
commit 3107c54b65
8 changed files with 358 additions and 189 deletions
@@ -82,6 +82,25 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
Select(`COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent, Select(`COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_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(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END), 0) AS owner_effective_settlement_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE 0
END), 0) AS offline_settlement_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
AND COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') = 'pending'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE 0
END), 0) AS offline_settlement_pending_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
AND COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') = 'pending'
AND COALESCE(ro.offline_settlement_amount_cent, 0) > 0
THEN 1
ELSE 0
END), 0) AS offline_settlement_pending_count,
COUNT(ro.id) AS settled_order_count`). COUNT(ro.id) AS settled_order_count`).
Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'"). Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'").
Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(db)). Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(db)).
@@ -96,7 +115,8 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
StartDate: query.StartDate, StartDate: query.StartDate,
EndDate: query.EndDate, EndDate: query.EndDate,
})). })).
Where("finance_status <> ?", "normal"). Where("finance_status <> ?", financeStatusNormal).
Where("finance_status <> ?", financeStatusOfflineSettlementPending).
Count(&exceptionCount).Error; err != nil { Count(&exceptionCount).Error; err != nil {
return nil, err return nil, err
} }
@@ -121,7 +141,10 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent, PlatformIncomeAmountCent: settlement.PlatformIncomeAmountCent,
OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent, OwnerShouldIncomeAmountCent: settlement.OwnerShouldIncomeAmountCent,
OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent, OwnerWalletIncomeAmountCent: settlement.OwnerWalletIncomeAmountCent,
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerWalletIncomeAmountCent, OwnerEffectiveSettlementAmountCent: settlement.OwnerEffectiveSettlementAmountCent,
OfflineSettlementAmountCent: settlement.OfflineSettlementAmountCent,
OfflineSettlementPendingAmountCent: settlement.OfflineSettlementPendingAmountCent,
SettlementDiffAmountCent: settlement.OwnerShouldIncomeAmountCent - settlement.OwnerEffectiveSettlementAmountCent,
EstimatedIncomeAmountCent: estimated.EstimatedIncomeAmountCent, EstimatedIncomeAmountCent: estimated.EstimatedIncomeAmountCent,
SuccessfulPayCount: payment.SuccessfulPayCount, SuccessfulPayCount: payment.SuccessfulPayCount,
SuccessfulRefundCount: payment.FullRefundCount + payment.PartialRefundCount, SuccessfulRefundCount: payment.FullRefundCount + payment.PartialRefundCount,
@@ -130,6 +153,7 @@ func (r *Repository) summary(ctx context.Context, query DashboardQuery) (*Financ
PendingRefundCount: payment.PendingRefundCount, PendingRefundCount: payment.PendingRefundCount,
SettledOrderCount: settlement.SettledOrderCount, SettledOrderCount: settlement.SettledOrderCount,
PendingSettleOrderCount: estimated.PendingSettleOrderCount, PendingSettleOrderCount: estimated.PendingSettleOrderCount,
OfflineSettlementPendingCount: settlement.OfflineSettlementPendingCount,
FinancialExceptionCount: exceptionCount, FinancialExceptionCount: exceptionCount,
}, nil }, nil
} }
@@ -165,6 +189,25 @@ func (r *Repository) dailyItems(ctx context.Context, query DashboardQuery) ([]Fi
COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent, COALESCE(SUM(oc.platform_fee_cent), 0) AS platform_income_amount_cent,
COALESCE(SUM(oc.owner_income_amount_cent), 0) AS owner_should_income_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(COALESCE(w.owner_wallet_income_amount_cent, 0)), 0) AS owner_wallet_income_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END), 0) AS owner_effective_settlement_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE 0
END), 0) AS offline_settlement_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
AND COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') = 'pending'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE 0
END), 0) AS offline_settlement_pending_amount_cent,
COALESCE(SUM(CASE WHEN ro.settlement_mode = 'platform_managed'
AND COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') = 'pending'
AND COALESCE(ro.offline_settlement_amount_cent, 0) > 0
THEN 1
ELSE 0
END), 0) AS offline_settlement_pending_count,
COUNT(ro.id) AS settled_order_count`). COUNT(ro.id) AS settled_order_count`).
Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'"). Joins("JOIN order_checkouts AS oc ON oc.order_id = ro.id AND oc.status = 'accepted'").
Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(db)). Joins("LEFT JOIN (?) AS w ON w.order_id = ro.id", ownerWalletIncomeSubquery(db)).
@@ -214,8 +257,12 @@ func (r *Repository) dailyItems(ctx context.Context, query DashboardQuery) ([]Fi
item.PlatformIncomeAmountCent = row.PlatformIncomeAmountCent item.PlatformIncomeAmountCent = row.PlatformIncomeAmountCent
item.OwnerShouldIncomeAmountCent = row.OwnerShouldIncomeAmountCent item.OwnerShouldIncomeAmountCent = row.OwnerShouldIncomeAmountCent
item.OwnerWalletIncomeAmountCent = row.OwnerWalletIncomeAmountCent item.OwnerWalletIncomeAmountCent = row.OwnerWalletIncomeAmountCent
item.SettlementDiffAmountCent = row.OwnerShouldIncomeAmountCent - row.OwnerWalletIncomeAmountCent item.OwnerEffectiveSettlementAmountCent = row.OwnerEffectiveSettlementAmountCent
item.OfflineSettlementAmountCent = row.OfflineSettlementAmountCent
item.OfflineSettlementPendingAmountCent = row.OfflineSettlementPendingAmountCent
item.SettlementDiffAmountCent = row.OwnerShouldIncomeAmountCent - row.OwnerEffectiveSettlementAmountCent
item.SettledOrderCount = row.SettledOrderCount item.SettledOrderCount = row.SettledOrderCount
item.OfflineSettlementPendingCount = row.OfflineSettlementPendingCount
itemsByDate[date] = item itemsByDate[date] = item
} }
for _, row := range estimates { for _, row := range estimates {
@@ -247,6 +294,10 @@ type settlementSummaryRow struct {
PlatformIncomeAmountCent int64 PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64 OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64 OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
OfflineSettlementAmountCent int64
OfflineSettlementPendingAmountCent int64
OfflineSettlementPendingCount int64
SettledOrderCount int64 SettledOrderCount int64
} }
@@ -271,6 +322,10 @@ type dailySettlementRow struct {
PlatformIncomeAmountCent int64 PlatformIncomeAmountCent int64
OwnerShouldIncomeAmountCent int64 OwnerShouldIncomeAmountCent int64
OwnerWalletIncomeAmountCent int64 OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
OfflineSettlementAmountCent int64
OfflineSettlementPendingAmountCent int64
OfflineSettlementPendingCount int64
SettledOrderCount int64 SettledOrderCount int64
} }
@@ -61,6 +61,9 @@ func (r *Repository) applyDetailFilters(db *gorm.DB, query DetailQuery) *gorm.DB
func (r *Repository) financeDetailBaseQuery(ctx context.Context, query DetailQuery) *gorm.DB { func (r *Repository) financeDetailBaseQuery(ctx context.Context, query DetailQuery) *gorm.DB {
db := r.db.WithContext(ctx).Table("rental_orders AS ro"). db := r.db.WithContext(ctx).Table("rental_orders AS ro").
Select(`ro.id AS order_id, ro.order_no, ro.status AS order_status, ro.settlement_status, ro.refund_status, Select(`ro.id AS order_id, ro.order_no, ro.status AS order_status, ro.settlement_status, ro.refund_status,
ro.handoff_mode, ro.settlement_mode,
COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') AS offline_settlement_status,
COALESCE(ro.offline_settlement_amount_cent, 0) AS offline_settlement_amount_cent,
ro.renter_id, COALESCE(ru.phone, '') AS renter_phone, COALESCE(ru.nickname, '') AS renter_nickname, ro.renter_id, COALESCE(ru.phone, '') AS renter_phone, COALESCE(ru.nickname, '') AS renter_nickname,
ro.owner_id, COALESCE(ou.phone, '') AS owner_phone, COALESCE(ou.nickname, '') AS owner_nickname, ro.owner_id, COALESCE(ou.phone, '') AS owner_phone, COALESCE(ou.nickname, '') AS owner_nickname,
ro.rent_amount_cent AS order_rent_amount_cent, ro.deposit_amount_cent AS order_deposit_amount_cent, ro.rent_amount_cent AS order_rent_amount_cent, ro.deposit_amount_cent AS order_deposit_amount_cent,
@@ -69,20 +72,36 @@ func (r *Repository) financeDetailBaseQuery(ctx context.Context, query DetailQue
COALESCE(oc.owner_income_amount_cent, 0) AS checkout_owner_income_cent, COALESCE(oc.owner_income_amount_cent, 0) AS checkout_owner_income_cent,
COALESCE(oc.platform_fee_cent, 0) AS checkout_platform_fee_cent, COALESCE(oc.platform_fee_cent, 0) AS checkout_platform_fee_cent,
COALESCE(w.owner_wallet_income_amount_cent, 0) AS owner_wallet_income_amount_cent, COALESCE(w.owner_wallet_income_amount_cent, 0) AS owner_wallet_income_amount_cent,
CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END AS owner_effective_settlement_amount_cent,
COALESCE(p.paid_amount_cent, 0) AS paid_amount_cent, COALESCE(p.paid_amount_cent, 0) AS paid_amount_cent,
COALESCE(p.refunded_amount_cent, 0) AS refunded_amount_cent, COALESCE(p.refunded_amount_cent, 0) AS refunded_amount_cent,
COALESCE(p.refunding_amount_cent, 0) AS refunding_amount_cent, COALESCE(p.refunding_amount_cent, 0) AS refunding_amount_cent,
COALESCE(p.failed_refund_amount_cent, 0) AS failed_refund_amount_cent, COALESCE(p.failed_refund_amount_cent, 0) AS failed_refund_amount_cent,
COALESCE(p.paid_amount_cent, 0) - COALESCE(p.refunded_amount_cent, 0) AS channel_net_amount_cent, COALESCE(p.paid_amount_cent, 0) - COALESCE(p.refunded_amount_cent, 0) AS channel_net_amount_cent,
COALESCE(oc.platform_fee_cent, 0) + ((COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0))) AS platform_net_amount_cent, COALESCE(oc.platform_fee_cent, 0) + (COALESCE(oc.owner_income_amount_cent, 0) - CASE WHEN ro.settlement_mode = 'platform_managed'
COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0) AS settlement_diff_amount_cent, THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END) AS platform_net_amount_cent,
COALESCE(oc.owner_income_amount_cent, 0) - CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END AS settlement_diff_amount_cent,
CASE CASE
WHEN COALESCE(p.failed_refund_amount_cent, 0) > 0 THEN 'refund_failed' WHEN COALESCE(p.failed_refund_amount_cent, 0) > 0 THEN 'refund_failed'
WHEN COALESCE(p.refunding_amount_cent, 0) > 0 OR ro.refund_status = 'refunding' THEN 'refund_pending' WHEN COALESCE(p.refunding_amount_cent, 0) > 0 OR ro.refund_status = 'refunding' THEN 'refund_pending'
WHEN ABS(COALESCE(oc.owner_income_amount_cent, 0) - COALESCE(w.owner_wallet_income_amount_cent, 0)) >= ? THEN 'settlement_diff' WHEN ABS(COALESCE(oc.owner_income_amount_cent, 0) - CASE WHEN ro.settlement_mode = 'platform_managed'
THEN COALESCE(ro.offline_settlement_amount_cent, 0)
ELSE COALESCE(w.owner_wallet_income_amount_cent, 0)
END) >= ? THEN 'settlement_diff'
WHEN ro.settlement_mode = 'platform_managed'
AND COALESCE(ro.offline_settlement_amount_cent, 0) > 0
AND COALESCE(NULLIF(ro.offline_settlement_status, ''), 'none') = 'pending' THEN 'offline_settlement_pending'
ELSE 'normal' ELSE 'normal'
END AS finance_status, END AS finance_status,
ro.created_at, ro.settled_at`, settlementDiffThresholdCent). ro.created_at, ro.settled_at, ro.offline_settled_at`, settlementDiffThresholdCent).
Joins("LEFT JOIN users AS ru ON ru.id = ro.renter_id"). Joins("LEFT JOIN users AS ru ON ru.id = ro.renter_id").
Joins("LEFT JOIN users AS ou ON ou.id = ro.owner_id"). Joins("LEFT JOIN users AS ou ON ou.id = ro.owner_id").
Joins("LEFT JOIN (?) AS oc ON oc.order_id = ro.id", acceptedCheckoutSubquery(r.db.WithContext(ctx))). Joins("LEFT JOIN (?) AS oc ON oc.order_id = ro.id", acceptedCheckoutSubquery(r.db.WithContext(ctx))).
@@ -42,6 +42,9 @@ type FinanceSummaryDTO struct {
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"` PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"` OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"` OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
OwnerEffectiveSettlementAmountCent int64 `json:"owner_effective_settlement_amount_cent"`
OfflineSettlementAmountCent int64 `json:"offline_settlement_amount_cent"`
OfflineSettlementPendingAmountCent int64 `json:"offline_settlement_pending_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"` SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"` EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"` SuccessfulPayCount int64 `json:"successful_pay_count"`
@@ -51,6 +54,7 @@ type FinanceSummaryDTO struct {
PendingRefundCount int64 `json:"pending_refund_count"` PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"` SettledOrderCount int64 `json:"settled_order_count"`
PendingSettleOrderCount int64 `json:"pending_settle_order_count"` PendingSettleOrderCount int64 `json:"pending_settle_order_count"`
OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"`
FinancialExceptionCount int64 `json:"financial_exception_count"` FinancialExceptionCount int64 `json:"financial_exception_count"`
} }
@@ -63,6 +67,9 @@ type FinanceDailyDTO struct {
PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"` PlatformIncomeAmountCent int64 `json:"platform_income_amount_cent"`
OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"` OwnerShouldIncomeAmountCent int64 `json:"owner_should_income_amount_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"` OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
OwnerEffectiveSettlementAmountCent int64 `json:"owner_effective_settlement_amount_cent"`
OfflineSettlementAmountCent int64 `json:"offline_settlement_amount_cent"`
OfflineSettlementPendingAmountCent int64 `json:"offline_settlement_pending_amount_cent"`
SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"` SettlementDiffAmountCent int64 `json:"settlement_diff_amount_cent"`
EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"` EstimatedIncomeAmountCent int64 `json:"estimated_income_amount_cent"`
SuccessfulPayCount int64 `json:"successful_pay_count"` SuccessfulPayCount int64 `json:"successful_pay_count"`
@@ -71,6 +78,7 @@ type FinanceDailyDTO struct {
PartialRefundCount int64 `json:"partial_refund_count"` PartialRefundCount int64 `json:"partial_refund_count"`
PendingRefundCount int64 `json:"pending_refund_count"` PendingRefundCount int64 `json:"pending_refund_count"`
SettledOrderCount int64 `json:"settled_order_count"` SettledOrderCount int64 `json:"settled_order_count"`
OfflineSettlementPendingCount int64 `json:"offline_settlement_pending_count"`
} }
type PaginatedResult struct { type PaginatedResult struct {
@@ -86,6 +94,10 @@ type FinanceDetailDTO struct {
OrderStatus string `json:"order_status"` OrderStatus string `json:"order_status"`
SettlementStatus string `json:"settlement_status"` SettlementStatus string `json:"settlement_status"`
RefundStatus string `json:"refund_status"` RefundStatus string `json:"refund_status"`
HandoffMode string `json:"handoff_mode"`
SettlementMode string `json:"settlement_mode"`
OfflineSettlementStatus string `json:"offline_settlement_status"`
OfflineSettlementAmountCent int64 `json:"offline_settlement_amount_cent"`
RenterID uint64 `json:"renter_id"` RenterID uint64 `json:"renter_id"`
RenterPhone string `json:"renter_phone"` RenterPhone string `json:"renter_phone"`
RenterNickname string `json:"renter_nickname"` RenterNickname string `json:"renter_nickname"`
@@ -99,6 +111,7 @@ type FinanceDetailDTO struct {
CheckoutOwnerIncomeCent int64 `json:"checkout_owner_income_cent"` CheckoutOwnerIncomeCent int64 `json:"checkout_owner_income_cent"`
CheckoutPlatformFeeCent int64 `json:"checkout_platform_fee_cent"` CheckoutPlatformFeeCent int64 `json:"checkout_platform_fee_cent"`
OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"` OwnerWalletIncomeAmountCent int64 `json:"owner_wallet_income_amount_cent"`
OwnerEffectiveSettlementAmountCent int64 `json:"owner_effective_settlement_amount_cent"`
PaidAmountCent int64 `json:"paid_amount_cent"` PaidAmountCent int64 `json:"paid_amount_cent"`
RefundedAmountCent int64 `json:"refunded_amount_cent"` RefundedAmountCent int64 `json:"refunded_amount_cent"`
RefundingAmountCent int64 `json:"refunding_amount_cent"` RefundingAmountCent int64 `json:"refunding_amount_cent"`
@@ -109,4 +122,5 @@ type FinanceDetailDTO struct {
FinanceStatus string `json:"finance_status"` FinanceStatus string `json:"finance_status"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
SettledAt *time.Time `json:"settled_at,omitempty"` SettledAt *time.Time `json:"settled_at,omitempty"`
OfflineSettledAt *time.Time `json:"offline_settled_at,omitempty"`
} }
@@ -12,6 +12,14 @@ import (
// SQL、presenter、前端展示统一引用此口径,避免多处硬编码不一致。 // SQL、presenter、前端展示统一引用此口径,避免多处硬编码不一致。
const settlementDiffThresholdCent = 5 const settlementDiffThresholdCent = 5
const (
financeStatusNormal = "normal"
financeStatusRefundPending = "refund_pending"
financeStatusRefundFailed = "refund_failed"
financeStatusSettlementDiff = "settlement_diff"
financeStatusOfflineSettlementPending = "offline_settlement_pending"
)
func refundBizTypes() []string { func refundBizTypes() []string {
return []string{ return []string{
"cancel_refund", "cancel_refund",
@@ -10,6 +10,10 @@ type financeDetailRow struct {
OrderStatus string OrderStatus string
SettlementStatus string SettlementStatus string
RefundStatus string RefundStatus string
HandoffMode string
SettlementMode string
OfflineSettlementStatus string
OfflineSettlementAmountCent int64
RenterID uint64 RenterID uint64
RenterPhone string RenterPhone string
RenterNickname string RenterNickname string
@@ -23,6 +27,7 @@ type financeDetailRow struct {
CheckoutOwnerIncomeCent int64 CheckoutOwnerIncomeCent int64
CheckoutPlatformFeeCent int64 CheckoutPlatformFeeCent int64
OwnerWalletIncomeAmountCent int64 OwnerWalletIncomeAmountCent int64
OwnerEffectiveSettlementAmountCent int64
PaidAmountCent int64 PaidAmountCent int64
RefundedAmountCent int64 RefundedAmountCent int64
RefundingAmountCent int64 RefundingAmountCent int64
@@ -33,16 +38,17 @@ type financeDetailRow struct {
FinanceStatus string FinanceStatus string
CreatedAt time.Time CreatedAt time.Time
SettledAt *time.Time SettledAt *time.Time
OfflineSettledAt *time.Time
} }
func (r financeDetailRow) toDTO() FinanceDetailDTO { func (r financeDetailRow) toDTO() FinanceDetailDTO {
diff := r.SettlementDiffAmountCent diff := r.SettlementDiffAmountCent
status := r.FinanceStatus status := r.FinanceStatus
if status == "" { if status == "" {
status = "normal" status = financeStatusNormal
} }
if absCent(diff) < settlementDiffThresholdCent && status == "settlement_diff" { if absCent(diff) < settlementDiffThresholdCent && status == financeStatusSettlementDiff {
status = "normal" status = financeStatusNormal
} }
return FinanceDetailDTO{ return FinanceDetailDTO{
OrderID: r.OrderID, OrderID: r.OrderID,
@@ -50,6 +56,10 @@ func (r financeDetailRow) toDTO() FinanceDetailDTO {
OrderStatus: r.OrderStatus, OrderStatus: r.OrderStatus,
SettlementStatus: r.SettlementStatus, SettlementStatus: r.SettlementStatus,
RefundStatus: r.RefundStatus, RefundStatus: r.RefundStatus,
HandoffMode: r.HandoffMode,
SettlementMode: r.SettlementMode,
OfflineSettlementStatus: r.OfflineSettlementStatus,
OfflineSettlementAmountCent: r.OfflineSettlementAmountCent,
RenterID: r.RenterID, RenterID: r.RenterID,
RenterPhone: r.RenterPhone, RenterPhone: r.RenterPhone,
RenterNickname: r.RenterNickname, RenterNickname: r.RenterNickname,
@@ -63,6 +73,7 @@ func (r financeDetailRow) toDTO() FinanceDetailDTO {
CheckoutOwnerIncomeCent: r.CheckoutOwnerIncomeCent, CheckoutOwnerIncomeCent: r.CheckoutOwnerIncomeCent,
CheckoutPlatformFeeCent: r.CheckoutPlatformFeeCent, CheckoutPlatformFeeCent: r.CheckoutPlatformFeeCent,
OwnerWalletIncomeAmountCent: r.OwnerWalletIncomeAmountCent, OwnerWalletIncomeAmountCent: r.OwnerWalletIncomeAmountCent,
OwnerEffectiveSettlementAmountCent: r.OwnerEffectiveSettlementAmountCent,
PaidAmountCent: r.PaidAmountCent, PaidAmountCent: r.PaidAmountCent,
RefundedAmountCent: r.RefundedAmountCent, RefundedAmountCent: r.RefundedAmountCent,
RefundingAmountCent: r.RefundingAmountCent, RefundingAmountCent: r.RefundingAmountCent,
@@ -73,5 +84,6 @@ func (r financeDetailRow) toDTO() FinanceDetailDTO {
FinanceStatus: status, FinanceStatus: status,
CreatedAt: r.CreatedAt, CreatedAt: r.CreatedAt,
SettledAt: r.SettledAt, SettledAt: r.SettledAt,
OfflineSettledAt: r.OfflineSettledAt,
} }
} }
@@ -1,7 +1,7 @@
import { apiClient } from '@/shared/api/client' import { apiClient } from '@/shared/api/client'
import type { ApiResponse, PaginatedResult } from '@/shared/types/types' import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
// 结算差异判定阈值(分)。号主应得与实际入账差额绝对值达到此值即标红。 // 结算差异判定阈值(分)。号主应得与钱包入账/线下结算差额绝对值达到此值即标红。
// 与后端 settlementDiffThresholdCent 保持一致,改口径时两端同步。 // 与后端 settlementDiffThresholdCent 保持一致,改口径时两端同步。
export const SETTLEMENT_DIFF_THRESHOLD_CENT = 5 export const SETTLEMENT_DIFF_THRESHOLD_CENT = 5
@@ -13,6 +13,9 @@ export interface FinanceSummary {
platform_income_amount_cent: number platform_income_amount_cent: number
owner_should_income_amount_cent: number owner_should_income_amount_cent: number
owner_wallet_income_amount_cent: number owner_wallet_income_amount_cent: number
owner_effective_settlement_amount_cent: number
offline_settlement_amount_cent: number
offline_settlement_pending_amount_cent: number
settlement_diff_amount_cent: number settlement_diff_amount_cent: number
estimated_income_amount_cent: number estimated_income_amount_cent: number
successful_pay_count: number successful_pay_count: number
@@ -22,6 +25,7 @@ export interface FinanceSummary {
pending_refund_count: number pending_refund_count: number
settled_order_count: number settled_order_count: number
pending_settle_order_count: number pending_settle_order_count: number
offline_settlement_pending_count: number
financial_exception_count: number financial_exception_count: number
} }
@@ -34,6 +38,9 @@ export interface FinanceDailyItem {
platform_income_amount_cent: number platform_income_amount_cent: number
owner_should_income_amount_cent: number owner_should_income_amount_cent: number
owner_wallet_income_amount_cent: number owner_wallet_income_amount_cent: number
owner_effective_settlement_amount_cent: number
offline_settlement_amount_cent: number
offline_settlement_pending_amount_cent: number
settlement_diff_amount_cent: number settlement_diff_amount_cent: number
estimated_income_amount_cent: number estimated_income_amount_cent: number
successful_pay_count: number successful_pay_count: number
@@ -42,6 +49,7 @@ export interface FinanceDailyItem {
partial_refund_count: number partial_refund_count: number
pending_refund_count: number pending_refund_count: number
settled_order_count: number settled_order_count: number
offline_settlement_pending_count: number
} }
export interface FinancePickupSummary { export interface FinancePickupSummary {
@@ -64,6 +72,10 @@ export interface FinanceDetail {
order_status: string order_status: string
settlement_status: string settlement_status: string
refund_status: string refund_status: string
handoff_mode: string
settlement_mode: string
offline_settlement_status: string
offline_settlement_amount_cent: number
renter_id: number renter_id: number
renter_phone: string renter_phone: string
renter_nickname: string renter_nickname: string
@@ -77,6 +89,7 @@ export interface FinanceDetail {
checkout_owner_income_cent: number checkout_owner_income_cent: number
checkout_platform_fee_cent: number checkout_platform_fee_cent: number
owner_wallet_income_amount_cent: number owner_wallet_income_amount_cent: number
owner_effective_settlement_amount_cent: number
paid_amount_cent: number paid_amount_cent: number
refunded_amount_cent: number refunded_amount_cent: number
refunding_amount_cent: number refunding_amount_cent: number
@@ -87,6 +100,7 @@ export interface FinanceDetail {
finance_status: string finance_status: string
created_at: string created_at: string
settled_at?: string settled_at?: string
offline_settled_at?: string
} }
export interface FinanceDateQuery { export interface FinanceDateQuery {
@@ -54,6 +54,10 @@ function rowDiffClass(row: FinanceDailyItem) {
? 'amount-danger' ? 'amount-danger'
: '' : ''
} }
function rowOfflinePendingClass(row: FinanceDailyItem) {
return Number(row.offline_settlement_pending_amount_cent || 0) > 0 ? 'amount-warning' : ''
}
</script> </script>
<template> <template>
@@ -62,7 +66,7 @@ function rowDiffClass(row: FinanceDailyItem) {
<div class="page-header"> <div class="page-header">
<p class="eyebrow">Finance Dashboard</p> <p class="eyebrow">Finance Dashboard</p>
<h1>财务仪表盘</h1> <h1>财务仪表盘</h1>
<p>按天查看第三方收款退款平台收入号主入账和结算差异</p> <p>按天查看第三方收款退款平台收入号主钱包入账/线下结算和结算差异</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-date-picker <el-date-picker
@@ -114,9 +118,14 @@ function rowDiffClass(row: FinanceDailyItem) {
<small>结账单口径</small> <small>结账单口径</small>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<span>号主实际入账</span> <span>号主结算金额</span>
<strong>{{ moneyCent(dashboard.summary.owner_wallet_income_amount_cent) }}</strong> <strong>{{ moneyCent(dashboard.summary.owner_effective_settlement_amount_cent) }}</strong>
<small>钱包流水口径</small> <small>钱包入账 + 平台代管线下结算</small>
</div>
<div class="metric-card">
<span>待线下结算</span>
<strong>{{ moneyCent(dashboard.summary.offline_settlement_pending_amount_cent) }}</strong>
<small>{{ dashboard.summary.offline_settlement_pending_count }} 笔平台代管待处理</small>
</div> </div>
<div class="metric-card"> <div class="metric-card">
<span>退款中</span> <span>退款中</span>
@@ -174,8 +183,17 @@ function rowDiffClass(row: FinanceDailyItem) {
<el-table-column label="号主应得" width="130"> <el-table-column label="号主应得" width="130">
<template #default="{ row }">{{ moneyCent(row.owner_should_income_amount_cent) }}</template> <template #default="{ row }">{{ moneyCent(row.owner_should_income_amount_cent) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="号主入账" width="130"> <el-table-column label="结算金额" width="130">
<template #default="{ row }">{{ moneyCent(row.owner_wallet_income_amount_cent) }}</template> <template #default="{ row }">{{
moneyCent(row.owner_effective_settlement_amount_cent)
}}</template>
</el-table-column>
<el-table-column label="待线下" width="130">
<template #default="{ row }">
<span :class="rowOfflinePendingClass(row)">
{{ moneyCent(row.offline_settlement_pending_amount_cent) }}
</span>
</template>
</el-table-column> </el-table-column>
<el-table-column label="结算差异" width="130"> <el-table-column label="结算差异" width="130">
<template #default="{ row }"> <template #default="{ row }">
@@ -211,6 +229,11 @@ function rowDiffClass(row: FinanceDailyItem) {
font-weight: 700; font-weight: 700;
} }
.amount-warning {
color: #d97706;
font-weight: 700;
}
.pickup-grid { .pickup-grid {
margin-top: 12px; margin-top: 12px;
} }
@@ -71,6 +71,7 @@ function financeStatusLabel(status: string) {
refund_pending: '退款中', refund_pending: '退款中',
refund_failed: '退款失败', refund_failed: '退款失败',
settlement_diff: '结算差异', settlement_diff: '结算差异',
offline_settlement_pending: '待线下结算',
} }
return map[status] || status return map[status] || status
} }
@@ -78,7 +79,7 @@ function financeStatusLabel(status: string) {
function financeStatusType(status: string) { function financeStatusType(status: string) {
if (status === 'normal') return 'success' if (status === 'normal') return 'success'
if (status === 'settlement_diff' || status === 'refund_failed') return 'danger' if (status === 'settlement_diff' || status === 'refund_failed') return 'danger'
if (status === 'refund_pending') return 'warning' if (status === 'refund_pending' || status === 'offline_settlement_pending') return 'warning'
return 'info' return 'info'
} }
@@ -91,6 +92,23 @@ function settlementStatusLabel(status: string) {
return map[status] || status || '-' return map[status] || status || '-'
} }
function isPlatformManaged(row: FinanceDetail) {
return row.handoff_mode === 'platform' || row.settlement_mode === 'platform_managed'
}
function settlementModeLabel(row: FinanceDetail) {
return isPlatformManaged(row) ? '平台代管' : '号主钱包'
}
function offlineSettlementStatusLabel(status: string) {
const map: Record<string, string> = {
none: '无需线下结算',
pending: '待线下结算',
settled: '已线下结算',
}
return map[status] || status || '-'
}
function shortDateTime(value?: string) { function shortDateTime(value?: string) {
if (!value) return '-' if (!value) return '-'
const text = formatDateTime(value) const text = formatDateTime(value)
@@ -114,7 +132,7 @@ function defaultEndDate() {
<div class="page-header"> <div class="page-header">
<p class="eyebrow">Finance Details</p> <p class="eyebrow">Finance Details</p>
<h1>财务明细</h1> <h1>财务明细</h1>
<p>按订单核对收款退款平台收入号主应得与钱包实际入账</p> <p>按订单核对收款退款平台收入号主应得与钱包入账/线下结算</p>
</div> </div>
<div class="toolbar-actions"> <div class="toolbar-actions">
<el-button @click="resetFilters">重置</el-button> <el-button @click="resetFilters">重置</el-button>
@@ -191,7 +209,7 @@ function defaultEndDate() {
<div class="amount-cell">净入</div> <div class="amount-cell">净入</div>
<div class="amount-cell">平台</div> <div class="amount-cell">平台</div>
<div class="amount-cell">应得</div> <div class="amount-cell">应得</div>
<div class="amount-cell">入账</div> <div class="amount-cell">入账/线下</div>
<div class="amount-cell">差异</div> <div class="amount-cell">差异</div>
</div> </div>
@@ -206,7 +224,13 @@ function defaultEndDate() {
<div class="status-stack"> <div class="status-stack">
<span>{{ orderStatusLabel(row.order_status) }}</span> <span>{{ orderStatusLabel(row.order_status) }}</span>
<small>{{ settlementStatusLabel(row.settlement_status) }}</small> <small
>{{ settlementStatusLabel(row.settlement_status) }} ·
{{ settlementModeLabel(row) }}</small
>
<small v-if="isPlatformManaged(row)">
{{ offlineSettlementStatusLabel(row.offline_settlement_status) }}
</small>
<el-tag size="small" :type="financeStatusType(row.finance_status)"> <el-tag size="small" :type="financeStatusType(row.finance_status)">
{{ financeStatusLabel(row.finance_status) }} {{ financeStatusLabel(row.finance_status) }}
</el-tag> </el-tag>
@@ -232,7 +256,7 @@ function defaultEndDate() {
moneyCent(row.checkout_owner_income_cent) moneyCent(row.checkout_owner_income_cent)
}}</span> }}</span>
<span class="amount-text amount-cell">{{ <span class="amount-text amount-cell">{{
moneyCent(row.owner_wallet_income_amount_cent) moneyCent(row.owner_effective_settlement_amount_cent)
}}</span> }}</span>
<span <span
class="amount-text amount-cell" class="amount-text amount-cell"